docs(security): add release-blocking threat model#91
Merged
Conversation
Apache Steward automates the ASF 16-step security-issue lifecycle, but until now the framework had no formal threat model: no enumeration of trust boundaries, adversary personas, or per-skill STRIDE rows for the security skill family. PR apache#81 landed nine prompt-injection findings without a document the next finding could be filed against, and the existing setup-level threat model in `docs/setup/secure-agent-internals.md` covers the agent host but not the eight security skills that compose the lifecycle. This change adds `docs/security/threat-model.md` (660 lines) — five trust boundaries, five adversary personas, a STRIDE matrix per skill family (27 rows), four cross-skill threats, a 29-entry mitigation cross-reference, eleven residual-risk entries with explicit re-eval triggers, and a re-audit cadence that gates every new Mode C skill on adding a STRIDE row. Backlinks added from the security index, process, roles, and how-to-fix guides so any reader of the security docs reaches the threat model in one click. The doc is overhead beyond ASF convention (no observed apache/* repo ships a comparable artefact), justified by the framework's pre-TLP release-blocking posture and the per-adopter blast radius of any security skill bug.
The doctoc-generated TOC URL-encoded the unicode `↔` character in the B1–B5 headings (anchors became `%E2%86%94` in the body links), which markdownlint MD051 rejects as invalid link fragments. Replaces `↔` with the word `and` in the heading text only — the body of the document continues to use `↔` where it improves readability — so the generated anchors are ASCII and round-trip cleanly. Adds a `text` language identifier to the trust-boundary diagram fenced code block to satisfy MD040.
7 tasks
potiuk
pushed a commit
that referenced
this pull request
May 7, 2026
…n` (#93) * feat(security): implement M.29 sandbox-lint for `.claude/settings.json` Mitigation M.29 in `docs/security/threat-model.md` (PR #91) committed to lint the agent-host sandbox configuration in CI on every PR that touches it. This is the implementation: - `tools/sandbox-lint/` — new stdlib-only Python project. The CLI compares `.claude/settings.json` against the canonical baseline at `tools/sandbox-lint/expected.json` (set semantics on `denyRead`, `allowRead`, `allowWrite`, `allowedDomains`, `deny`, `ask`) and runs three layers of hard invariants — required `denyRead` entries, forbidden `allowRead` and `allowWrite` paths, required `permissions.deny` entries — against both the live settings and the baseline itself. The same invariants applied to the baseline catch the case where a future PR weakens both files in lockstep. - `.github/workflows/sandbox-lint.yml` — runs the linter on every PR that touches `.claude/settings.json`, the baseline, or the linter code. Path-scoped so the rest of the matrix is unaffected. - `.pre-commit-config.yaml` — adds `ruff check`, `ruff format --check`, `mypy`, and `pytest` hooks for the new project; the pytest hook also fires when `.claude/settings.json` changes because the test suite loads both files. - `.github/workflows/tests.yml` — adds the new project to the per-project pytest matrix so the visible-signal lane reports pass/fail in the CI checks list. Threat-model cross-references are in `tools/sandbox-lint/README.md`. The X3 residual (a maintainer editing the file locally outside a PR) remains accepted; the lint gates the shipped configuration, not local overrides during a single agent run. Generated-by: Claude Opus 4.7 * fix(sandbox-lint): satisfy lychee and CodeQL on the M.29 PR Two CI failures on PR #93 against `apache/airflow-steward`: - `lychee` fails because `tools/sandbox-lint/README.md` linked to `docs/security/threat-model.md` paths and anchors — that file lands in a companion PR and is not on `main` yet. Replace the three direct links with prose references that note the threat-model doc is a companion PR; the lint stands on its own. - `CodeQL` (`py/incomplete-url-substring-sanitization`) fires on the test sentinel `evil.example.com` because the rule keys on hostname-shaped literals appearing in `in` substring checks. This is test code asserting against a diff message, not URL validation, but the rule is right that hostname-shaped sentinels are a footgun. Rename to `sandbox-lint-test-extra-marker` so the sentinel is unambiguously not a URL pattern. Generated-by: Claude Opus 4.7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
docs/security/threat-model.md— the framework's first formal threat model: 5 trust boundaries, 5 adversary personas (P1 malicious reporter, P2 hostile public contributor, P3 compromised supply-chain dep, P4 network-layer adversary, P5 negligent insider), a STRIDE matrix per skill family (27 rows across A inbound import, B triage/reconciliation, C CVE allocation, D public remediation, E closure), 4 cross-skill threats, a 29-entry mitigation cross-reference table, 11 residual-risk entries with explicit re-eval triggers, and a re-audit cadence.process.md,roles.md, andhow-to-fix-a-security-issue.mdso a reader enteringdocs/security/from any door reaches the threat model in one click.Why this overhead is the right call
ASF does not mandate a threat-model artefact, and no observed apache/* repo ships a comparable document. The framework's posture is different from a typical ASF project: Apache Steward is pre-TLP, ships release-blocking security automation, and is designed to be adopted by multiple ASF projects' security teams. The blast radius of a security-skill bug crosses every adopter. The threat model gates every new Mode C skill on adding a STRIDE row — a discipline proportional to that radius.
The existing setup-level threat model at
docs/setup/secure-agent-internals.mdยง Threat model covers the agent host (env vars, filesystem leak, sandbox prompt-injection). This new document covers the security workflow lifecycle and the 8 security skills. The two are complementary, not overlapping.What's in scope
docs/security/README.md: import (3), sync, deduplicate, invalidate, cve-allocate, fix..claude/settings.json.gh, Vulnogram OAuth, Gmail OAuth.Out of scope (called out in the doc)
security-issue-fix(proposed but not shipped).lists.apache.org,cve.org, GitHub).Review notes
Test plan
doctocreports clean onthreat-model.mdand on the four wiring files.(#anchor)body references resolve to actual headings.AGENTS.md,tools/vulnogram/record.md,security-issue-sync/SKILL.md,security-issue-fix/SKILL.md,docs/modes.md) for the load-bearing claims.Follow-ups (out of scope here)
.claude/settings.jsonagainst the shipped baseline) — currently flagged asPlanned, not yet shipped.tokens.mdtemplate underprojects/_template/per residual risk secure-agent-setup: layered credential isolation + 7-day pinned tools #11.