Threat model of ThreatSpec itself

ThreatSpec runs inside a coding agent with read/write access to a repository. This is its own threat model, in the same shape it asks of others.

🔗What we are working on

AssetTypeWhy it matters
spec.md, plan.md, tasks.mdproject artifactssource of intent; ThreatSpec writes into two of them
threat-model.yamlsecurity recorddrives what gets built and what counts as verified
Agent sessionagentexecutes commands and scripts with the developer’s privileges
threatspec-config.ymlconfigurationverification.test_command is executed by converge
Verdicts fileagent outputbecomes append-only verification history

Actors: the developer (trusted), other contributors to the repo (semi-trusted), authors of content the spec or code quotes (untrusted), package registries (semi-trusted).

🔗What can go wrong

IDThreatCategoryMitigationStatus
T1Instruction-like text in spec.md, plan.md, code, tests, or fetched pages steers the agent (indirect prompt injection) into marking threats mitigated, writing bogus verdicts, or running commandsTamperingEvery command treats artifact content as data, quotes suspicious text under “Unverified”, and never follows it; verified needs an evidence pointer the engine checks; status roll-up is computed, not assertedmitigated by command text; residual risk remains because LLM adherence is probabilistic
T2$ARGUMENTS is used to inject instructions or shell textTamperingArguments only select scope; commands never pass them to a shell; the engine takes explicit flagsmitigated
T3The SR block overwrites human-authored spec contentTamperingWrites are confined to the <!-- threatspec:begin/end --> markers; first insertion goes before ## Success Criteria; covered by testsmitigated
T4Converge rewrites or renumbers existing tasksTamperingAppend-only contract; byte-for-byte unchanged when converged; covered by testsmitigated
T5verification.test_command or scanners execute arbitrary commandsElevation of PrivilegeEmpty by default; only the configured command runs, once, from the repo root; config is versioned and reviewable; the command prompt forbids running anything elseaccepted: config is developer-owned, same trust as any project script
T6The bash/PowerShell wrapper runs uv run --with pyyaml --with jsonschema, pulling packages from PyPI at first useTampering (supply chain)Only when no local Python with PyYAML exists; pinned to two widely used packages; teams can pre-install PyYAML to avoid itaccepted until v0.2 adds version pins
T7A malicious verdicts file marks everything verifiedRepudiationVerdicts require an evidence pointer; entries record commit and timestamp; history is append-only and reviewable in diffspartially mitigated: the evidence pointer is not opened by the engine
T8Secrets or system prompts from artifacts are copied into threat-model.md or the SR blockInformation DisclosureCommands forbid verbatim reproduction; the renderer only emits model fieldsmitigated by command text
T9Hook made mandatory by a project runs ThreatSpec without the developer noticingElevation of PrivilegeAll hooks ship optional: true; changing that is an explicit, versioned edit of .specify/extensions.ymlmitigated
T10Baseline extends path points outside the repositoryInformation DisclosureThe engine resolves the path but only reads YAML; v0.2 will reject paths outside the repo rootopen, low

🔗Did we do a good job

Tests cover T3, T4, T7 (evidence pointer required), and the append-only contract. T1 and T8 rely on prompt discipline and should be exercised with adversarial fixtures (a spec containing injected instructions) as part of the v0.2 test suite. T6 and T10 are tracked for v0.2.

Edit this page on GitHub