| name | threat-model |
| description | Build and maintain a threat model for a target codebase. Six modes: "interview" walks an application owner through the four-question framework; "bootstrap" derives a model from code plus past vulnerabilities (CVEs, git history, pentest reports, --context docs) when no owner is available; "bootstrap-then-interview" chains the two; "review" measures an existing threat model's drift against the current code and offers to apply fixes; "update" applies targeted feedback without regenerating; "pr" threat-models a diff and returns an approve/request-changes assessment. Output is always named after the target (<repo>-threat-model.md); legacy THREAT_MODEL.md files are still read and are migrated on write. Emissions share one schema gated by lint_threat_model.py. Use when asked to "threat model", "map the attack surface", "is the threat model still current", "threat model this MR/PR/diff", or "what should we be worried about in this codebase". |
| argument-hint | [bootstrap-then-interview|bootstrap|interview|review|update|pr] <target-dir> [--vulns <file>] [--design-doc <file>] [--context <paths>] [--seed <model-file>] [--base <ref>] [--auto|--apply] [--fresh] |
| user-invocable | true |
| allowed-tools | ["Read","Glob","Bash(python3 *checkpoint.py:*)","Bash(python3 *lint_threat_model.py:*)","Grep","Write","Bash(git:*)","Bash(mv:*)","Bash(gh api:*)","Bash(find:*)","Bash(ls:*)","Bash(cat:*)","AskUserQuestion","Task"] |
threat-model
A threat model answers "what could go wrong with this system, who would do
it, and what should we do about it?" independently of whether any specific
bug has been found yet. It is the map; vulnerability discovery is the metal
detector. A good threat model tells the pipeline where to look and tells triage
which findings matter.
Litmus test: If patching one line of code makes an entry disappear, it was
a vulnerability, not a threat. A threat ("attacker achieves RCE via untrusted
media parsing") still stands after every known bug is fixed; a vulnerability
("dr_wav.h:412 doesn't bounds-check chunk_size") does not. This skill
produces threats. Vulnerabilities appear only as evidence that raises a
threat's likelihood score.
Invocation: /threat-model [bootstrap-then-interview|bootstrap|interview|review|update|pr] <target-dir> [flags]
Model filename (<model-file>, applies to every mode that writes).
The model file is always named after its target:
<name>-threat-model.md, where <name> is the repo name from
git -C <target-dir> remote get-url origin (basename, .git stripped),
falling back to <target-dir>'s directory basename when there is no
git remote. Portfolio copies may be branch-suffixed
(<name>__release-4.22-threat-model.md). Resolve <model-file> once at
startup and state it in your first response. THREAT_MODEL.md is a
legacy name: still read (resolution below), never written.
Model-file resolution (applies to every mode that reads an existing
model — review, update, pr cross-reference, --seed). Resolve in
this order:
- The argument is a file path → use it as-is.
- Exactly one
*-threat-model.md exists in <target-dir> → use it.
<target-dir>/THREAT_MODEL.md exists (legacy name) → use it.
- Several
*-threat-model.md exist (branch variants) → interactive:
ask which one; --auto/non-interactive: stop and list them — never
guess a branch variant.
- None → the mode's no-model behavior (review/update stop and suggest
bootstrap; pr proceeds without cross-referencing).
Write-back targets the file that was resolved — review/update on a
portfolio artifact must never create a stray model file next to it —
with one exception: when the resolved file is a legacy
THREAT_MODEL.md, any mode that writes first renames it to
<model-file> (git mv if tracked, plain mv otherwise), tells the
user about the migration, and targets the new name from then on.
Paths: <skill-base> is this skill's base directory (injected by the
runtime as "Base directory for this skill"; it is
ai-security-harness/harnessing/threat-model — interview.md,
bootstrap.md, review.md, pr.md, and schema.md live there). <harness> is the
ai-security-harness repo root, i.e. <skill-base>/../..; checkpoint I/O
uses python3 <harness>/scripts/checkpoint.py. Resolve both to absolute
paths once at startup.
Step 0 — Safety preamble (always runs first)
This skill performs static analysis only. It reads source, git history,
and any vulnerability reports the user supplies, and writes a single output
file (<target-dir>/<model-file>). It does not build, execute, fuzz, or
modify the target, and does not make network requests against the target's
infrastructure.
Before proceeding, confirm and state in your first response:
- The target directory exists and is a local checkout you can read.
- You will not execute any code from the target directory.
- If
--vulns points at a URL or you are asked to "fetch CVEs", you will
query only public advisory databases (NVD, GitHub Security Advisories, the
project's own issue tracker) and never the target's live deployment.
If the user asks you to validate a threat by running an exploit, decline and
point them at the in-repo validate-findings skill instead.
Step 1 — Route to a mode
Parse $ARGUMENTS:
| First token | Route to |
|---|
interview | Read interview.md in this directory and follow it. |
bootstrap | Read bootstrap.md in this directory and follow it. |
bootstrap-then-interview | Bootstrap first, then interview seeded from the draft. See below. |
review | Read review.md and follow its review flow (drift measurement, then an interactive offer to apply; --auto = report-only, --apply = apply all — both prompt-free for batch runs). |
update | Read review.md and follow its update flow (targeted changes, continuity rules). |
pr | Read pr.md and follow it (diff-scoped assessment; does NOT write a model file). |
| anything else, or empty | Interactive routing — see below. |
Interactive routing (no mode token). Two questions, not a form:
- If model-file resolution (above) finds an existing model, first ask:
"A threat model already exists here (, dated
). Review it against the current code, update it with specific
changes, or rebuild from scratch?" →
review / update / continue
to question 2.
- Ask: "Is someone who owns or built this system available to answer
questions in this session?" Yes and the codebase is checked out →
recommend
bootstrap-then-interview. Yes but no codebase →
interview.md. No → bootstrap.md.
Interactivity is an option, never a requirement. Every prompt in this
skill fires only when the invocation left a decision open: an explicit mode
token skips the routing questions; bootstrap and pr never prompt;
review --auto (report-only) and review --apply (accept all) are
prompt-free for batch sweeps; update with feedback in the invocation
proceeds without asking. Only interview is inherently conversational —
that is its purpose. A fully automated pipeline can drive every other mode
end-to-end with no human present.
All full modes write the same artifact (<model-file>, schema in
schema.md) so downstream consumers (pipeline recon/judge, verifier
agents) do not need to know which mode produced it. review and pr
produce reports, not models; review modifies the model only after the
user agrees. Every mode that writes or modifies the model file finishes
by running python3 <harness>/scripts/lint_threat_model.py on it until
Result: ALL PASSED.
| interview | bootstrap |
|---|
| Needs | An application owner present in the session | A local checkout; optionally past vulns |
| Method | Four-question framework: conversational walk through what are we working on → what can go wrong → what are we going to do about it → did we do a good job | Five stages: parallel research swarm → synthesize sections 1-3 + vuln table → generalize vulns into threat classes → STRIDE gap-fill → emit |
| Best for | New systems, design reviews, systems where the risk lives in business logic the code doesn't show | Inherited systems, third-party code, OSS dependencies, anything with a CVE history |
| Provenance tag | interview | bootstrap |
Context durability. Interview mode is multi-turn; tool results from early
reads may be evicted before you need them. To stay resilient:
- Do not read
interview.md or bootstrap.md in full up front. Read the
mode file (or the relevant section of it) at the point you need it, one
question or stage at a time.
- If a re-read via the Read tool is refused as "file unchanged", the prior
result was evicted; reload with
cat <path> via Bash instead.
Interview backbone (so you can proceed even if interview.md is
unavailable mid-session):
| Q | Question | Fills schema sections |
|---|
| Q1 | What are we working on? | section 1 context, section 2 assets, section 3 entry points |
| Q2 | What can go wrong? | section 4 threat rows (id, threat, actor, surface, asset) |
| Q3 | What are we going to do about it? | section 4 impact/likelihood/status/controls; section 5 deprioritized; section 8 recommended mitigations |
| Q4 | Did we do a good job? | validate ranking, coverage check, section 6 open questions |
bootstrap-then-interview mode
When the owner is available and the codebase is checked out, this is the
recommended path: the owner's time goes to refining a code-grounded draft
instead of describing the system from scratch.
- Tell the owner: "I'll read the code first and come back with a draft
(about 5-10 min), then we'll walk it together. Want that, or would you
rather start cold?" Only proceed if they opt in; otherwise fall back to
interview.md.
- Read
bootstrap.md and follow it end-to-end. Write
<target-dir>/<model-file>.
- Immediately continue into interview mode: read
interview.md and follow
it with --seed <target-dir>/<model-file> in effect. The section 6 open
questions from bootstrap become your Q1-Q4 prompts; the owner confirms,
corrects, and adds rather than starting from nothing.
- Overwrite
<target-dir>/<model-file> with the refined model. Set
provenance mode: bootstrap-then-interview.
The same flow is available manually: run bootstrap first, then
interview --seed <model-file> in a later session.
Step 2 — Shared output contract
All modes MUST emit <target-dir>/<model-file> conforming to schema.md
in this directory. Read schema.md immediately before you write the file,
not at routing time; in interview mode the gap between routing and emit can be
many turns, and an early read will be evicted before it's used.
After writing the file, run the deterministic gate and fix every ERROR
until it passes (same discipline as validate_report.py for audit/triage
artifacts):
python3 <harness>/scripts/lint_threat_model.py <target-dir>/<model-file>
Then print to the user:
- The path to the model file and the linter result line.
- The top 5 threats by likelihood × impact (id, one-line description, L×I).
- For
bootstrap: any open questions the code could not answer (these seed a
later interview pass).
- For
interview: any owner statements that could not be verified in code
(these seed follow-up code review).
References
- The harness's AGENTS.md "Security Testing Context"
section for the engagement-context and authorization framing this skill
inherits.
- Downstream consumers:
/triage (threat-model answers shape verifier
environment/threat context) and the secure-code-audit reports whose
findings feed threat likelihood as evidence.