diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..d3035c3 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,52 @@ +# Security policy + +release-creator is a command-line tool that validates news fragments, links them to pull requests and authors, renders +changelog sections into GitHub Release notes, and checks published JetBrains plugin archives. 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/release-creator/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 release-creator, 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 release-creator makes. A report that first needs control of something the threat model +trusts, such as the repository's Git history, the GitHub API answers `gh` returns, or the caller's options, 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 fragment from a pull request passing `validate-news` while it carries a hand-written author credit or pull request + link, so release notes credit or link someone the history does not show. +- Fragment or changelog text breaking out of its section in rendered notes, for example to add a fake `New Contributors` + or `Full Changelog` entry. +- A plugin ZIP comparison reporting a match when member contents, types or permissions differ. +- Data from Git or GitHub reaching a GraphQL query or a command as anything but data. + +**Out of scope** + +- The wording of a fragment: maintainers review it before it merges, as they review code. +- The token `gh` uses and what that token may do. +- Mentions in rendered notes that GitHub turns into notifications. +- Correctness of `towncrier` itself. + +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..bcea3a1 --- /dev/null +++ b/.github/THREAT_MODEL.md @@ -0,0 +1,102 @@ +# Threat model + +This model says what release-creator 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 release-creator works + +In a pull request, `validate-news` checks each changed fragment against the Towncrier categories and rejects +hand-written credits and pull request links. When a release is prepared, `fragments` finds each fragment's squash-merged +pull request through `git log` and `gh api graphql`, writes the link and the commit authors, and Towncrier builds the +changelog. `notes` renders one tagged changelog section into a GitHub Release body with package and comparison links. +`marketplace` downloads a published JetBrains plugin and compares its members, by SHA-256, type and permissions, with +the local archive. The `anchors`, `changelog`, `contributors` and `tags` commands add Markdown anchors, preview the +changelog, look up contributors and calculate tags from the same trusted history. Every command runs `git` and `gh` with +argument lists, never a shell. + +```mermaid +flowchart LR + pr[("PR fragments
(untrusted until merged)")] -->|"validate-news"| rc["release-creator"] + git[("Git history
(trusted)")] --> rc + gh["gh api graphql
(trusted answers)"] --> rc + rc -->|"links, credits"| cl[("Changelog")] + cl -->|"notes"| body["GitHub Release body"] + mk[("Marketplace ZIP")] -->|"marketplace"| rc + classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a; + classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00; + class git,gh,rc trusted; + class pr,cl,body,mk 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 | +| ------------------------------------------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| [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. | +| [release-please](https://github.com/googleapis/release-please/security/policy) | TypeScript | Changelog and release PR automation; reports go through Google's vulnerability intake. | +| [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. | +| [Towncrier](https://github.com/twisted/towncrier) | Python | Builds the changelog from fragments release-creator validated; it follows the Twisted project's security process. | + +## Trusted + +An attacker who controls any of these already has what release-creator could give them: + +- The repository's Git history and the merged changelog. +- Answers from the GitHub API through `gh`, and the token it uses. +- The command-line options, including the repository, tag and artifact URLs. + +## Untrusted inputs + +| Input | Comes from | What release-creator guarantees | +| ---------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------- | +| Fragments in a pull request | Any contributor | Category, name and content are validated; hand-written credits and pull request links are rejected. | +| A downloaded Marketplace ZIP | The Marketplace, after recompression | Members are hashed in 1 MiB chunks; duplicate member names are rejected. | + +## Threats and controls + +| STRIDE | Threat | Control | +| --------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------- | +| Spoofing | A fragment credits a maintainer for a contribution they did not make. | Credits come from the merge commit's authors; hand-written ones fail validation. | +| Tampering | Changelog text forges a section in the release body. | Report any text that escapes its section. | +| Tampering | A GraphQL query is altered by repository data. | Owner and name are JSON-encoded; commit SHAs come from `git`. | + +## 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 | +| ---------------------------------------------------------- | ------------------- | -------------------------------------- | +| A PR fragment with `by @maintainer` passes `validate-news` | Vulnerability | Hand-written credits must be rejected. | +| A merged fragment says something misleading | Not a vulnerability | Maintainers reviewed and merged it. | +| The ZIP check passes although a member's mode changed | Vulnerability | Comparison must cover mode. | +| `notes` mis-renders a nested list | Bug | Rendering only. | + +## Severity + +- **High:** pull request data reaches a shell, a GraphQL query or a command as code, or the plugin ZIP comparison + reports a match for differing contents, types or permissions. +- **Medium:** a fragment with a hand-written credit or link passes `validate-news`, or fragment text forges a section of + the rendered notes. +- **Low:** a crash on unusual history or changelog text instead of a clean error. + +## Accepted risks + +- Release notes mention GitHub users, and GitHub may notify them. + +## Keeping this current + +Revisit this model when release-creator 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.