sota-threat-modeling
State-of-the-art threat modeling for both designing new systems and auditing existing ones. Use when designing a feature, service, integration, or architecture that touches untrusted input, new trust boundaries, sensitive data, or third-party dependencies (BUILD mode), and when r
Install
npx skills add https://github.com/martinholovsky/SOTA-skills/tree/main/skills/sota-threat-modeling
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install martinholovsky-sota-skills@llmmart
git clone https://github.com/martinholovsky/SOTA-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole martinholovsky/sota-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
SOTA Threat Modeling
Purpose
Threat modeling answers Shostack's four questions with engineering rigor:
- What are we working on? (decompose: DFD, trust boundaries, assets, actors)
- What can go wrong? (enumerate: STRIDE/LINDDUN per element, catalogs, attack trees)
- What are we going to do about it? (treat: mitigate/accept/transfer/avoid, map to requirements and tests)
- Did we do a good job? (verify: abuse-case tests, residual risk review, re-model triggers)
This skill operationalizes those questions in two modes. Never produce a threat model that is only prose — every threat must land as a tracked requirement, a test, or an explicitly accepted risk with an owner.
BUILD Mode — Threat-Model-While-Designing
Run this workflow whenever designing anything that crosses a trust boundary. Scale effort to risk: a 15-minute "four questions" pass for a small feature; a full STRIDE-per-interaction model for a new service or auth flow.
Workflow
- Scope the delta. Model what is new or changed, not the whole system. List new entry points, new data classes, new dependencies, new actors.
- Draw the DFD as text/mermaid (see
rules/02). Mark trust boundaries explicitly. If you cannot draw a boundary, you do not understand the design yet — stop and ask. - Pick the methodology (see
rules/01): STRIDE-per-interaction by default; add LINDDUN if personal data flows; attack trees for a single high-value asset; four-questions-only for low-risk deltas. - Enumerate threats crossing each boundary using the per-component
catalogs in
rules/03. Write each threat as: actor → action → asset → impact. No vague entries ("hacking", "data breach"). - Rate and treat each threat (see
rules/04): likelihood × impact matrix, then accept / mitigate / transfer / avoid. Every mitigation becomes a security requirement with an ID. - Emit artifacts (see
rules/05): threat model doc, security requirements backlog entries, abuse cases as test stubs, and re-modeling triggers. - Wire into delivery. Reference requirement IDs in the design doc, tickets, and PR descriptions. A threat without a tracked artifact does not exist.
Continuous / incremental (agile, PR reviews)
- Threat model the story, not the sprint. Add a "Security notes" section to design docs and PR descriptions for any change matching a re-model trigger: new dependency, new endpoint/route/queue/cron/webhook, new trust boundary, new data class, auth/authz change, file/deserialization handling.
- In PR review, run a micro-STRIDE on the diff only: what new input enters? whose privilege executes it? what does it write or call? Takes 5 minutes; catches the majority of design-level regressions.
AUDIT Mode — Reconstructing a Threat Model from Code
Use when handed an existing system with no (trustworthy) threat model. Goal:
rebuild the implicit model from artifacts, then diff intended vs. actual
controls. Full procedure in rules/06.
Workflow
- Inventory entry points from code (see
rules/02§extraction): routes, queue consumers, cron jobs, webhooks, third-party callbacks, CLI/admin tools, file uploads, IaC-exposed ports. - Reconstruct the DFD from the inventory: processes, stores, external entities, flows; infer trust boundaries from network topology, authn checkpoints, and IAM policies.
- Identify assets and actors from schemas, secrets handling, and config.
- Run the catalogs (
rules/03) against each component; for every catalog item record: control present / absent / partial, with file:line evidence. - Gap analysis: rank absent/partial controls by exploitability × blast radius; distinguish "missing control" from "missing defense-in-depth".
- Report findings in the standard format below.
Severity conventions
| Severity | Definition |
|---|---|
| Critical | Remotely exploitable now, by an unauthenticated or low-priv actor, leading to full compromise of a key asset (RCE, auth bypass, mass data exfil). Fix before anything else ships. |
| High | Exploitable with realistic preconditions (one valid account, one misconfig, MitM position) compromising a key asset; or a Critical with a single weak mitigating layer. Fix this sprint. |
| Medium | Requires chaining, elevated access, or unusual conditions; or impacts a secondary asset; or defense-in-depth gap on a Critical path. Schedule. |
| Low | Hardening, hygiene, info disclosure of low-value data, theoretical with strong existing controls. Backlog. |
Severity = exploitability × impact in this deployment context — never copy
a CVE/CVSS base score without environmental adjustment (see rules/04).
Finding format (every finding, no exceptions)
[SEV] TITLE (component, STRIDE/LINDDUN class)
Location: path/to/file.py:123 (and IaC/config refs)
Threat: <actor> can <action> via <vector> because <missing/weak control>,
impacting <asset> (<C/I/A/privacy impact>).
Evidence: code excerpt or config line proving the gap.
Recommendation: specific control + where it goes; map to requirement ID.
Residual risk if accepted: one sentence.
Rules Index
| File | Read this when... |
|---|---|
rules/01-methodologies.md |
Choosing between STRIDE, LINDDUN, PASTA, attack trees, kill chains; deciding lightweight vs. heavyweight; setting up continuous/PR-level threat modeling. |
rules/02-decomposition.md |
Drawing DFDs in mermaid, defining trust boundaries, listing entry points/assets/actors/privilege levels; extracting all of these from an existing codebase. |
rules/03-threat-catalogs.md |
Enumerating threats for a specific component: web frontend, API, database, message queue, file storage, CI/CD, mobile, LLM agent/tool-use, cloud/IAM. |
rules/04-risk-rating-treatment.md |
Rating threats (DREAD pitfalls, CVSS usage, L×I matrices), choosing accept/mitigate/transfer/avoid, mapping mitigations to requirements and tests, documenting residual risk. |
rules/05-outputs-operationalization.md |
Writing the threat model document, building the security requirements backlog, turning abuse cases into tests, keeping the model alive (re-model triggers). |
rules/06-audit-reconstruction.md |
Auditing an existing system: reconstructing the model from code, control-presence matrix, gap analysis, severity calibration, reporting. |
Load only the files you need; rules/02 + rules/03 cover 80% of day-to-day work.
Top-10 Non-Negotiables
- No model without a diagram. Every threat model includes a DFD (mermaid or ASCII) with explicit trust boundaries. Prose-only models hide boundary confusion.
- Threats are sentences, not nouns. Actor → action → asset → impact. "SQL injection" is a vector; "anonymous user exfiltrates the orders table via unparameterized search query" is a threat.
- Every entry point gets enumerated — including queues, cron, webhooks, callbacks, admin tooling, and CI/CD. HTTP routes are never the whole attack surface.
- Trust boundary crossings drive enumeration. Apply STRIDE per interaction at each crossing; data inside one boundary at one privilege level rarely needs the full treatment.
- Personal data ⇒ LINDDUN pass. STRIDE does not cover linkability, identifiability, or non-compliance; run a privacy pass whenever PII flows or is stored.
- Rate with likelihood × impact in context. Never ship raw DREAD scores or unadjusted CVSS base scores as priorities.
- Every threat gets a disposition. Mitigate (→ requirement ID + test), accept (→ named owner + expiry date), transfer, or avoid. "Noted" is not a disposition.
- Mitigations become tests. Each mitigated threat yields at least one abuse-case test (unit, integration, or rule-based check) that fails if the control regresses.
- LLM/agent components are first-class attack surface. Model prompt injection, tool-call abuse, excessive agency, and data exfil via outputs for any system invoking an LLM with tools or retrieved content.
- Models expire. Define re-model triggers (new dependency, new trust boundary, new data class, auth change) in the document itself; an undated, trigger-less threat model is treated as absent in audits.
Files (sota-skills)
-
rules
-
01-methodologies.md 13.8 KB
# 01 — Methodology Selection & Continuous Threat Modeling Pick the cheapest methodology that surfaces the threats that matter for THIS change. Methodology is a lens, not a deliverable; the deliverable is a set of dispositioned threats (see `04`, `05`). ## 1. Decision table — which method, when | Situation | Method | Why | |---|---|---| | Default for any service/feature with a DFD | STRIDE-per-interaction | Best coverage-to-effort for technical threats at boundaries | | Quick element-level sweep, early sketch | STRIDE-per-element | Faster, coarser; fine when interactions aren't designed yet | | Personal data collected/stored/shared | LINDDUN (added to STRIDE, never instead) | STRIDE misses linkability, identifiability, non-compliance | | One crown-jewel asset (signing key, payment flow, model weights) | Attack tree | Forces enumeration of ALL paths to one goal, incl. non-technical | | Business-risk-driven, exec-facing, regulated org | PASTA (or PASTA-lite) | Ties threats to business impact; expensive — don't use for sprints | | Understanding a realistic adversary's end-to-end campaign | Kill chain / ATT&CK mapping | Validates detection & response coverage, not just prevention | | Small, low-risk delta; PR review; time-boxed | Shostack's four questions only | 15 minutes of structured thinking beats zero minutes of process | | Agentic / multi-agent AI system | STRIDE-per-interaction + agent catalog (`03` §8); CSA MAESTRO's layered analysis for complex multi-agent ecosystems | Agent threats (goal hijack, memory poisoning, collusion) span layers STRIDE alone doesn't name | Rationale: methods fail in predictable ways — STRIDE over-generates duplicates, LINDDUN is privacy-only, PASTA stalls in stage ceremony, attack trees go stale. Combine narrowly; never run all of them "to be thorough." ## 2. STRIDE — per-element vs. per-interaction ### The mnemonic, with the property each violates | Threat | Violated property | Canonical question | |---|---|---| | **S**poofing | Authentication | Can the caller lie about who it is? | | **T**ampering | Integrity | Can data/code be modified in flight or at rest? | | **R**epudiation | Non-repudiation | Can an actor deny an action because we can't prove it? | | **I**nformation disclosure | Confidentiality | Can data leak to someone unauthorized? | | **D**enial of service | Availability | Can the element be exhausted, crashed, or locked? | | **E**levation of privilege | Authorization | Can a low-priv actor do high-priv things? | ### Per-element (coarse) Apply the applicable subset to each DFD element type: | Element | S | T | R | I | D | E | |---|---|---|---|---|---|---| | External entity | x | | x | | | | | Process | x | x | x | x | x | x | | Data store | | x | x | x | x | | | Data flow | | x | | x | x | | Use when: the design is a sketch, you need breadth in 30 minutes. Cost: misses threats that only exist because of WHO talks to WHOM (e.g., a queue consumer trusting producer-supplied IDs). ### Per-interaction (default) For each data flow that **crosses a trust boundary**, ask all six STRIDE questions about the tuple *(source, flow, destination)*. Skip flows that stay inside one boundary at one privilege level unless they carry a crown-jewel asset. Rationale: ~90% of exploitable design flaws live at boundary crossings; per-interaction concentrates effort there and naturally produces *actor → action → asset* threat sentences. ### Worked mini-example ```mermaid flowchart LR U[Browser] -->|1 POST /orders JWT| A[API] A -->|2 SQL| D[(orders DB)] A -->|3 publish order.created| Q[[queue]] Q -->|4 consume| W[Fulfillment worker] subgraph TB1[Internet → DMZ boundary] U end subgraph TB2[Backend boundary] A; D; Q; W end ``` STRIDE-per-interaction on flow 1 (crosses TB1→TB2): - **S**: forged/alg-confused JWT → API acts as another user. Mitigation: verify alg allowlist + issuer + audience; requirement `SR-101`. - **T**: order body altered client-side (price field trusted). Mitigation: server-side price lookup; `SR-102`. - **R**: user disputes placing order. Mitigation: signed audit log incl. JWT sub + request hash; `SR-103`. - **I**: order IDs sequential → enumeration of others' orders via GET. Mitigation: authz check object-level + UUIDv7; `SR-104`. - **D**: unbounded order payload / no rate limit. Mitigation: body size cap + per-user rate limit; `SR-105`. - **E**: `role` claim taken from request body, not token. Mitigation: derive authz only from verified token/server state; `SR-106`. Flow 4 (inside TB2) still gets a pass because the worker acts at higher privilege than the producer's data deserves: poisoned `order.created` message with attacker-chosen `shipping_address` and `sku` → **T/E** on the worker. Lesson: a queue is a deferred entry point; producer authentication and message schema validation are boundary controls (see `03` §queue). ## 3. LINDDUN — privacy threats (run WITH STRIDE when PII flows) | Threat | Question to ask per flow/store holding personal data | |---|---| | **L**inking | Can two records/actions be tied to the same person across contexts? | | **I**dentifying | Can a "pseudonymous" subject be re-identified (quasi-identifiers, logs, analytics)? | | **N**on-repudiation | Can the subject NOT plausibly deny an action they should be able to deny? (inverse of STRIDE-R) | | **D**etecting | Can an outsider learn a person exists in the system (login timing, "email already registered")? | | **D**ata disclosure | Is more personal data collected/stored/shared than the purpose needs? | | **U**nawareness & unintervenability | Does the subject not know what's collected/shared — or have no way to access, correct, or erase it? | | **N**on-compliance | Does the flow violate stated policy / GDPR-class obligations (retention, purpose, residency)? | Use LINDDUN GO (card-style, per-flow prompts) for sprint-speed work; full LINDDUN per-element only for new products handling sensitive categories. Mini-example: the orders DFD above stores `shipping_address`. LINDDUN adds: **L** — address joins orders across guest checkouts into a profile; **D**etect — "email already used" on checkout leaks account existence; **N**on-compliance — no retention period on fulfilled orders. None of these fall out of STRIDE. ## 4. PASTA — when business context must drive the model Seven stages: (1) business objectives → (2) technical scope → (3) decomposition → (4) threat analysis (intel-driven) → (5) vulnerability analysis → (6) attack modeling/simulation → (7) risk & impact analysis. Use full PASTA only when the audience is risk owners/executives and the system is business-critical or regulated. Otherwise run **PASTA-lite**: write stages 1–2 as a half-page "business impact preamble" on top of a STRIDE model — it sharpens severity ratings (`04`) by naming what the business actually loses. Pitfall: PASTA used as ceremony produces 40-page documents nobody re-reads; cap the artifact. ## 5. Attack trees Root = attacker goal (a state, not an action): "Read another tenant's documents." Children = OR/AND-decomposed subgoals down to concrete attack steps. Annotate leaves with cost/skill/detectability; prune branches whose cheapest path is dominated by another branch. ``` GOAL: read another tenant's documents ├─ OR exploit the app │ ├─ IDOR on GET /docs/{id} (no tenant check) [cheap → mitigate first] │ └─ SQLi in search → cross-tenant SELECT ├─ OR exploit the platform │ ├─ AND: SSRF → cloud metadata creds → S3 GetObject on shared bucket │ └─ misconfigured bucket policy (public-read) └─ OR exploit people/process └─ phish support agent → admin "impersonate" feature ``` Use when: one asset dominates the risk profile, or to communicate "why this mitigation" to non-modelers. AND-nodes show where a single control kills a whole branch — invest there. Keep trees ≤3 levels; deeper trees rot. ## 6. Kill chains / ATT&CK Map a realistic campaign across recon → initial access → execution → persistence → privilege escalation → lateral movement → exfiltration. Use to answer "would we DETECT and STOP this, and at which stage?" — i.e., to threat model detection/response, logging coverage, and segmentation, which STRIDE-per-interaction does not test. For each kill-chain stage, name the telemetry that would fire; a stage with no telemetry is a logging requirement (feeds `05` backlog). Don't use kill chains to find design flaws — that's STRIDE's job. ## 7. Lightweight: the four questions beat heavyweight process when... - The delta is small and inside existing boundaries (new field, new report). - The team will genuinely do a 15-minute pass but would skip a 2-hour workshop. - You are in a PR review (see §8). A four-questions pass still produces artifacts: 3–10 threat sentences, each with a disposition. If the pass surfaces a new trust boundary, new data class, or new dependency, **escalate** to a full STRIDE model — that's the upgrade trigger, codify it in the team's definition of ready. ### Worked four-questions pass (feature: "export my data as CSV") 1. *What are we working on?* New `GET /export` endpoint → async job → CSV in the uploads bucket → emailed signed link. New flow crosses: user→API, job→bucket, email→user. 2. *What can go wrong?* - T1: user A triggers export, job exports ALL users' rows (missing tenant scope in the bulk query — bulk paths often bypass per-row authz). - T2: signed link forwarded/leaked from email → anyone reads the CSV (email is an untrusted store; long-expiry link = standing credential). - T3: CSV formula injection — `=HYPERLINK(...)` in user-controlled fields executes in victims' spreadsheets (your file, their machine). - T4: unbounded concurrent exports exhaust DB/worker (D). 3. *What are we going to do?* T1 → reuse tenant-scoped repo + abuse test (mitigate); T2 → 15-min expiry + require login to download (mitigate); T3 → prefix `'` on `=+-@` cells (mitigate); T4 → 1 concurrent export/user (mitigate). No accepts needed. 4. *Did we do a good job?* Four threats, four requirements, three tests; re-check at next data-class change. Elapsed: ~15 minutes. T3 is the kind of threat a checklist-free brainstorm misses — which is why even the lightweight pass consults the relevant catalog (`03` §5 file pipelines). ### Enumeration aids (any methodology) - **Elevation-of-Privilege / cornucopia card decks:** use for team workshops to break "blank page" paralysis; cards are prompts, the output is still threat sentences in the table. - **Organizational threat library:** harvest recurring threats + standard mitigations from past models into a reusable library keyed by component type; new models import and check applicability rather than re-deriving. This is the highest-leverage maturity step after triggers (`05` §4). - **LLM-assisted enumeration:** useful as a breadth generator against your DFD; treat output as candidate threats requiring human reachability triage — never paste raw generation into the model (violates the triage- capacity rule below). ## 8. Continuous / incremental threat modeling (agile + PRs) Rules: 1. **Baseline once, then delta forever.** Maintain one living baseline model per service (`05` §living). Sprint work models only the diff against it. 2. **Re-model triggers gate stories.** A story matching any trigger — new dependency, new entry point (route/queue/cron/webhook/callback), new trust boundary, new data class, authn/authz change, crypto change, file or deserialization handling — requires a threat-model note before merge. 3. **PR micro-STRIDE (5 minutes, on the diff only):** - What NEW input enters, and from whom? (S, T) - At whose privilege does the new code run, and what can it now reach? (E) - What does it write, emit, or call — including logs and third parties? (I, R) - What happens at 1000× volume or with a 100 MB payload? (D) Record the answers in the PR description under `## Security notes`; "no new input, no privilege change, no new sink" is a valid and useful answer. 4. **Timebox enumeration, not treatment.** Cut off brainstorming at the timebox; never cut off writing dispositions for what you found. 5. **One owner per model.** The service's tech lead owns baseline freshness; audits (`06`) treat a stale baseline (no update across a trigger-matching change) as a finding. Anti-patterns: threat modeling only at "security review" milestones (too late, batch too big); tool-generated 200-threat reports pasted into tickets (nobody triages them — generate ≤ the team's triage capacity); modeling the whole system every sprint (re-derives known results, exhausts goodwill). ## Audit checklist - [ ] A documented methodology choice exists and matches the system's risk profile (not "we ran a tool"). - [ ] STRIDE applied per-interaction at every trust boundary crossing; flows skipped only when source and destination share boundary AND privilege. - [ ] LINDDUN (or equivalent privacy pass) performed wherever personal data is collected, stored, or shared; linkability, detectability, and intervenability (subject can access/correct/erase) considered. - [ ] Crown-jewel assets have an attack tree or equivalent all-paths analysis. - [ ] Detection/response coverage assessed (kill-chain or ATT&CK mapping) for at least the top abuse scenario; logging gaps captured as requirements. - [ ] Every threat is an actor→action→asset→impact sentence, not a category. - [ ] Re-model triggers are defined and observably enforced (PRs matching triggers contain security notes; baseline model updated after them). - [ ] Enumeration volume matches triage capacity — no untriaged threat dumps. - [ ] Lightweight passes escalate to full models when triggers fire (evidence: at least one escalation in history, or no trigger-matching changes). -
02-decomposition.md 12.3 KB
# 02 — System Decomposition: DFDs, Trust Boundaries, and Extraction from Code Decomposition quality caps threat-model quality: you cannot enumerate threats against elements you never drew. This file covers (A) building the model and (B) extracting it from an existing codebase. ## A. Building the model ### 1. DFD element vocabulary Use exactly four element types plus boundaries — more notation adds cost, not threats: | Element | Mermaid convention | Examples | |---|---|---| | External entity | `E[Name]` rectangle | user browser, third-party API, attacker, payment provider | | Process | `P(Name)` rounded | API service, worker, lambda, cron job, LLM agent | | Data store | `D[(Name)]` cylinder | Postgres, S3 bucket, Redis, browser localStorage, log sink | | Data flow | labeled arrow | `-->|order JSON / HTTPS|` — label with DATA + PROTOCOL | | Trust boundary | `subgraph TB_x[...]` | network segment, process/user-priv change, org boundary | Rules: - **Label every flow with what data moves and over what channel.** Unlabeled arrows hide information-disclosure and tampering threats. - **Logs, metrics, and backups are data stores.** They hold copies of your assets at usually-weaker protection; draw them or you will miss the most common disclosure path. - **The attacker is not drawn; attackers are positions.** Any external entity, any compromised element, any element across a boundary is a potential attacker position. - **One diagram per level.** L0 context diagram (system + externals), L1 per service. Stop decomposing when every flow within an element is same-trust; decompose further when a single box contains a privilege change. ### 2. Trust boundaries — the load-bearing concept A trust boundary is any line across which the level of trust in data or identity changes. Draw one wherever ANY of these change: 1. **Network zone** — internet → DMZ → internal → restricted. 2. **Identity/authn** — anonymous → authenticated → admin; service A → service B (even "internal" services authenticate to each other or share a boundary). 3. **Process privilege** — user-mode app → root daemon; container → host. 4. **Organization/control** — your code → third-party SaaS, package registry, customer-supplied plugin, model provider. 5. **Data validation state** — raw upload → parsed/validated object. (Parsers ARE boundary controls; file parsing is a boundary crossing.) 6. **Time** — producer → queue → consumer; data written now, executed later (stored XSS, poisoned cache, scheduled job args). Deferred trust is still crossing trust. Heuristic: if compromising X gives the attacker nothing they didn't already have at Y, then X and Y share a boundary. If you "aren't sure" whether two elements share a boundary, they don't — model the crossing. ### 3. Entry points, assets, actors, privilege levels Produce these four tables for every model; they are the enumeration substrate. **Entry points** — every place external data or control enters: | ID | Entry point | Channel | Authn required | Reaches | |---|---|---|---|---| | EP1 | `POST /api/orders` | HTTPS | JWT (user) | API → DB, queue | | EP2 | `order.created` consumer | AMQP | broker creds (any internal producer) | worker → shipping API | | EP3 | Stripe webhook `/hooks/stripe` | HTTPS | signature header | API → DB | | EP4 | nightly `reconcile` cron | scheduler | none (implicit) | DB read/write | **Assets** — what an attacker wants; rank them: | ID | Asset | Class | Worst-case impact | |---|---|---|---| | A1 | customer PII (orders, addresses) | personal data | regulatory + churn | | A2 | Stripe API key | credential | financial fraud | | A3 | order integrity (prices, states) | business data | direct loss | | A4 | service availability | availability | SLA breach | Include **abstract assets**: reputation, compute (cryptomining), your users' trust in messages you send (phishing-from-you), and your system as a pivot into others. **Actors and privilege levels:** | Actor | Privilege | Notes | |---|---|---| | anonymous internet | none | reaches EP1 pre-auth surface, EP3 | | customer | own-tenant read/write | the IDOR baseline | | support agent | cross-tenant read, impersonate | high-value phish target | | CI pipeline | deploy + secrets | machine actor, often over-privileged | | `api` service account | DB rw, queue publish | blast radius if API popped | Always include **machine actors** (service accounts, CI, cron) — they hold the broadest standing privilege in most real systems — and at least one **insider** actor. ### 4. Worked mini-example — 10-line DFD with threats ```mermaid flowchart LR B[Browser] -->|creds, then session / HTTPS| W(Web API) S[Stripe] -->|webhook: payment event / HTTPS+sig| W W -->|SQL: orders, users| DB[(Postgres)] W -->|order.created JSON| Q[[RabbitMQ]] Q -->|consume| F(Fulfillment worker) F -->|label request / HTTPS| C[Carrier API] W -->|structured logs| L[(Log store)] subgraph TB_internet[internet]; B; S; end subgraph TB_backend[VPC]; W; DB; Q; F; L; end subgraph TB_thirdparty[third party]; C; end ``` Boundary crossings → headline threats (full enumeration via `03` catalogs): - B→W: credential stuffing (S), session fixation (S), tampered order body (T). - S→W: forged webhook if signature unchecked or timestamp not validated — replay marks orders paid (S/T). - W→L: PII/credentials in logs — disclosure to anyone with log access (I); log store is a data store, modeled as such. - Q→F: time-shifted boundary — poisoned message executes later at worker privilege (T/E); broker creds shared by all internal producers = weak authn. - F→C: carrier creds in worker env (I); carrier outage stalls queue (D); response from C is untrusted input INTO F (T) — third-party responses cross a boundary inward too. ## B. Extracting the model from an existing codebase (AUDIT mode) Reconstruct entry points, flows, stores, and boundaries from artifacts in this order — code lies less than docs. ### 5. Entry-point extraction by mechanism Search patterns (adapt to stack; run broad, then verify by reading): | Mechanism | What to grep / read | |---|---| | HTTP routes | framework registrations: `@app.route|@Get\(|router\.(get|post)|http.HandleFunc|urlpatterns|#\[get\(`; OpenAPI specs; ingress/ALB rules in IaC | | gRPC | `.proto` files, `RegisterService`, server interceptors (authn lives here) | | Queue/stream consumers | `@KafkaListener|consumer|subscribe|sqs.receive|channel.basic_consume|pubsub.*subscription`; broker IaC for topic ACLs | | Cron/scheduled | `crontab`, k8s `CronJob`, `@Scheduled|celery beat|cloudwatch event|cloud scheduler`; note: cron handlers often skip authn entirely | | Webhooks (inbound) | routes named `hook|callback|notify|ipn`; verify presence of signature validation NEXT TO the route | | Third-party callbacks | OAuth `redirect_uri` handlers, payment IPN, SSO ACS endpoints — high-trust by design, check state/nonce/signature | | File ingestion | upload routes, `multipart`, S3 event triggers, watched directories, email-attachment pipelines | | CLI/admin | `argparse|cobra|click` entry points, `/admin` routes, debug endpoints (`/actuator|/debug/pprof|graphql introspection`) | | Outbound that returns | every HTTP client call site is an entry point for the RESPONSE (deserialization, SSRF-redirects) | For each hit record: path:line, authn mechanism (or none), input schema, downstream reach. **An entry point with no findable authn check is a finding, not a TODO.** Worked extraction excerpt (Flask + Terraform monorepo): ``` $ grep -rn "@app.route\|@bp.route" services/ | wc -l → 38 routes $ grep -rn "@require_auth\|@login_required" services/ | wc -l → 31 decorators → 7 routes to read by hand. Verdict: 4 are health/static; 2 are the OAuth callback + Stripe webhook (sig-checked, OK); 1 is services/admin/jobs.py:14 POST /internal/requeue — NO AUTH. Finding. $ grep -rn "basic_consume\|@celery" services/ → 5 consumers $ grep -rn "aws_cloudwatch_event\|CronJob" infra/ → 2 schedules $ grep -rn "0.0.0.0/0" infra/*.tf infra/sg.tf:41 ingress 5432 ← the DB is internet-reachable. Finding. ``` The arithmetic pattern (routes minus auth decorators, then read the remainder) scales to any framework and is reproducible evidence for the audit trail (`06` §2). ### 6. Stores, flows, and assets from code - **Stores:** DB connection strings/ORM configs, S3/GCS client instantiations, Redis/memcached, message broker (it stores messages), log/metric sinks, `localStorage|document.cookie` in frontend code, mobile keychain/shared-prefs. - **Data classes/assets:** ORM models and migrations (column names: `email`, `ssn`, `dob`, `address`, `token`), secrets managers usage, `.env.example`, IaC secret resources. Sequential integer PKs on user-owned resources → enumeration risk, note it. - **Flows:** trace from each entry point to stores/outbound calls. For large codebases, trace only flows from entry points that touch top-3 assets — depth over breadth. ### 7. Inferring trust boundaries from an existing system - **Network:** VPC/subnet/security-group/k8s NetworkPolicy in IaC; ingress vs. ClusterIP services; "0.0.0.0/0" rules mark the internet boundary precisely. - **Identity:** where authn middleware is mounted (and which routes are EXCLUDED — exclusion lists are boundary holes); service-to-service auth (mTLS, IAM, shared static token = weak boundary); IAM policies map machine actors to reach. - **Privilege:** Dockerfile `USER`, k8s `securityContext`, sudoers, DB users per service (one shared `app` superuser = no internal boundaries in the data tier). - **Org:** every third-party SDK/API in lockfiles + outbound allowlists; each is an org boundary with its own catalog pass (`03`). Then DIFF inferred boundaries against any documented architecture: each mismatch (documented boundary absent in code, or real flow absent from docs) is itself a finding — drift is how systems rot. ### 8. Privilege-reach map (what a compromise buys) For each process and machine actor, answer: "if THIS is compromised, what can the attacker now reach?" One table, built from creds in env/IAM/DB grants: | If compromised | Reaches directly | Notable NOT-reach | |---|---|---| | Web API pod | orders DB rw, queue publish, Stripe key | carrier creds, CI | | Fulfillment worker | queue consume, carrier API, orders DB **rw** | Stripe key | | CI runner | deploy creds, all repo secrets, prod kubeconfig | — | This table does three jobs: exposes over-privilege at a glance (why does the worker have DB **write**? why does CI reach prod directly?), provides the impact half of every rating in `04`, and identifies where one segmentation control collapses multiple attack-tree branches. In audit mode it is derived purely from IAM policies, DB grants, and network policy — no interviews needed, and it rarely matches what the team believes. ### 9. Decomposition outputs A decomposition is done when you have: L0+L1 mermaid DFDs with boundaries; the four tables (entry points, assets, actors/privileges, stores) with code refs in audit mode; and a stated scope ("modeled X; excluded Y because Z"). Excluded scope must be written down — silent exclusions are where breaches live. ## Audit checklist - [ ] DFD exists, uses the 4-element vocabulary, every flow labeled with data + channel; logs/backups/caches drawn as stores. - [ ] Trust boundaries explicit and justified by network/identity/privilege/ org/validation/time changes — not just "internal vs external". - [ ] Entry-point table covers HTTP, gRPC, queues, cron, webhooks, callbacks, file ingestion, admin/CLI, and third-party responses; each row has authn mechanism and downstream reach. - [ ] Every entry point in code appears in the table (grep sweep performed); authn-less entry points flagged. - [ ] Asset table ranks concrete and abstract assets; data classes derived from schemas, not memory. - [ ] Actor table includes machine actors (CI, service accounts, cron) and an insider; privilege per actor stated. - [ ] Boundaries inferred from IaC/middleware/IAM match documented architecture; drift recorded as findings. - [ ] Time-shifted crossings (queues, caches, stored content, scheduled jobs) modeled as boundary crossings. - [ ] Privilege-reach map built from actual grants (IAM/DB/network), and over-privilege deltas vs. need recorded. - [ ] Scope exclusions written down with reasons. -
03-threat-catalogs.md 18.3 KB
# 03 — Per-Component Threat Catalogs Use these catalogs AFTER decomposition (`02`): for each component on the DFD, walk its catalog and emit threat sentences (*actor → action → asset → impact*) for every applicable item. In AUDIT mode, additionally record control present/partial/absent with file:line evidence (`06`). Catalogs are floors, not ceilings — they encode the threats that recur in practice; boundary-specific reasoning (`01` §2) finds the rest. STRIDE/LINDDUN letters annotate each item for classification. ## 1. Web frontend (browser-executed code) - **XSS in all variants (T/I/E):** reflected, stored, DOM-based; framework escape hatches (`dangerouslySetInnerHTML`, `v-html`, `innerHTML`, `bypassSecurityTrust*`). Stored XSS is a time-shifted boundary crossing — attacker content executes later in victims' sessions. - **CSP absent or neutered (defense-in-depth for XSS):** `unsafe-inline`, `unsafe-eval`, wildcard sources defeat the point. Audit the header, not the intent. - **Token storage (I/S):** long-lived tokens in `localStorage` are exfiltrable by any XSS; prefer httpOnly+SameSite cookies or in-memory + refresh. - **CSRF (S/T):** state-changing requests authenticated by cookies need SameSite + anti-CSRF token; JSON-only APIs still need it if cookies auth and content-type isn't enforced server-side. - **Client-side authz as the ONLY authz (E):** hidden admin menu ≠ access control; every privileged decision re-checked server-side. - **Secrets shipped in bundles (I):** API keys, internal URLs, feature-flag payloads — assume the bundle is public; grep build artifacts for entropy. - **Supply chain (T):** npm dependencies execute in your security context; third-party `<script>` tags execute in your origin; subresource integrity for CDN assets; lockfile + provenance for packages. - **Open redirects (S):** `?next=` params feeding `location` enable phishing and OAuth token theft; allowlist destinations. - **postMessage / iframe (S/T/I):** missing origin checks on `message` listeners; embedding untrusted iframes without `sandbox`; clickjacking — `frame-ancestors`. - **Sensitive data residue (I/L):** PII in URL params (logged everywhere), browser cache/history, analytics events, session-replay tools (LINDDUN: L, Dd). ## 2. API / backend service - **Broken object-level authz / IDOR (E/I):** THE top API threat. Every handler touching a resource by ID must verify the caller's right to THAT object, not just a valid session. Audit: pick 5 by-ID endpoints, find the tenant/owner check. - **Broken function-level authz (E):** admin routes guarded only by URL obscurity or frontend; middleware exclusion lists. - **Mass assignment / over-binding (T/E):** request body bound straight to model (`role`, `is_admin`, `price` settable). Use explicit allowlists/DTOs. - **Injection (T/I/E):** SQL (string-built queries), NoSQL (`$where`, operator injection), command (`exec` with user input), template (SSTI), LDAP, header/ log injection (CRLF). Parameterize; treat logs as an injection sink. - **SSRF (S/I/E):** any user-influenced URL fetched server-side (webhook test buttons, importers, PDF renderers, image proxies) reaches internal services and cloud metadata (§9). Allowlist scheme+host+port; block link-local; re- resolve after redirects (DNS rebinding). - **Authn weaknesses (S):** JWT alg confusion / `none`, unvalidated `aud`/ `iss`, no expiry check, symmetric key reuse across services; password reset token predictability; missing brute-force lockout/rate limit; session fixation. - **Unsafe deserialization (T/E):** pickle/Java serialization/`yaml.load` on external data = RCE primitive. Treat serialized blobs as code. - **Excessive data exposure (I):** returning full ORM objects and filtering client-side; verbose errors with stack traces/SQL; GraphQL introspection + unbounded query depth/aliases (also D). - **Rate limiting & resource caps (D):** per-principal limits on authn endpoints, expensive queries, pagination (`?limit=10000000`), regex on user input (ReDoS), zip/XML expansion (bombs, XXE — also I). - **Repudiation (R):** security-relevant actions (login, authz failure, privilege change, data export) logged with actor + object + outcome, to a store app credentials can't rewrite. - **Internal trust (S/E):** "internal" services accepting unauthenticated calls — anyone with SSRF or a foothold is "internal". ## 3. Database / data tier - **Shared superuser account (E):** every service connecting as one privileged user = no internal boundaries; per-service users, least privilege, no DDL at runtime. - **Network exposure (S/I):** DB reachable beyond its app subnet; public IPs on managed DBs; no TLS on connections. - **Encryption (I):** at-rest (and: who holds the key — disk encryption doesn't stop SQL-level theft); field-level for crown jewels (tokens, SSNs) so a SQLi/backup leak yields ciphertext. - **Backups & replicas (I):** backups inherit none of prod's access controls by default — encrypted? access-logged? tested restore? Read replicas with weaker creds; snapshots shared cross-account. - **Row-level isolation (E/I):** multi-tenant tables relying solely on app WHERE clauses; consider RLS as a second layer for high-value tenancy. - **Audit & repudiation (R):** DDL/DCL and bulk-read logging; who watches `SELECT *` of the users table? - **Retention & privacy (LINDDUN Dd/Nc):** retention enforced (TTL/jobs), data deletion actually deletes (incl. backups policy), purpose-bound copies (no prod PII in staging/analytics without masking). - **ORM truths (T):** raw query escape hatches (`.raw(`, `text(`, `query_string`) carry injection risk audits often skip. ## 4. Message queue / event stream A queue is a **time-shifted entry point**: consumers execute attacker-era data in a later, often more privileged context. - **Producer authn/authz (S):** can any internal workload publish to any topic? Shared broker creds = any compromised pod forges any event. Per- service credentials + topic ACLs. - **Message integrity & schema (T):** consumers validate schema + business invariants (price ≥ 0, state transitions legal); never trust producer-supplied identity fields — derive actor from broker auth or signed envelope. - **Confused-deputy consumers (E):** consumer acts at high privilege on low-trust data ("delete-user" event with attacker-chosen ID). Re-check authorization at consumption time, not just production time. - **Poison messages & retries (D):** malformed message crash-looping a consumer stalls the partition; DLQ + max retries; idempotency keys (replay = duplicate side effects — payments!). - **Disclosure (I):** queues persist PII/secrets in payloads; broker at-rest encryption, payload minimization (send IDs not blobs), DLQ contents are an unmonitored data store of your worst messages. - **Ordering/race abuse (T):** logic depending on event order an attacker can influence (cancel-after-ship races, double-spend via concurrent consumers). - **Repudiation (R):** event provenance — which principal produced this message, traceable end to end? ## 5. File storage / upload pipelines - **Content-type laundering (T/E):** trust magic bytes + re-encode, never the client `Content-Type` or extension; SVG is XSS, HTML upload + same-origin serving = stored XSS; polyglot files. - **Parser attack surface (E/D):** image/PDF/office parsers are RCE farms — sandbox/isolate parsing workers (separate service, no creds, egress-deny); decompression bombs; XML in office docs → XXE. - **Path traversal (T/I):** user-influenced filenames/keys (`../`, absolute paths, unicode normalization); generate server-side names, map via DB. - **Serving (I/E):** serve user content from a separate origin/domain (cookie isolation); signed URLs with short expiry; no public-listable buckets (§9); `Content-Disposition` + `X-Content-Type-Options: nosniff`. - **Authorization (E/I):** signed-URL leakage via referer/logs; per-object authz on download endpoints (file IDOR is as common as API IDOR). - **Malware relay (abstract asset):** your storage distributing malware to other users — scanning or risk-acceptance, stated either way. - **Quota (D):** per-user size/count caps; multipart abandonment cleanup. ## 6. CI/CD & build pipeline The pipeline holds deploy creds and shapes every artifact — it is usually the highest-privilege, least-modeled component. - **Poisoned pipeline execution (T/E):** PRs from forks executing privileged workflows; `pull_request_target` + checkout-of-PR-head; injectable expressions (`${{ github.event.issue.title }}` in `run:`); pipeline config editable by the same PR it gates. - **Secrets exposure (I):** long-lived cloud keys in CI secrets vs. OIDC federation; secrets readable by all repo collaborators; secret-echo via `set -x`/debug logs; cache poisoning across trust levels (PR cache reused by main builds). - **Dependency/supply chain (T):** lockfiles + integrity hashes enforced in CI; dependency confusion (internal package names registered publicly, registry order); install scripts execute at build privilege; typosquats; base-image provenance and pinning by digest. - **Artifact integrity (T):** signed artifacts/provenance (SLSA-style) so prod runs what CI built; deploy step verifies signature; who can push to the registry directly, bypassing CI? - **Runner trust (E):** self-hosted runners on shared infra = lateral movement; ephemeral runners; runner reach into prod networks. - **Branch protection as a security control (T/R):** force-push/admin-bypass on release branches; required reviews on workflow files specifically; audit trail of who approved what. - **Environment promotion (E):** can staging creds deploy to prod? Separate identities per environment. ## 7. Mobile clients - **The app binary is public (I):** secrets/API keys in the app are extracted — assume so; per-user tokens only; server-side checks for everything (client is UI, never enforcement). - **Local storage (I):** tokens/PII in Keychain/Keystore, not plist/ shared-prefs/SQLite plaintext; OS backups capture app data — mark exclusions. - **Transport (S/I):** TLS everywhere + pinning for high-value apps (with a rotation story); user-installed CA threat for sensitive verticals. - **Deep links / app links (S/T):** unvalidated deep-link params hitting authn actions; link hijacking (claim verified app links); OAuth redirect via custom scheme is interceptable — use PKCE always. - **IPC surface (E):** exported Android components (activities/receivers/ providers) — audit the manifest; iOS URL schemes and extensions. - **WebView (T/E):** JS bridges (`addJavascriptInterface`) exposing native functions to loaded content; loading remote content in privileged WebViews. - **Reverse engineering & tamper (T):** root/jailbreak + repackaging for client-trusting apps (games, payments); attestation (Play Integrity/App Attest) as mitigation where the business case warrants. - **Privacy (LINDDUN):** device identifiers enabling cross-app linking (L/I); analytics SDKs shipping PII (Dd/U); permissions minimal (U). ## 8. LLM agent / tool-use systems Model the LLM as a **confused-deputy-prone process whose control plane and data plane are the same channel**. Every token the model reads is potentially instructions. Companion catalogs: OWASP LLM Top 10 and the OWASP Top 10 for Agentic Applications 2026 (ASI01–ASI10: goal hijack, tool misuse, identity/ privilege abuse, agentic supply chain, unexpected code execution, memory poisoning, inter-agent comms, cascading failures, human-trust exploitation, rogue agents) — the items below cover them; use ASI numbering when reporting. - **Direct prompt injection (S/T):** user input overriding system intent. System prompts are not a security boundary — assume full disclosure of the prompt (I) and design so that prompt knowledge gains nothing. - **Indirect prompt injection (S/T/E) — the defining threat:** instructions embedded in retrieved web pages, RAG documents, emails, tickets, tool outputs, repo files. Any agent that (a) reads attacker-influenceable content AND (b) has tools with side effects is exploitable by default. Enumerate every content source on the DFD as a hostile entry point. - **Excessive agency (E):** tool scope beyond the task (agent with `send_email` + `read_all_docs` = exfiltration machine). Least-privilege tools: per-task allowlists, read-only by default, scoped credentials per invocation, human confirmation gates on irreversible/external actions (payments, deletes, sends, code execution). - **Exfiltration via outputs (I):** injected instructions encode secrets into markdown image URLs, links, or tool parameters. Mitigate: egress allowlists, render-time URL sanitization, no auto-fetch of model-emitted URLs. - **Tool-call injection (T/E):** model-generated arguments flowing into SQL/shell/URLs — tool implementations must validate args exactly like an internet-facing API (the model is an untrusted caller). - **Cross-tenant leakage in RAG (I/E):** retrieval must enforce the CALLER's ACLs at query time — embeddings stores rarely carry authz natively. - **Multi-agent / MCP trust (S/T):** third-party tool servers and agent cards are org-boundary crossings; tool descriptions themselves can carry injections; pin/verify tool definitions. - **Named MCP attack classes (T/S/E)** — enumerate per attached server; use these names in findings (OWASP MCP Top 10 MCP03:2025; MITRE ATLAS AML.T0104); mitigations detailed in sota-code-security rules/08 §5: - *Tool poisoning:* hidden instructions in tool descriptions/metadata → violates the instruction/data boundary → pin + human-review full definitions at install. - *Rug pull:* tool definition/behavior changes after approval → violates the approval's integrity over time → hash-pin definitions, re-approve on change. - *Tool shadowing:* one server's descriptions steer use of ANOTHER server's tools → violates inter-server isolation → separate sessions/agents for high-privilege tools. - *Line jumping:* injection at `tools/list`, before any call → bypasses invocation-time gates → treat listings as untrusted; review before connecting. - *Preference manipulation (MPMA):* manipulative descriptions bias tool selection toward attacker servers → violates selection integrity → allowlisted servers + description review. - **Reasoning-model attacks (S/D):** CoT hijacking / H-CoT — untrusted text posing as the model's own reasoning steers safety/tool decisions (S); OverThink-class decoys in retrieved content force excessive reasoning tokens — cost/latency exhaustion (D). Cap reasoning budgets; keep untrusted content out of reasoning scaffolds (sota-code-security rules/08 §5). - **Memory/state poisoning (T):** persisted conversation memory or scratchpads let an injection survive across sessions and users. - **DoS / cost (D):** unbounded agent loops, token-expensive inputs, recursive tool calls — cap steps, tokens, spend per request. - **Privacy (LINDDUN):** prompts/completions logged with PII (Dd); user data in fine-tuning/eval sets (Nc); provider data-retention terms (org boundary). - **Mitigation pattern:** dual-LLM / plan-then-execute (planner sees untrusted content, executor with tools sees only structured plan), or taint-tracking: once untrusted content enters context, downgrade available tools for the rest of the session. ## 9. Cloud-specific threats (cross-cutting) - **IAM misconfig (E):** wildcard actions/resources (`"Action":"*"`); privilege-escalation primitives (`iam:PassRole` + `*:Create*`, `iam:PutUserPolicy`, `sts:AssumeRole` trust policies open to broad principals); unused-but-live credentials; no permission boundaries on CI identities. Audit IaC, not the console. - **Metadata service / credential theft (I/E):** SSRF → `169.254.169.254` → role creds (the canonical cloud kill chain). Enforce IMDSv2/hop-limit=1 (or GCP metadata header), block link-local egress from app containers, minimal instance roles. - **Public buckets/storage (I):** bucket policy + ACL + account-level public-access-block all checked; "authenticated users" ≠ "my users"; listable buckets enumerate keys; same for public snapshots/AMIs/container registries. - **Cross-account trust (S/E):** resource policies (S3, KMS, SNS, Lambda) granting external accounts; confused deputy without `ExternalId`/source-arn conditions. - **Secrets sprawl (I):** secrets in env vars visible to whole task/pod, in IaC state files (state bucket protection!), in container layers, in Lambda env config readable by `lambda:GetFunction`. - **Logging/detection (R):** control-plane audit logs (CloudTrail-class) on, immutable, alarmed for IAM changes and impossible-travel; flow logs at boundaries you claimed in the model. - **Serverless/managed (E/D):** function resource policies (who can invoke), event-source injection (S3 key names, SNS payloads as untrusted input), concurrency limits as DoS containment. ## Audit checklist - [ ] Every DFD component mapped to its catalog; each item dispositioned (present/partial/absent/N-A) — no silent skips. - [ ] Frontend: XSS sinks audited, CSP effective, token storage justified, CSRF covered, no secrets in bundles. - [ ] API: object-level authz verified on sampled by-ID endpoints; mass assignment, SSRF, deserialization, and rate limits checked with code evidence. - [ ] Database: per-service least-priv users, encryption + key custody, backup/replica protections, retention enforcement. - [ ] Queues: producer authn, schema validation at consumers, authz re-check at consumption, idempotency, DLQ handling. - [ ] File pipeline: magic-byte validation, parser isolation, traversal-proof naming, separate serving origin, download authz. - [ ] CI/CD: fork-PR privilege, OIDC over static keys, lockfile enforcement, artifact signing, workflow-file review protection, runner isolation. - [ ] Mobile: no embedded shared secrets, Keychain/Keystore usage, PKCE, exported-component audit, WebView bridge audit. - [ ] LLM/agent: every content source treated as hostile, tool least- privilege + confirmation gates, output URL handling, RAG ACL enforcement, step/spend caps. - [ ] Cloud: IAM wildcards and escalation primitives, IMDSv2/metadata protection, public-access blocks, cross-account conditions, audit-log immutability. -
04-risk-rating-treatment.md 12.3 KB
# 04 — Risk Rating & Treatment Rating exists to ORDER work and justify dispositions — not to produce precise-looking numbers. Prefer a coarse, calibrated, contextual scale applied consistently over a fine-grained scale applied inconsistently. ## 1. DREAD — know it, and know why not to trust it DREAD scores Damage, Reproducibility, Exploitability, Affected users, Discoverability (1–10 each, averaged). Pitfalls — each one disqualifying for serious use: 1. **Unanchored scales.** "Damage = 7" means different things to different raters; scores drift by who's in the room. (Microsoft itself dropped DREAD for this.) 2. **Averaging hides extremes.** Damage 10 / everything-else 2 averages to a "low" — but a hard-to-find RCE is still an RCE. Worst-dimension logic beats means. 3. **Discoverability rewards obscurity.** Rating threats lower because "nobody will find it" institutionalizes security-by-obscurity; attackers read code, run scanners, and get lucky. If you keep D, score it 10 always — at which point drop it. 4. **Double counting.** Reproducibility, Exploitability, Discoverability are three blurry views of likelihood; one inflated dimension skews the total. If an org mandates DREAD: anchor every level with written exemplars, replace the mean with max-of(Damage, weakest-link likelihood), and fix D=10. At that point you have rebuilt likelihood × impact — use that directly (§3). ## 2. CVSS — what it's for and how to use it without lying CVSS scores **vulnerability severity characteristics**, not your risk. Base score assumes a "reasonable worst case" deployment that is not yours. Rules: - **Use CVSS for vulnerabilities (CVEs, pentest findings), not design-stage threats.** Threats from a model lack the concrete exploit parameters CVSS vectors encode; scoring hypotheticals produces false precision. - **Never rank by base score alone.** Adjust with environmental/threat metrics — or informally: Is the vulnerable path reachable here? Authn in front? Exploit public? Asset behind it valuable? A 9.8 in a dev-only, network-isolated tool can be Medium; a 6.5 authz bypass on the payment API is Critical. - **Record the vector string, not just the number** (e.g. `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H`) so reviewers can audit the assumptions — the vector is the rating; the number is a summary. - **Note the version prefix.** CVSS v4.0 (the current spec, 2023) replaced temporal metrics with threat metrics (CVSS-B/BT/BTE nomenclature); v3.1 and v4.0 scores coexist in feeds — the prefix says which rules produced the number, so never compare scores across versions. - **CVSS ≠ exploitation probability.** For "will this actually be exploited", consult exploit-prediction signals (EPSS-class scores, KEV-style known-exploited lists) and your own exposure; severity and likelihood are separate axes. ## 3. Likelihood × impact — the default rating method Use a 3×3 (or at most 4×4) matrix with ANCHORED level definitions. Anchors are the whole method; publish them with the model. **Likelihood anchors** (calibrate to your exposure): | Level | Anchor | |---|---| | High | Exploitable by an unauthenticated or any-customer actor with public techniques/tools; or attack is automated/commodity (credential stuffing, scanner-findable) | | Medium | Needs a valid account, a specific misconfig, social engineering, or chaining two weaknesses; technique documented but targeted | | Low | Needs insider access, physical access, significant novel research, or multiple independent failures | **Impact anchors** (write per system, in business terms — PASTA-lite preamble from `01` §4 feeds this): | Level | Anchor (example for a payments SaaS) | |---|---| | High | Any-customer data breach; money movement; RCE in prod; full account takeover class | | Medium | Single-account compromise; partial data exposure; sustained outage of a paid feature | | Low | Cosmetic, single-user self-inflicted, info of negligible value | **Matrix → priority:** | | Impact Low | Impact Med | Impact High | |---|---|---|---| | **Likelihood High** | Medium | High | Critical | | **Likelihood Med** | Low | Medium | High | | **Likelihood Low** | Low | Low | Medium* | \* Escalate Low×High to High when the impact is catastrophic-irreversible (safety, signing-key theft) — fat tails don't average. Rules: - **Rate the threat AS MITIGATED TODAY,** then optionally note inherent (un- mitigated) risk. Ranking inherent risk floods the top band uselessly. - **Likelihood follows the easiest path** to the impact, not the path you enumerated first. Attack trees (`01` §5) find the cheapest branch. - **Don't multiply made-up numbers.** 1–5 scales multiplied give 1–25 pseudo-precision; band labels resist abuse better. For real quantification use a FAIR-style analysis with ranges — only worth it for top-5 enterprise risks. - **Calibrate across raters:** score 5 threats independently, compare, refine anchors where raters diverged. Re-calibrate when the team changes. Severity-label conventions for audit findings are defined in `SKILL.md` and elaborated in `06` — same axes, evidence-backed. ## 4. Treatment: accept / mitigate / transfer / avoid Every threat gets exactly one disposition, recorded with the threat. "Noted" or an unassigned ticket is not a disposition. | Treatment | Use when | Required record | |---|---|---| | **Mitigate** | Control is cheaper than expected loss; default for High+ | Requirement ID(s) + owner + verification test (§5) | | **Accept** | Residual risk below appetite; cost of control exceeds it | Named individual owner (a person with authority over the asset, not "the team"), rationale, expiry/review date. Un-expiring acceptances are how risk registers rot. | | **Transfer** | Liability shiftable: insurance, payment processor, managed service | Contract/SLA reference + the residual you keep (reputation never transfers; breach-response duties rarely do) | | **Avoid** | Feature/design not worth its risk | Design change record — e.g., don't store PANs at all (use a token vault), drop the "fetch any URL" feature, remove the agent's write-tools | Rules: - **Avoid is underused.** Always ask "can we not have this data/feature/ privilege?" before designing a mitigation — deleted attack surface needs no patching. Data minimization is risk avoidance (and LINDDUN Dd treatment). - **Mitigations can change likelihood OR impact — say which.** Rate limiting cuts likelihood; encryption + tokenization cut impact; segmentation cuts blast radius. Prefer one impact-cutting control plus one likelihood-cutting control on Critical paths (defense in depth, but bounded — two layers, justified, not five vague ones). - **Acceptance escalates with severity:** Low — tech lead; Medium — eng manager; High — director/CISO; Critical — not acceptable without executive sign-off and a dated remediation plan. ## 5. Mapping mitigations to requirements and tests A mitigation that is not a requirement will not be built; a requirement that is not a test will not survive refactoring. **Chain:** threat → requirement → implementation → verification. Keep IDs linked end to end: ``` T-012 (High) Authenticated customer reads other tenants' orders via GET /orders/{id} (IDOR; STRIDE-E/I) SR-104 Every order access MUST verify order.tenant_id == caller.tenant_id at the data-access layer (not per-handler). IMPL OrderRepository.get() takes tenant from auth context; handlers cannot pass tenant explicitly. (PR #482) VER-1 Integration test: tenant-A token + tenant-B order id → 404. [abuse case] VER-2 Static check: CI greps for OrderRepository bypass / raw order queries outside the repository module. [regression guard] ``` Rules: - **Write requirements as testable MUSTs** with a location ("at the data-access layer"), not aspirations ("handle authz properly"). One requirement may cover many threats; never the reverse without splitting. - **Every mitigated High+ threat gets a VER entry** — an abuse-case test (negative test from the attacker's perspective, see `05` §3) and, where possible, a structural guard (lint rule, policy-as-code, dependency rule) that fails CI on regression. - **Requirements live in the backlog with normal tracking** (see `05` §2) — a threat model PDF with embedded TODOs is a graveyard. ## 6. Residual risk documentation After treatment, what risk remains — because controls are partial, accepted, or transferred-with-remainder? Document per threat, one line: ``` T-012 residual: tenant check enforced in repository layer; raw-SQL escape hatches remain in /reports module (compensated by VER-2 grep, reviewed quarterly). Residual rating: Low. Owner: j.doe. Review: 2026-09. ``` Rules: - **Residual rating uses the same L×I matrix** — re-rate after controls, don't hand-wave "lower". - **Aggregate residuals roll up to a risk register** (one table: threat ID, residual rating, owner, review date) — this is the artifact leadership actually reads; keep it under a page. - **An expired review date flips the acceptance to a finding** in audits (`06`). Build the expiry sweep into the living-model cadence (`05` §4). ## 7. Rating anti-patterns (rejected on sight in review) - **Severity haggling by remediation cost.** "Let's call it Medium because the fix is hard" — cost belongs in the treatment decision (maybe you accept or phase it), never in the rating. Once cost leaks into severity, the register stops describing reality. - **Best-case likelihood.** Rating "Low because our WAF probably blocks it" — rate against the easiest path WITH evidence of the control; "probably" means the control is Partial and the rating stands. - **Impact capped at the component.** "Only the worker is compromised" — impact follows the privilege-reach map (`02` §8), not the initially-popped box. A worker with DB write is a database compromise. - **Stale ratings after architecture change.** L×I was assigned when the service was internal; it's public now. Re-rating is part of every re-model trigger (`05` §4), not a separate ceremony. - **One number for a threat class.** "XSS: High" — each instance rates on its own context (what the page can reach, who views it). Classes get thematic findings (`06` §4); instances get ratings. - **Risk-appetite fog.** Accept/mitigate thresholds undefined, so every dispute is relitigated. Write the appetite line once ("we mitigate all High+, accept Medium with director sign-off, batch Lows quarterly") and point at it. - **Probability theater.** "0.3 likelihood × $2.4M = $720k expected loss" built on gut numbers. Either do the FAIR-style estimation with calibrated ranges and document the basis, or keep honest bands. ## Worked micro-example — rating in context Same vulnerability, two ratings, both correct: - *Unauthenticated SSRF in PDF renderer; renderer pod has IMDSv2 enforced, egress-deny netpol, no cloud role.* Likelihood High, Impact Low (reaches nothing) → **Medium**, mitigate via URL allowlist; accept residual Low. - *Same SSRF; pod runs with node's instance role, flat VPC.* Likelihood High, Impact High (metadata creds → account) → **Critical**: fix the egress/IMDS posture (impact cut) AND the SSRF (likelihood cut); nothing acceptable here. The CVE/base score would be identical for both. Context is the rating. ## Audit checklist - [ ] A single, anchored rating scheme is defined and used consistently; anchors written down, not folklore. - [ ] No raw DREAD means or unadjusted CVSS base scores used as priority; CVSS vectors recorded where CVSS is used. - [ ] Threats rated as-mitigated, with likelihood reflecting the easiest path; Low×catastrophic escalated, not averaged away. - [ ] Every threat has exactly one disposition: mitigate/accept/transfer/ avoid; no "noted"/orphaned threats. - [ ] Acceptances have a named individual owner, rationale, and unexpired review date; acceptance authority matches severity. - [ ] "Avoid" considered (data/feature/privilege removal) before expensive mitigations — evidence in at least one disposition. - [ ] Threat→requirement→test ID chain intact for all High+ mitigations; abuse-case tests exist and run in CI. - [ ] Residual risks re-rated on the same scale and rolled into a ≤1-page register with owners and review dates. - [ ] Expired acceptances/reviews flagged as findings. -
05-outputs-operationalization.md 12.1 KB
# 05 — Outputs & Operationalization A threat model's value is measured by the controls it ships and the regressions it catches — not by the document. This file defines the artifacts and the machinery that keeps them alive. ## 1. The threat model document — template Keep it short enough to re-read at every trigger (target: 2–6 pages / one markdown file in-repo). Store it NEXT TO THE CODE (`docs/threat-model.md` or per-service), versioned in git — review-diffable, blame-able, findable. ```markdown # Threat Model: <system/service> — v<NN> Owner: <tech lead> Last full review: <date> Methodologies: STRIDE-per-interaction (+LINDDUN) ## 1. Scope & business context What is modeled, what is explicitly excluded and why. 3 sentences on what the business loses if this system fails (feeds impact anchors, see 04 §3). ## 2. System model L0/L1 mermaid DFDs with trust boundaries (02). Tables: entry points, assets (ranked), actors/privilege levels, data stores. ## 3. Assumptions Numbered, falsifiable: "A1: broker is reachable only inside the VPC", "A2: provider X does not train on our data (contract §4)". Every assumption is a standing threat if false — audits test these first. ## 4. Threats & dispositions | ID | Threat (actor→action→asset→impact) | Class | L | I | Rating | Disposition | Req IDs | Residual | One row per threat. This table IS the model; everything else is support. ## 5. Security requirements SR-IDs with testable MUST statements + verification refs (04 §5). ## 6. Risk register (residuals & acceptances) | Threat | Residual | Owner | Review date | ≤ 1 page (04 §6). ## 7. Re-model triggers & history The trigger list for THIS system + changelog of revisions and what prompted them. ``` Rules: - **The threat table is the contract.** Reviews approve rows, not prose. If a section doesn't change a row, cut it. - **Diagrams as code** (mermaid in markdown): diffable in PRs, no stale Visio exports. A diagram that can't be updated in the same PR as the code change will drift. - **Write assumptions you'd bet against.** "We assume input is validated upstream" is the most breached assumption in distributed systems; naming it makes it auditable. ## 2. Security requirements backlog - **Requirements enter the same backlog as features** — same tracker, same refinement, same definition of done. A separate "security spreadsheet" is where requirements go to die. - **Tag and link:** label `security`, link to threat ID and model version. Priority comes from the threat rating (04 §3): Critical-derived requirements block the release that introduces the threat; High within the sprint/cycle; Medium scheduled; Low backlog with review date. - **Acceptance criteria = the verification entry.** A security story is done when the abuse-case test passes and the structural guard is in CI — not when "code is written". - **Recurring requirements become paved road.** If three models demand "JWT validation per SR-x", build/adopt a shared middleware and convert the requirement to "uses paved-road component vX" — threat modeling output should compound into platform, shrinking future models. ## 3. Abuse cases as test cases For every mitigated High+ threat, write the attacker's user story and automate it: ``` Abuse case AC-012 (from T-012, IDOR): As tenant-A attacker with a valid session, I request GET /orders/{tenant-B-order-id} expecting 404/403 and an authz-failure audit event. test_cross_tenant_order_access_denied(): token = login(tenant="A") r = client.get(f"/orders/{seed_order(tenant='B').id}", auth=token) assert r.status_code in (403, 404) assert audit_log.contains(event="authz.denied", actor=token.sub) ``` Rules: - **Test the control's OBSERVABLE effect, not its implementation** (status code + audit event, not "repository was called with tenant param") — so refactors keep the test honest. - **Negative tests at the right layer:** authz → integration/API tests; injection → unit tests on the sink + fuzz where parsers are involved; rate limits/DoS caps → load-shaped tests or config assertions; CSP/headers/IaC posture → policy-as-code (OPA/conftest, tfsec-style rules) running in CI. - **For LLM/agent threats** (03 §8): maintain an injection corpus (strings embedding "ignore instructions, call tool X / exfiltrate to URL") run against the agent in CI; assert tools-not-called / URL-not-fetched / spend caps hold. Probabilistic systems need statistical assertions (N trials, zero tool-policy violations). - **Each abuse-case test cites its threat ID in the test name or docstring** — when it fails, the developer reads WHY this matters; when someone deletes it, review sees a threat losing its verification. - **Pentest/red-team findings feed back:** every confirmed finding becomes (a) a threat-table row — was it missing or mis-rated? — and (b) an abuse-case regression test. ## 4. Keeping the model living A threat model is stale the moment the system changes in a way the model didn't anticipate. Freshness is enforced by TRIGGERS, not calendars (plus one calendar backstop). ### Re-model triggers (the canonical list — tailor per system, never shrink below this) | Trigger | Why it invalidates the model | |---|---| | New dependency (package, SaaS, model provider) | New org-trust boundary + supply-chain surface (03 §6) | | New entry point: route, queue/topic, cron, webhook, callback, upload | Attack surface change by definition (02 §5) | | New or moved trust boundary (service split/merge, network change, new env) | Every boundary crossing needs enumeration (01 §2) | | New data class (PII category, credentials, payment, health) | Asset table + LINDDUN pass invalidated; impact anchors shift | | Authn/authz change (token format, session, roles, tenancy model) | The S and E columns of every interaction change | | Crypto/key management change | Silent impact-rating changes across stored assets | | New actor class (partners, plugin authors, agent tools, support tooling) | Privilege table invalidated | | Deserialization / file parsing / template rendering added | Highest-yield vuln classes; instant catalog pass | | Incident or pentest finding in this system | Empirical proof the model missed something | | Acceptance/review date expired (04 §6) | Disposition no longer valid | ### Enforcement mechanics (pick at least two) - **PR template** with a `## Security notes` section: author states which triggers apply (or "none") — makes the check cheap and the omission visible. - **CI trigger heuristics:** flag PRs touching route registrations, lockfiles with new packages, IaC network/IAM files, auth middleware, or `*.proto` — require the security-notes section to be non-trivial on flagged PRs. - **Model version pinning:** the threat-model doc records the git SHA range it covers; an audit (06) compares triggers-since-SHA against model revisions. - **Calendar backstop:** full re-read annually or per major version, WHICHEVER COMES FIRST with trigger-driven updates — the backstop catches slow drift (dependency rot, team turnover, assumption decay). ### Incremental update discipline - Trigger fires → update only the affected rows/diagram region + bump model version with a one-line changelog ("v12: added Stripe webhook EP3, threats T-031..034"). Full rewrites are for re-architecture only. - **Deleting is updating:** removed features must remove threats/requirements, or the model accretes noise until nobody reads it. Dead rows are marked `retired (vNN)`, kept one version for the diff, then dropped. - New team members onboard by READING the threat model before the code — if that's not useful, the model has failed its second purpose (knowledge transfer), fix it. ## 5. PR security-notes template (paste into PR template) ```markdown ## Security notes Triggers touched: [ ] new dependency [ ] new entry point [ ] trust boundary [ ] new data class [ ] authn/authz [ ] crypto [ ] parsing/deserialization [ ] none New input → from whom: ... Runs at privilege → can now reach: ... Writes/emits/calls (incl. logs, third parties): ... At 1000× volume / 100MB payload: ... Threat model updated? <link/version or "no triggers"> ``` A filled template takes 3 minutes for "none" and ~10 when triggers fire; it gives reviewers a fixed place to look and auditors (`06`) a drift signal. Reject "N/A" without the checkbox rationale — the box list IS the rationale. ## 6. Threat-model review session format (when a workshop IS warranted) For new services, new trust boundaries, or escalations from a four-questions pass. 60–90 minutes, hard cap; 3–6 people: feature owner, one engineer who did NOT write the design, security (if available), someone who runs prod. 1. (10 min) Owner walks the DFD; attendees attack the DIAGRAM first — missing flows, unlabeled arrows, "where does the webhook actually land?" Fixing the model is cheaper than fixing threats against the wrong model. 2. (35 min) Per boundary crossing, STRIDE prompts; scribe writes threat sentences directly into the table — no minutes, no slides. 3. (15 min) Rate and disposition in-session for everything captured; assign requirement owners. Undispositioned threats don't leave the room. 4. (5 min) Confirm re-model triggers and the model owner. Rules: the design's author never scribes (they defend instead of capture); "that's already handled" requires naming WHERE (file/control) or the threat stays; park exploit-tactics rabbit holes after 2 minutes — enumeration breadth beats depth here. ## 7. Program health metrics (measure the machinery, not threat counts) Track quarterly, per service: | Metric | Healthy signal | Smell | |---|---|---| | Trigger compliance | % trigger-matching PRs with non-trivial security notes ≥ 90% | template rubber-stamped "none" on PRs adding routes | | Model freshness | days since last revision < days since last trigger-matching merge | model older than the current architecture | | Verification coverage | % High+ mitigations with passing abuse-case tests = 100% | requirements "done" with no VER link | | Acceptance hygiene | 0 expired review dates | acceptances from departed owners | | Escape rate | incidents/pentest findings that existed as un-actioned model rows | model knew, backlog buried it — a prioritization failure, fix the rating pipeline not the model | Do NOT manage to "number of threats found" — it incentivizes noise and punishes good design. Escape rate is the only outcome metric that matters. ## 8. Output sizing — match artifact to audience | Audience | Artifact | Size | |---|---|---| | Engineers (daily) | threat table + requirements in repo | the source of truth | | Reviewers (per PR) | security-notes section | 3–10 lines | | Leadership (quarterly) | risk register | ≤ 1 page | | Auditors/customers | the doc (template §1) + evidence links | 2–6 pages | Never produce the 40-page monolith: it satisfies no audience and updates never. Generate views from the threat table instead. ## Audit checklist - [ ] Threat model exists in-repo, versioned, with owner and last-review date; follows (or maps cleanly onto) the template sections. - [ ] The threat table has L/I/rating/disposition/requirement-ID columns filled for every row; no prose-only threats. - [ ] Assumptions are explicit, numbered, and individually testable; spot- check 2–3 against reality (network reachability, contract terms). - [ ] Security requirements live in the team's actual backlog with threat-ID links and rating-derived priority — not a side spreadsheet. - [ ] Every mitigated High+ threat has an automated abuse-case test citing its threat ID; tests assert observable effects and run in CI. - [ ] Posture controls (headers, IaC, IAM) covered by policy-as-code checks, not manual review notes. - [ ] Re-model trigger list documented for this system; PR template or CI heuristics enforce it; sample 5 trigger-matching PRs for security notes. - [ ] Model changelog shows trigger-driven incremental updates (not one big- bang revision years ago); covered-SHA or date range recorded. - [ ] Incidents/pentest findings traceable into threat rows + regression tests. - [ ] Risk register ≤ 1 page, current owners, no expired review dates. -
06-audit-reconstruction.md 12.3 KB
# 06 — AUDIT Mode: Reconstructing a Threat Model from an Existing System Goal: given a codebase (plus IaC, configs, pipelines), rebuild the threat model the system IMPLIES, compare it with whatever the team INTENDED, and report the gaps as severity-rated, evidence-backed findings. This is gap analysis against catalogs — not pentesting (you prove absence of controls, not presence of exploits) and not code review (you work at the boundary/control level, not line-by-line). ## 1. Audit procedure (ordered; timebox each phase) 1. **Collect artifacts (30 min):** repo(s), lockfiles, IaC, CI configs, Dockerfiles/k8s manifests, any existing threat model/architecture docs, `.env.example`, OpenAPI/proto specs. Note what you were NOT given — un-auditable surface goes in the report as such, never silently skipped. 2. **Extract the system model** per `02` §B: entry-point sweep, store/asset inventory, actor/privilege table, inferred trust boundaries. Output the reconstructed mermaid DFD — this diagram is deliverable #1 even if you find nothing else; most teams have never seen their real attack surface drawn. 3. **Reconstruct intent:** read existing docs/ADRs/old models, auth middleware comments, IaC structure. Write down the implied assumptions ("services trust the gateway's headers", "bucket is private"). Each assumption becomes a test target. 4. **Run the catalogs (`03`)** against each component → control-presence matrix (§2 below). Prioritize components on paths to top-3 assets when time-boxed; record de-scoping explicitly. 5. **Test assumptions from step 3:** for each, find the code/config that makes it true. No evidence = broken assumption = finding (these are usually the Criticals: "gateway-only trust" with services bound to 0.0.0.0). 6. **Gap analysis (§3):** absent/partial controls → threat sentences → rate in deployment context (`04` §3) → findings (§6). 7. **Deliver (§7):** findings report + reconstructed model + remediation ordering. Hand the reconstructed model to the team as their new baseline (`05` §4) — the audit's lasting value. ## 2. Control-presence matrix For every (component × applicable catalog item) record one of: | State | Meaning | Evidence required | |---|---|---| | **Present** | Control implemented and reachable on all relevant paths | file:line of the control + how you confirmed coverage | | **Partial** | Implemented but bypassable, inconsistent, or covering some paths | both: where it works and where it doesn't | | **Absent** | No control found after a genuine search | the searches performed (so reviewers can re-run them) | | **N/A** | Catalog item doesn't apply | one-line reason | | **Unverifiable** | Outside provided artifacts | what artifact would settle it | Rules: - **Evidence or it didn't happen — in both directions.** "Present" without file:line is as worthless as "Absent" without the search trail. Audits get challenged; the matrix is your defense. - **Partial is the most important state.** One authz-checked endpoint proves the team knows the pattern; the seventeen unchecked ones are the finding. Inconsistency also tells you remediation is adoption, not invention. - **Sample honestly:** for repetitive surfaces (50 routes), sample ≥20% plus ALL routes touching top assets; state the sampling rule in the report. - **Check the negative space:** middleware exclusion lists, `// TODO: auth`, `@SkipAuth`-style decorators, IaC `count = 0`/commented blocks, disabled tests with `security` in the name, `.allowlist` files. Disabled controls are stronger findings than never-built ones (someone decided). Matrix excerpt (orders-api × API catalog, `03` §2): | Catalog item | State | Evidence | |---|---|---| | Object-level authz | **Partial** | tenant check in invoice_repo.py:22, payments.py:31; ABSENT in order_repo.py:18 + 8 of 11 sampled by-id routes | | Mass assignment | Present | DTO allowlists via schemas/*.py; no `**request.json` hits | | SSRF controls | **Absent** | webhook "test" fetch at hooks.py:77, no allowlist; searches: `requests.get(`, `urlopen`, `httpx` | | Rate limiting | **Unverifiable** | nginx config not provided; app-level none found | | Deserialization | N/A | JSON only; no pickle/yaml.load hits | ## 3. Gap analysis — from absent control to ranked finding For each Absent/Partial cell: 1. **Write the threat sentence** the missing control would have addressed (actor → action → asset → impact), using the actual actors and assets from the reconstructed model — never "an attacker could potentially". 2. **Walk the real path:** can the named actor actually reach the weakness from an entry point in THIS deployment? An unparameterized query fed only by a config constant is hygiene (Low), not injection (Critical). Reachability is what separates an audit from a scanner run. 3. **Classify the gap:** - **Missing primary control** — nothing stands between actor and asset (no authz check on a reachable endpoint). Rates on raw L×I. - **Missing defense-in-depth** — primary control exists; backstop absent (authz present, but no RLS / no audit log). Cap at one band below what primary-control failure would rate, and say which primary it backstops. - **Posture/hygiene** — weakens future changes rather than today's paths (shared DB superuser in a single-service system). Usually Low/Medium with a "rises to X when Y" note. 4. **Chain before rating.** Individually-Medium gaps that compose into a Critical path get ONE chained finding rated for the chain (SSRF [M] + IMDSv1 [M] + over-privileged role [M] = metadata-credential takeover [C]), with member gaps listed as remediation points. Report the chain, not three medium tickets nobody connects. ## 4. Severity calibration (audit-specific) Apply `SKILL.md` conventions + `04` §3 anchors, with these audit rules: - **Rate what IS, not what might be coded later.** Severity reflects current deployment context; include a "context sensitivity" note when one config change would jump the rating ("Medium today; Critical if this service is ever exposed publicly — see T-7"). - **Unverifiable ≠ Low.** A control you couldn't verify on a Critical path is reported as "Unverified, potential High" with the artifact request — downgrading for lack of access rewards opacity. - **Broken stated assumptions inherit the severity of what relied on them.** If "internal network is trusted" underpins all service authn and is false, that single finding is Critical even though each individual service merely "lacks mTLS". - **No severity inflation for volume.** Forty Low hygiene findings do not sum to a High; instead emit one thematic finding ("input validation is not systematic: 40 instances, list attached") so the remediation is a pattern fix, not whack-a-mole. ## 5. Tooling and timeboxing Tools feed the audit; they are never the audit: - **Use scanners as input, not output.** SAST/dep-scan/IaC-scan results are candidate Absent/Partial cells — each still needs the reachability walk (§3.2) before it becomes a finding. Forwarding scanner output as an audit is the canonical failure mode of this discipline. - **Targeted code sweeps** (opengrep/grep) excel at the matrix's repetitive cells: authz-check presence per route, raw-SQL escape hatches, `verify=False`, skip-auth decorators, `dangerouslySetInnerHTML`. Save the rule set — it becomes the structural regression guard you recommend (`04` §5 VER-2). - **Read IaC before code.** Network policy, IAM, and bucket policy answer reachability questions that would take hours to establish from app code. Timebox tiers (state which tier the report represents): | Tier | Budget | Scope | |---|---|---| | Rapid | 1 day | DFD + entry-point sweep + assumptions test + top-asset paths only; catalogs for the 2–3 highest-risk components | | Standard | 3–5 days | Full matrix on all components on top-3 asset paths; sampled elsewhere | | Deep | 2+ weeks | Full matrix everywhere + chain construction + process meta-audit | A Rapid audit that says so is honest; a Rapid audit formatted like a Deep one is malpractice — the un-examined surface must be listed (§1.1). ## 6. Finding format (binding) Every finding uses the `SKILL.md` format. Worked example: ``` [HIGH] Cross-tenant order read via unscoped lookup (orders-api, STRIDE-E/I) Location: services/orders/handlers/get_order.py:41; absent tenant filter in repositories/order_repo.py:18 Threat: Any authenticated customer can read any other tenant's orders (PII: names, addresses — asset A1) by iterating sequential order IDs on GET /orders/{id}, because the lookup filters by id only and IDs are sequential (migrations/0042_orders.sql:7). Evidence: get_order.py:41 `order = repo.get(order_id)` — no tenant_id in query; confirmed pattern on 9 of 11 by-id endpoints sampled (exceptions: invoices, payments — both filter by tenant). Recommendation: enforce tenant scoping in the repository layer (single choke point, pattern already exists in invoice_repo.py:22); switch new IDs to UUIDv7. Map to SR-104; add abuse-case test per 05 §3. Residual risk if accepted: mass PII enumeration by any self-service signup; likely notifiable breach. ``` Rules: - One finding = one decidable remediation. Split findings the team would assign to different owners; merge instances of one pattern (per §4). - The Evidence line must let a skeptic reproduce your conclusion from the repo alone. Quote minimally; cite precisely. - Recommendation names WHERE the control goes and reuses the team's existing good patterns when they exist (adoption beats invention, §2). - Positive observations (Present controls on critical paths) get a short section — they calibrate trust in the audit and stop teams "fixing" what works. ## 7. Audit report structure ``` 1. Scope & method: artifacts received/withheld, sampling rules, timebox, catalogs applied, un-audited surface. 2. Reconstructed system model: DFD + entry-point/asset/actor tables (02). 3. Findings: Critical → Low, chained findings first within band. 4. Thematic observations: systemic patterns (one per theme) + positives. 5. Control-presence matrix: appendix, full table with evidence. 6. Assumption test results: stated/implied assumptions, held or broken. 7. Proposed baseline: the model handed back for living maintenance (05 §4), pinned to the audited git SHA. 8. Remediation ordering: chains broken at cheapest link first; pattern fixes over instance fixes; quick wins (config-level Criticals) flagged. ``` Meta-findings — the audit also rates the team's PROCESS: - No threat model existed → Medium process finding (plus the baseline you deliver remediates it). - Model existed but drifted (trigger-matching changes since last revision, `05` §4) → finding, with the missed triggers listed. - Expired risk acceptances, orphaned threats without dispositions, abuse-case tests deleted/skipped → findings per `04`/`05` checklists. ## Audit checklist (meta — quality bar for the audit itself) - [ ] Artifact inventory recorded, including what was NOT provided; nothing silently skipped — unverifiable surface reported as such. - [ ] Reconstructed DFD + entry-point sweep completed per 02 §B before any catalog work; sweep commands/searches reproducible. - [ ] Implied assumptions written down and each tested against code/config; broken assumptions rated by what relied on them. - [ ] Control-presence matrix complete for in-scope components; every cell Present/Partial/Absent/N-A/Unverifiable with evidence or search trail. - [ ] Sampling rules stated; all top-asset paths examined, not sampled. - [ ] Negative space checked: exclusion lists, skip-auth decorators, disabled tests, commented-out IaC. - [ ] Reachability walked for every finding; hygiene vs. primary-control vs. defense-in-depth gaps distinguished and rated accordingly. - [ ] Exploit chains assembled and rated as chains; no severity-by-volume. - [ ] Every finding in the binding format with file:line evidence and a located, pattern-reusing recommendation. - [ ] Positive controls acknowledged; process meta-findings (stale model, expired acceptances) included. - [ ] Baseline model delivered, pinned to the audited SHA, ready for living maintenance per 05.
-
-
SKILL.md 9.1 KB
--- name: sota-threat-modeling description: >- State-of-the-art threat modeling for both designing new systems and auditing existing ones. Use when designing a feature, service, integration, or architecture that touches untrusted input, new trust boundaries, sensitive data, or third-party dependencies (BUILD mode), and when reviewing, auditing, or pen-test-scoping an existing codebase to reconstruct its implicit threat model and find gaps (AUDIT mode). Not for code-level vulnerability review — use sota-code-security. Trigger keywords: threat model, STRIDE, LINDDUN, PASTA, attack tree, kill chain, data flow diagram, DFD, trust boundary, attack surface, abuse case, security design review, security architecture review, risk rating, DREAD, CVSS, security requirements, secure design, security audit, gap analysis, prompt injection, excessive agency, threat catalog, mitigations, residual risk. --- # SOTA Threat Modeling ## Purpose Threat modeling answers Shostack's four questions with engineering rigor: 1. **What are we working on?** (decompose: DFD, trust boundaries, assets, actors) 2. **What can go wrong?** (enumerate: STRIDE/LINDDUN per element, catalogs, attack trees) 3. **What are we going to do about it?** (treat: mitigate/accept/transfer/avoid, map to requirements and tests) 4. **Did we do a good job?** (verify: abuse-case tests, residual risk review, re-model triggers) This skill operationalizes those questions in two modes. Never produce a threat model that is only prose — every threat must land as a tracked requirement, a test, or an explicitly accepted risk with an owner. ## BUILD Mode — Threat-Model-While-Designing Run this workflow whenever designing anything that crosses a trust boundary. Scale effort to risk: a 15-minute "four questions" pass for a small feature; a full STRIDE-per-interaction model for a new service or auth flow. ### Workflow 1. **Scope the delta.** Model what is new or changed, not the whole system. List new entry points, new data classes, new dependencies, new actors. 2. **Draw the DFD as text/mermaid** (see `rules/02`). Mark trust boundaries explicitly. If you cannot draw a boundary, you do not understand the design yet — stop and ask. 3. **Pick the methodology** (see `rules/01`): STRIDE-per-interaction by default; add LINDDUN if personal data flows; attack trees for a single high-value asset; four-questions-only for low-risk deltas. 4. **Enumerate threats** crossing each boundary using the per-component catalogs in `rules/03`. Write each threat as: *actor → action → asset → impact*. No vague entries ("hacking", "data breach"). 5. **Rate and treat** each threat (see `rules/04`): likelihood × impact matrix, then accept / mitigate / transfer / avoid. Every mitigation becomes a security requirement with an ID. 6. **Emit artifacts** (see `rules/05`): threat model doc, security requirements backlog entries, abuse cases as test stubs, and re-modeling triggers. 7. **Wire into delivery.** Reference requirement IDs in the design doc, tickets, and PR descriptions. A threat without a tracked artifact does not exist. ### Continuous / incremental (agile, PR reviews) - Threat model the **story**, not the sprint. Add a "Security notes" section to design docs and PR descriptions for any change matching a re-model trigger: new dependency, new endpoint/route/queue/cron/webhook, new trust boundary, new data class, auth/authz change, file/deserialization handling. - In PR review, run a micro-STRIDE on the diff only: what new input enters? whose privilege executes it? what does it write or call? Takes 5 minutes; catches the majority of design-level regressions. ## AUDIT Mode — Reconstructing a Threat Model from Code Use when handed an existing system with no (trustworthy) threat model. Goal: rebuild the implicit model from artifacts, then diff intended vs. actual controls. Full procedure in `rules/06`. ### Workflow 1. **Inventory entry points from code** (see `rules/02` §extraction): routes, queue consumers, cron jobs, webhooks, third-party callbacks, CLI/admin tools, file uploads, IaC-exposed ports. 2. **Reconstruct the DFD** from the inventory: processes, stores, external entities, flows; infer trust boundaries from network topology, authn checkpoints, and IAM policies. 3. **Identify assets and actors** from schemas, secrets handling, and config. 4. **Run the catalogs** (`rules/03`) against each component; for every catalog item record: control present / absent / partial, with file:line evidence. 5. **Gap analysis**: rank absent/partial controls by exploitability × blast radius; distinguish "missing control" from "missing defense-in-depth". 6. **Report findings** in the standard format below. ### Severity conventions | Severity | Definition | |---|---| | Critical | Remotely exploitable now, by an unauthenticated or low-priv actor, leading to full compromise of a key asset (RCE, auth bypass, mass data exfil). Fix before anything else ships. | | High | Exploitable with realistic preconditions (one valid account, one misconfig, MitM position) compromising a key asset; or a Critical with a single weak mitigating layer. Fix this sprint. | | Medium | Requires chaining, elevated access, or unusual conditions; or impacts a secondary asset; or defense-in-depth gap on a Critical path. Schedule. | | Low | Hardening, hygiene, info disclosure of low-value data, theoretical with strong existing controls. Backlog. | Severity = exploitability × impact in **this** deployment context — never copy a CVE/CVSS base score without environmental adjustment (see `rules/04`). ### Finding format (every finding, no exceptions) ``` [SEV] TITLE (component, STRIDE/LINDDUN class) Location: path/to/file.py:123 (and IaC/config refs) Threat: <actor> can <action> via <vector> because <missing/weak control>, impacting <asset> (<C/I/A/privacy impact>). Evidence: code excerpt or config line proving the gap. Recommendation: specific control + where it goes; map to requirement ID. Residual risk if accepted: one sentence. ``` ## Rules Index | File | Read this when... | |---|---| | `rules/01-methodologies.md` | Choosing between STRIDE, LINDDUN, PASTA, attack trees, kill chains; deciding lightweight vs. heavyweight; setting up continuous/PR-level threat modeling. | | `rules/02-decomposition.md` | Drawing DFDs in mermaid, defining trust boundaries, listing entry points/assets/actors/privilege levels; extracting all of these from an existing codebase. | | `rules/03-threat-catalogs.md` | Enumerating threats for a specific component: web frontend, API, database, message queue, file storage, CI/CD, mobile, LLM agent/tool-use, cloud/IAM. | | `rules/04-risk-rating-treatment.md` | Rating threats (DREAD pitfalls, CVSS usage, L×I matrices), choosing accept/mitigate/transfer/avoid, mapping mitigations to requirements and tests, documenting residual risk. | | `rules/05-outputs-operationalization.md` | Writing the threat model document, building the security requirements backlog, turning abuse cases into tests, keeping the model alive (re-model triggers). | | `rules/06-audit-reconstruction.md` | Auditing an existing system: reconstructing the model from code, control-presence matrix, gap analysis, severity calibration, reporting. | Load only the files you need; `rules/02` + `rules/03` cover 80% of day-to-day work. ## Top-10 Non-Negotiables 1. **No model without a diagram.** Every threat model includes a DFD (mermaid or ASCII) with explicit trust boundaries. Prose-only models hide boundary confusion. 2. **Threats are sentences, not nouns.** *Actor → action → asset → impact.* "SQL injection" is a vector; "anonymous user exfiltrates the orders table via unparameterized search query" is a threat. 3. **Every entry point gets enumerated** — including queues, cron, webhooks, callbacks, admin tooling, and CI/CD. HTTP routes are never the whole attack surface. 4. **Trust boundary crossings drive enumeration.** Apply STRIDE per interaction at each crossing; data inside one boundary at one privilege level rarely needs the full treatment. 5. **Personal data ⇒ LINDDUN pass.** STRIDE does not cover linkability, identifiability, or non-compliance; run a privacy pass whenever PII flows or is stored. 6. **Rate with likelihood × impact in context.** Never ship raw DREAD scores or unadjusted CVSS base scores as priorities. 7. **Every threat gets a disposition.** Mitigate (→ requirement ID + test), accept (→ named owner + expiry date), transfer, or avoid. "Noted" is not a disposition. 8. **Mitigations become tests.** Each mitigated threat yields at least one abuse-case test (unit, integration, or rule-based check) that fails if the control regresses. 9. **LLM/agent components are first-class attack surface.** Model prompt injection, tool-call abuse, excessive agency, and data exfil via outputs for any system invoking an LLM with tools or retrieved content. 10. **Models expire.** Define re-model triggers (new dependency, new trust boundary, new data class, auth change) in the document itself; an undated, trigger-less threat model is treated as absent in audits.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.