From ba52b89fda3488e0e32d7b68c20b92053c9e3d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 28 Oct 2026 00:47:25 -0700 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=9D=20docs(security):=20add=20a=20?= =?UTF-8?q?threat=20model=20and=20align=20the=20policy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/SECURITY.md | 55 ++++++++++++++++++++++----- .github/THREAT_MODEL.md | 84 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 .github/THREAT_MODEL.md diff --git a/.github/SECURITY.md b/.github/SECURITY.md index fa935b0..16e484b 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,13 +1,50 @@ -# Security Policy +# Security policy -## Supported Versions +sphinx-argparse-cli is a Sphinx extension that renders the command-line interface an `argparse` parser defines. This +policy says how to report a security problem and how we decide whether a report is one. -| Version | Supported | -| ------- | ------------------ | -| 1.10 + | :white_check_mark: | -| < 1.10 | :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/sphinx-argparse-cli/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 sphinx-argparse-cli, 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 sphinx-argparse-cli makes. A report that first needs control of something the threat +model trusts, such as the documented project's code or the Sphinx configuration, 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** + +- The directive importing a module, or calling a function, other than the ones its `:module:` and `:func:` options name. +- The extension reading or writing files beyond the Sphinx build's own outputs. + +**Out of scope** + +- Code that runs when the directive imports the module and calls the parser factory: that is the documented project's + code, trusted like every module autodoc imports. +- Markup in help strings: the extension parses them as reStructuredText on purpose, so they are trusted like the code + that defines them. +- Markup the documentation author writes around the directive. +- Sphinx and docutils behavior. + +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..ecfb10a --- /dev/null +++ b/.github/THREAT_MODEL.md @@ -0,0 +1,84 @@ +# Threat model + +This model says what sphinx-argparse-cli 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 sphinx-argparse-cli works + +The `sphinx_argparse_cli` directive imports the module named by `:module:`, gets the parser either by calling the +function named by `:func:` or, with `:hook:`, by running that function while `ArgumentParser.parse_known_args` is +patched to capture the parser. It walks the parser's actions and subcommands and emits docutils nodes for them, which +Sphinx then renders. + +```mermaid +flowchart LR + conf[("conf.py and .rst
(trusted)")] --> ext["sphinx-argparse-cli"] + ext -->|"import :module:, call :func:"| code["Project code
(trusted)"] + code -->|"ArgumentParser: help, choices"| ext + ext -->|"docutils nodes"| html["Built docs"] + classDef trusted fill:#cfe4ff,stroke:#1f6feb,color:#0b1f3a; + classDef boundary fill:#ffe3a3,stroke:#d29200,color:#3a2c00; + class conf,ext,code trusted; + class html 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 | +| ------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------- | +| [Sphinx autodoc](https://www.sphinx-doc.org/en/stable/usage/extensions/autodoc.html) | Python | Imports the modules it documents and warns that their import-time side effects run; the documented code is trusted. | +| [TypeDoc](https://github.com/TypeStrong/typedoc/security/policy) | TypeScript | Documentation generator that loads the project's own compiler setup; patches the latest minor version. | +| [Hugo](https://gohugo.io/about/security/) | Go | Templates and configuration are trusted, content is not, and output is escaped by default. | + +## Trusted + +An attacker who controls any of these already has what sphinx-argparse-cli could give them: + +- The Sphinx configuration and the documents that use the directive. +- The documented project, its modules and the parser factory, which run during the build. + +## Untrusted inputs + +| Input | Comes from | What sphinx-argparse-cli guarantees | +| ----------------- | -------------------------------------- | -------------------------------------------------------------- | +| Directive options | The `.rst` source of the documentation | Select only which module, function and parser parts to render. | + +## Threats and controls + +| STRIDE | Threat | Control | +| ---------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| Tampering | A help string with `.. raw:: html` reaches the HTML. | By design: help text is reStructuredText from trusted code; disable `raw` in Sphinx if contributors are not trusted. | +| Elevation of privilege | Building docs runs the project's code. | Accepted, as in Sphinx autodoc. | + +## 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 hash-checked from `pylock.toml` with +a seven-day upload cooldown; 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 `:module:` of `a.b` imports `c` instead | Vulnerability | Import target is a guarantee. | +| A help string with `.. raw:: html` adds a script | Not a vulnerability | Help text is trusted reStructuredText. | +| The parser factory deletes files during the docs build | Not a vulnerability | Documented code is trusted. | +| Subcommand order differs from `--help` | Bug | Rendering only. | + +## Accepted risks + +- Building documentation executes the documented module's import-time code and parser factory. + +## Keeping this current + +Revisit this model when sphinx-argparse-cli 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. From a2f7d17dbc29f12689b7c210c905aaf181fe2254 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Wed, 28 Oct 2026 07:09:47 -0700 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20docs(security):=20check=20th?= =?UTF-8?q?e=20threat=20model=20against=20the=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/THREAT_MODEL.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/THREAT_MODEL.md b/.github/THREAT_MODEL.md index ecfb10a..34b851b 100644 --- a/.github/THREAT_MODEL.md +++ b/.github/THREAT_MODEL.md @@ -7,9 +7,9 @@ two disagree, SECURITY.md wins and we fix this file. ## How sphinx-argparse-cli works The `sphinx_argparse_cli` directive imports the module named by `:module:`, gets the parser either by calling the -function named by `:func:` or, with `:hook:`, by running that function while `ArgumentParser.parse_known_args` is -patched to capture the parser. It walks the parser's actions and subcommands and emits docutils nodes for them, which -Sphinx then renders. +function named by `:func:` or, with `:hook:`, by running that function while `ArgumentParser.parse_known_args` and +`parse_known_intermixed_args` are patched to capture the parser. It walks the parser's actions and subcommands and emits +docutils nodes for them, parsing help text as reStructuredText, and writes its stylesheet into the build output. ```mermaid flowchart LR @@ -34,7 +34,7 @@ the attacker controls. | Project | Language | Where it draws the line | | ------------------------------------------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------- | | [Sphinx autodoc](https://www.sphinx-doc.org/en/stable/usage/extensions/autodoc.html) | Python | Imports the modules it documents and warns that their import-time side effects run; the documented code is trusted. | -| [TypeDoc](https://github.com/TypeStrong/typedoc/security/policy) | TypeScript | Documentation generator that loads the project's own compiler setup; patches the latest minor version. | +| [TypeDoc](https://github.com/TypeStrong/typedoc/security/policy) | TypeScript | Documentation generator; patches the latest minor version and accepts community fixes for the one before. | | [Hugo](https://gohugo.io/about/security/) | Go | Templates and configuration are trusted, content is not, and output is escaped by default. | ## Trusted @@ -60,8 +60,8 @@ An attacker who controls any of these already has what sphinx-argparse-cli could ## 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 hash-checked from `pylock.toml` with -a seven-day upload cooldown; PyPI receives the files through +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. @@ -74,6 +74,12 @@ Every workflow pins third-party actions to a commit SHA. | The parser factory deletes files during the docs build | Not a vulnerability | Documented code is trusted. | | Subcommand order differs from `--help` | Bug | Rendering only. | +## Severity + +- **High:** the directive imports a module or calls a function other than the ones its options name. +- **Medium:** the extension reads or writes files beyond the Sphinx build's own outputs. +- **Low:** a crash on an unusual parser instead of a clean build error. + ## Accepted risks - Building documentation executes the documented module's import-time code and parser factory.