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.