diff --git a/.github/SECURITY.md b/.github/SECURITY.md
index ae419c1..f6efc2f 100644
--- a/.github/SECURITY.md
+++ b/.github/SECURITY.md
@@ -1,9 +1,51 @@
-# Security Policy
+# Security policy
-## Supported Versions
+pyproject-api is a Python library that drives a project's PEP 517 build backend from a separate process. This policy
+says how to report a security problem and how we decide whether a report is one.
-Only the latest released version is supported with security updates. Older versions will not receive patches.
+## 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/pyproject-api/security/advisories/new). If neither
+works for you, email [gaborjbernat@gmail.com](mailto:gaborjbernat@gmail.com).
-Please report security vulnerabilities via [GitHub's private vulnerability reporting](../../security/advisories/new).
+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 pyproject-api, 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 pyproject-api makes. A report that first needs control of something the threat model
+trusts, such as the project being built and its build backend, 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**
+
+- A `build-system.backend-path` entry that resolves outside the project being accepted as an import location.
+- A frontend call writing a result or artifact outside the directory the caller named, including while extracting
+ `.dist-info` from a built wheel.
+- Clearing a metadata directory deleting the project root, one of its ancestors, or anything reached through a symlink
+ inside it.
+- A hook result naming a path outside its output directory, returned to the caller as the artifact or metadata path.
+- Results of one backend call being read as another call's, for example through a predictable result file.
+
+**Out of scope**
+
+- Code a build backend runs: building a project runs its backend, so building untrusted source runs untrusted code, as
+ PEP 517 intends.
+- What the backend writes into the artifacts it returns.
+- The interpreter, environment and `requires` the caller provisions for the backend.
+
+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..1d602e4
--- /dev/null
+++ b/.github/THREAT_MODEL.md
@@ -0,0 +1,101 @@
+# Threat model
+
+This model says what pyproject-api 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 pyproject-api works
+
+A frontend such as tox points pyproject-api at a project folder. pyproject-api reads `[build-system]` from
+`pyproject.toml`, checks that every `backend-path` entry stays inside the project, and starts the backend in a
+subprocess of the interpreter the caller chose: `SubprocessFrontend` starts a fresh one per call, while other frontends,
+such as tox's, can keep one alive across calls (`reuse_backend`). Each hook call (`build_wheel`,
+`prepare_metadata_for_build_wheel` and so on) goes to the backend's stdin as JSON, and the backend writes its answer to
+a temporary JSON file that the frontend reads back. Without a metadata hook, pyproject-api builds a wheel and extracts
+its `.dist-info` into the caller's metadata directory.
+
+```mermaid
+flowchart LR
+ fe["Frontend, e.g. tox
(trusted)"] -->|project dir, python| api["pyproject-api"]
+ api -->|"reads [build-system]"| proj[("Project source")]
+ api -->|"spawn, JSON over stdin"| be["Backend subprocess
runs project-chosen code"]
+ be -->|"result JSON in temp file,
artifacts in output dir"| api
+ classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a;
+ classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00;
+ class fe,api trusted;
+ class proj,be 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 |
+| ----------------------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [Cargo build scripts](https://doc.rust-lang.org/cargo/reference/build-scripts.html) | Rust | A package's `build.rs` runs with full rights during a build; building a crate means trusting it. |
+| [Hatch](https://github.com/pypa/hatch/security/policy) | Python | Assumes the user has full control over the build environment. |
+| [pnpm](https://github.com/pnpm/pnpm/security/policy) | JavaScript | Draws its boundary at filesystem permissions: the store, project, lockfile and configuration are trusted, and running it in a repository means trusting that repository. |
+
+## Trusted
+
+An attacker who controls any of these already has what pyproject-api could give them:
+
+- The frontend and the project folder, interpreter, environment and output directories it passes.
+- The project's build backend and its `requires`; the backend runs with the user's rights.
+- The filesystem and temporary directory.
+
+## Untrusted inputs
+
+| Input | Comes from | What pyproject-api guarantees |
+| -------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `build-system.backend-path` | The project's `pyproject.toml` | Each entry must be relative and resolve inside the project; otherwise the call fails. |
+| Backend stdout and stderr | The backend and anything it imports | Captured and returned to the caller as text; hook results come only from the result file. |
+| Malformed `[build-system]` | The project's `pyproject.toml` | Wrong types raise a `ValueError` naming the key; a missing table falls back to the legacy setuptools backend, as PEP 517 specifies. |
+| Members of a built wheel | The backend | Only members under a top-level `.dist-info` directory are extracted, through `zipfile`'s path sanitizing. |
+| Entries inside a metadata directory to clear | Earlier builds, or the backend | Refused when it is the project root or one of its ancestors; symlinks inside it are unlinked, never followed. |
+
+## Threats and controls
+
+| STRIDE | Threat | Control |
+| ---------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------ |
+| Tampering | `backend-path = ["../other"]` loads a backend from outside the project. | Entries outside the project are rejected. |
+| Spoofing | Backend output printed to stdout is taken as a hook's result. | Results travel through a per-call temporary JSON file, not stdout. |
+| Elevation of privilege | A backend runs code during a build. | Accepted by design; PEP 517 builds run the project's code. |
+
+## 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 |
+| -------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------ |
+| `backend-path` with `../` imports a module from the parent directory | Vulnerability | The in-tree guarantee of PEP 517 is broken. |
+| Building a malicious sdist runs its `setup.py` | Not a vulnerability | The backend is trusted; build untrusted source in a sandbox. |
+| A backend that prints JSON confuses `get_requires_for_build_wheel` | Bug, or vulnerability if the result file is bypassed | Results must come only from the result file. |
+
+## Severity
+
+- **High:** a frontend call deletes or writes outside the directory the caller named, or clearing a metadata directory
+ removes the project or follows a symlink inside it.
+- **Medium:** a `backend-path` outside the project is accepted, a hook's returned path escapes its directory, or one
+ call's result is read as another's.
+- **Low:** a malformed backend response crashes or hangs the frontend instead of failing with an error.
+
+## Accepted risks
+
+- Every hook call runs project code; isolation from the host is the caller's job.
+- The backend subprocess inherits the environment the caller passes, secrets included.
+
+## Keeping this current
+
+Revisit this model when pyproject-api 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.