diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index 5968e5b..d1bac24 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -1,13 +1,49 @@
-# Security Policy
+# Security policy
-## Supported Versions
+devpi-process is a Python library that starts a throwaway devpi server, creates indexes on it, and uploads packages, for
+use in tests. This policy says how to report a security problem and how we decide whether a report is one.
-| Version | Supported |
-| ------- | ------------------ |
-| 1.0.0 + | :white_check_mark: |
-| < 1.0.0 | :x: |
+## Reporting a vulnerability
-## Reporting a Vulnerability
+**Do not open a public issue for a security problem.** Use
+[GitHub's private vulnerability reporting](https://github.com/tox-dev/devpi-process/security/advisories/new). If you do
+not have a GitHub account, or you are a Tidelift subscriber, report through the
+[Tidelift security contact](https://tidelift.com/security) instead; Tidelift coordinates the fix and the disclosure with
+us. If neither works for you, email [gaborjbernat@gmail.com](mailto:gaborjbernat@gmail.com).
-To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift
-will coordinate the fix and disclosure.
+Include what the attacker controls and what they gain, the steps or a proof of concept, and the versions and platform
+you tested. Write the report yourself; unreviewed tool or model output takes longer to triage.
+
+## What to expect
+
+Volunteers maintain devpi-process, with no on-call rotation. We aim to acknowledge a report within **5 working days**
+and assess it within **15 working days**. Fixes are developed in a private fork attached to the draft advisory, ship in
+a new release, and the advisory, with a CVE requested through GitHub, goes out with that release. The default embargo is
+**90 days** from the report; we agree any change with the reporter. We credit reporters in the advisory under the name
+they choose, or not at all. There is no bug bounty.
+
+## Supported versions
+
+Only the latest release receives fixes. Confirm the problem reproduces on it before reporting.
+
+## Vulnerability or bug
+
+A report is a vulnerability when an attacker who controls only an input that the [threat model](THREAT_MODEL.md) lists
+as untrusted breaks a guarantee devpi-process makes. A report that first needs control of something the threat model
+trusts, such as the test code that drives it or the devpi tools it runs, is a bug: we fix it in the open and publish no
+advisory. Classify by who supplies the dangerous input, not by how bad the outcome sounds.
+
+**In scope**
+
+- devpi-process starting the server on an interface other than localhost when the caller passes no `start_args`.
+- devpi-process running a program other than the `devpi-init`, `devpi-server` and `devpi` in the interpreter's scripts
+ directory, or passing caller data to them as anything but separate arguments.
+- Server data written outside the directory the caller passed.
+
+**Out of scope**
+
+- Using devpi-process as a production package index: it is a test helper.
+- Other local users reaching the server's port or reading its command line on a shared machine (see accepted risks).
+- Vulnerabilities in devpi itself; report them to [devpi](https://github.com/devpi/devpi).
+
+If you cannot tell which side a finding falls on, report it privately and we will decide.
diff --git a/.github/THREAT_MODEL.md b/.github/THREAT_MODEL.md
new file mode 100644
index 0000000..a403d69
--- /dev/null
+++ b/.github/THREAT_MODEL.md
@@ -0,0 +1,93 @@
+# Threat model
+
+This model says what devpi-process trusts, where untrusted input enters, and which guarantees hold at each boundary, so
+a report can be classified as a vulnerability or a bug. [SECURITY.md](SECURITY.md) covers reporting; if the two
+disagree, SECURITY.md wins and we fix this file.
+
+## How devpi-process works
+
+`IndexServer(path)` runs `devpi-init` to initialize a server directory with a random root password, starts
+`devpi-server` on a free port at its default host, `localhost`, and drives the `devpi` client to log in as root, create
+indexes and upload distributions. The three programs come from the interpreter's scripts directory and run with argument
+lists, never a shell. Upstream PyPI is off unless `with_root_pypi` is set. When the context manager exits, the server
+process stops.
+
+```mermaid
+flowchart LR
+ test["Test code
(trusted)"] --> dp["devpi-process"]
+ dp -->|"devpi-init, devpi-server,
devpi (argument lists)"| srv["devpi server on localhost"]
+ srv <--> dir[("Server directory")]
+ local["Other local users"] -.->|"can reach the port"| srv
+ classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a;
+ classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00;
+ class test,dp trusted;
+ class srv,dir,local boundary;
+```
+
+## Compared with similar tools
+
+We drew the boundary where comparable projects in other ecosystems draw theirs. The shared rule, stated by the
+[Node.js threat model](https://github.com/nodejs/node/blob/main/SECURITY.md#the-nodejs-threat-model), is that a report
+which first needs a trusted element compromised is out of scope; the first triage question, as
+[DOMPurify](https://github.com/cure53/DOMPurify/blob/main/SECURITY.md#what-we-consider-a-vulnerability) puts it, is what
+the attacker controls.
+
+| Project | Language | Where it draws the line |
+| --------------------------------------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| [Verdaccio](https://github.com/verdaccio/verdaccio/security/policy) | JavaScript | Private registry; reports go through Snyk. |
+| [distribution (registry)](https://github.com/distribution/distribution/security/policy) | Go | The OCI registry used as a test fixture; private reports to its CNCF list. |
+| [Vitest](https://github.com/vitest-dev/vitest/security/policy) | JavaScript | Trusts the developer, the config file, plugins and every project file; only reports that need no trusted element compromised count. |
+
+## Trusted
+
+An attacker who controls any of these already has what devpi-process could give them:
+
+- The test code and every path, index name, argument and file it passes, `start_args` included.
+- The devpi tools in the interpreter's scripts directory, and the packages uploaded.
+
+## Untrusted inputs
+
+| Input | Comes from | What devpi-process guarantees |
+| --------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------- |
+| Network clients | Anything that can connect to the port | The server listens on `localhost` unless `start_args` change it; uploads need the generated password. |
+
+## Threats and controls
+
+| STRIDE | Threat | Control |
+| ---------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------- |
+| Spoofing | Another process logs in as root. | A random root password is generated for each server. |
+| Information disclosure | Another local user reads the root password. | Accepted risk: it is passed to `devpi-init` and `devpi login` on the command line. |
+| Tampering | Caller data injects shell syntax into a devpi command. | Programs run with argument lists, never a shell. |
+
+## Release chain
+
+Releases run on the shared [tox-dev/github-workflows](https://github.com/tox-dev/github-workflows) pipeline. A release
+App, not a person, pushes the release commit and tag; the build installs its tools from a hash-checked `pylock.toml`
+resolved with a seven-day upload cooldown by default; PyPI receives the files through
+[trusted publishing](https://docs.pypi.org/trusted-publishers/) with attestations, and the GitHub Release is immutable.
+Every workflow pins third-party actions to a commit SHA.
+
+## Vulnerability or bug
+
+| Report | Verdict | Why |
+| --------------------------------------------------------------- | ------------- | ------------------------------------------- |
+| The server listens on `0.0.0.0` by default | Vulnerability | Exposes a test index to the network. |
+| An index name with `;` runs a shell command | Vulnerability | Arguments must stay data. |
+| Another user on a shared CI host sees the root password in `ps` | Accepted risk | Documented below; use a single-user runner. |
+
+## Severity
+
+- **High:** a caller value runs as a shell command, or the server listens beyond localhost without `start_args` asking
+ for it.
+- **Medium:** server data written outside the caller's directory, or a program other than the devpi tools runs.
+- **Low:** the server fails to stop, or leaves files behind, after the context manager exits.
+
+## Accepted risks
+
+- The root password is 8 letters from Python's `random`, and it is visible in the process list while `devpi-init` and
+ `devpi login` run. devpi-process is for tests on single-user machines and CI runners.
+
+## Keeping this current
+
+Revisit this model when devpi-process gains a new input source, network access, a new way to run code, or a new place it
+writes to, and whenever an advisory is published.