---
key: "threat-pattern-library"
title: "Threat pattern library for threat modeling"
url: "https://softwaresecurity.com/domains/appsec/threat-modeling/threat-pattern-library/"
api: "https://softwaresecurity.com/api/v1/articles/threat-pattern-library.json"
domain: "appsec"
topic: "Application security"
type: "Reference"
identifiers: ["CWE-290","CWE-441","CWE-639","CWE-863","CWE-915","CWE-89","CWE-436","CWE-502","CWE-367","CWE-294","CWE-918","CWE-532","CWE-829","CWE-770","CWE-1427","A01:2025","A06:2025","CAPEC-21","CAPEC-1","CAPEC-66","CAPEC-33","CAPEC-586","CAPEC-29","CAPEC-664","CAPEC-141","CAPEC-538","CAPEC-130"]
tags: ["Threat modeling"]
stages: ["design"]
roles: ["developer","appsec-engineer"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Threat pattern library for threat modeling

Thirty-two recurring threat patterns grouped by identity, authorization, parsers, concurrency, integrations, data exposure, supply chain, availability and AI agents, each with where it shows up on a data-flow diagram, mitigations, a verification step and STRIDE, CWE and CAPEC links, plus a YAML catalog and a script that lists patterns per diagram element.

Canonical URL: https://softwaresecurity.com/domains/appsec/threat-modeling/threat-pattern-library/

## In short

Most threats in a design review are not new: a trusted identity header, a store shared by tenants, a check followed by a separate action, a callback nobody verifies. Walk the data-flow diagram, look up each element in the table at the top of this page, and ask the question for each pattern listed. Each pattern gives the mitigation that usually works, how to verify it and its STRIDE, CWE and CAPEC entries. Finish with STRIDE per element for anything the library does not cover.

- Do start from the diagram: the element type and what it does (reads by ID, fetches URLs, runs a model) select the patterns to check.
- Do ask each pattern's question in the session and record the answer, even when it is "already handled".
- Do turn every pattern you keep into a requirement with a test that fails when the mitigation is removed.
- Do add patterns from your own incidents and findings to the catalog so the next review asks about them.
- Don't treat the library as complete; follow it with STRIDE per element and per interaction for new kinds of components.

## Verify it

1. For your last threat model, check that every element was compared with the lookup table and that each applicable pattern has a recorded answer.
2. Pick three patterns marked "handled" and find the test that would fail if the mitigation were removed.
3. Run the review script on the current diagram and compare its list with the session agenda; investigate any element it flags with no pattern.
4. After each incident, add or update a pattern and the tag that would have selected it.

## What you will learn

- Select the threat patterns that apply to each element and flow of a diagram.
- Name the usual mitigation and a verification step for each pattern.
- Map patterns to STRIDE, CWE and CAPEC for tracking and search.
- Maintain a team catalog of patterns and generate a review agenda from a diagram.

Audience: Developers, architects and AppSec engineers who run or join threat modeling sessions

Before you start:

- Be able to read a data-flow diagram with trust boundaries.
- Know the six STRIDE categories.
- For the script: Python 3.11 or later with PyYAML.

Time: About 38 minutes: 13 to read, 5 on the code and 20 for the exercise; afterwards, use the tables as a reference

## How do you use a threat pattern library?

A threat pattern is a design shape that keeps producing the same kind of vulnerability: a service that trusts an identity header, a store shared by tenants, a check followed later by an action. During a threat modeling session, walk the data-flow diagram element by element, look up the element in the table below, and ask the pattern's question. Each pattern gives the mitigations that usually work, how to verify them, and its STRIDE category, CWE and CAPEC entries.

The library speeds up the "what can go wrong?" question; it does not replace it. Patterns cover what recurs across systems, so finish with STRIDE per element and per interaction for anything the library does not cover, as described in /domains/appsec/threat-modeling/threat-modeling-methods/. Turn each pattern you keep into a requirement and a test with /domains/appsec/threat-modeling/security-requirements-definition-of-done/, and use /domains/appsec/threat-modeling/abuse-case-library/ for feature-level abuse cases.

| On the diagram you see | Check these patterns |
| --- | --- |
| A flow that crosses a trust boundary | TP-01 caller-supplied identity, TP-03 token for the wrong audience, TP-10 parser differential, TP-14 replayable message |
| A process that reads or writes records by ID | TP-05 object access by client key, TP-07 mass assignment, TP-08 client-side enforcement |
| A data store, cache or search index shared by tenants | TP-06 missing tenant filter, TP-21 shared cache, TP-32 retrieval without filtering |
| A process that builds queries, commands or templates | TP-09 interpreter injection, TP-11 unsafe deserialization, TP-12 user input in file paths |
| Money, quotas, inventory or single-use tokens | TP-13 check-then-act race, TP-15 workflow step skipping |
| An inbound callback or outbound fetch | TP-14 replayable message, TP-16 user-chosen URL, TP-17 unverified callback, TP-18 trusting a partner API |
| A service that acts for many users | TP-02 confused deputy, TP-04 access that outlives the user |
| Logs, analytics or responses | TP-19 over-broad responses, TP-20 sensitive data in logs |
| Configuration, CI and the build | TP-22 long-lived shared secrets, TP-23 signing keys within reach, TP-24 untrusted code with release credentials, TP-25 dependency substitution, TP-26 unverified artifacts |
| Batch jobs, uploads, queues and public APIs | TP-27 unbounded work per request, TP-28 fan-out without backpressure |
| An LLM, agent or retrieval component | TP-29 untrusted content steering tools, TP-30 standing agent credentials, TP-31 model output as code, TP-32 retrieval without filtering |

## Which identity and trust patterns recur?

These patterns come from trusting a claim about who is calling without verifying it, or from privileges that outlast the reason they were granted. Deeper: /domains/identity-crypto/authentication/jwt-validation-rollover/, /domains/identity-crypto/identity/workload-identity-models/ and /domains/identity-crypto/authentication/scim-provisioning-deprovisioning/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-01 Caller-supplied identity | A service reads `X-User-Id`, a tenant field or a role from the request; internal services trust headers set by the edge | Derive identity from a verified credential at every boundary; strip identity headers at the edge and authenticate service-to-service calls | Send the request with a forged identity header from outside and from a neighboring service; expect refusal | Spoofing / CWE-290, CWE-807 / CAPEC-21 |
| TP-02 Confused deputy | A file service, CI job, webhook sender or agent performs actions with its own privileges on behalf of callers | Act with the caller's identity or a token scoped to the caller's request; check the caller's rights before acting | Ask the deputy to act on a resource the caller cannot access; expect refusal | Elevation of privilege / CWE-441 |
| TP-03 Token accepted for the wrong audience or purpose | Several services accept the same token issuer; ID tokens used as access tokens | Validate signature, issuer, audience, expiry and token type at each service; one audience per service | Present a valid token minted for service A to service B; expect 401 | Spoofing / CWE-287 / CAPEC-21 |
| TP-04 Access that outlives the user | Long sessions, refresh tokens, API keys or cached roles after deprovisioning or a role change | Short-lived tokens, revocation on deprovisioning, role changes applied immediately (v5.0.0-8.3.2) | Disable a test user and replay their session, refresh token and API key; expect all refused | Elevation of privilege / CWE-613 |

## Which authorization and tenancy patterns recur?

Most serious API findings are authorization failures that a design review could have predicted. Deeper: /domains/appsec/threat-modeling/multitenant-design-review/, /domains/appsec/authorization/object-level-authorization/ and /domains/appsec/authorization/function-property-authorization/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-05 Object access by client-supplied key | Routes such as `/invoices/{id}`, export jobs, signed URLs, GraphQL node IDs | Check the caller's right to the object on every read and write; scope queries by owner or tenant | Two-tenant test: tenant A requests tenant B's IDs on every route, including bulk and export | Elevation of privilege / CWE-639 / CAPEC-1 |
| TP-06 Missing tenant filter in a shared store | Shared database, cache, search index, object store or analytics table | Tenant condition inside every query, cache key and index lookup; row-level security as a backstop | Query every access path from tenant A for data only tenant B has | Information disclosure / CWE-863 |
| TP-07 Mass assignment | Handlers bind request JSON directly to models or ORM objects | Explicit allowlists of writable fields per action (v5.0.0-15.3.3) | Add `role`, `tenant_id` or `owner_id` to update requests; expect them ignored | Tampering / CWE-915 |
| TP-08 Client-side enforcement | Hidden buttons, disabled fields or client-computed prices stand in for server checks | Enforce every rule on the server (v5.0.0-8.3.1) | Call the API directly with values the UI would not allow | Tampering / CWE-602 |

## Which input and parser patterns recur?

Injection appears wherever data crosses into an interpreter; parser differentials appear wherever two components read the same bytes. Deeper: /domains/appsec/secure-coding/injection-parser-boundaries/, /domains/runtime/edge-defense/http-request-desynchronization/, /domains/appsec/input-handling/bounded-json-xml-parsing/ and /domains/appsec/secure-features/archive-path-safety/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-09 Interpreter injection at the boundary | SQL, shell commands, templates, LDAP or NoSQL queries built from request data | Parameterized APIs; no shell; logic-less templates for user content; allowlists for identifiers | Fuzz each parameter with interpreter metacharacters; static analysis for string-built queries | Tampering / CWE-89, CWE-78, CWE-1336 / CAPEC-66, CAPEC-88 |
| TP-10 Parser differential | A proxy and a backend parse HTTP framing differently; a validator and a fetcher parse URLs differently | Normalize once and pass the parsed form; reject ambiguous input; keep proxy and server settings aligned | Send ambiguous requests (duplicate `Content-Length`, mixed encodings, odd URL forms) through the real chain | Tampering / CWE-436 / CAPEC-33 |
| TP-11 Unsafe deserialization | Native object formats (`pickle`, Java serialization, YAML with tags) read from queues, cookies or files | Data-only formats such as JSON with schema validation; sign anything that must round-trip | Search for native deserializers on untrusted input; submit a gadget payload in a test environment | Tampering / CWE-502 / CAPEC-586 |
| TP-12 User input in file paths | Upload names, archive entries or download parameters used to build paths | Generate names; resolve paths and check they stay under the base directory | Submit `../` sequences, absolute paths and archive entries that escape | Tampering / CWE-22 / CAPEC-126 |

## Which state, concurrency and replay patterns recur?

These bugs are about time: two requests at once, the same message twice, or steps in the wrong order. Deeper: /domains/appsec/business-logic/transaction-races/, /domains/appsec/secure-features/verify-webhook-deliveries/ and /domains/appsec/business-logic/business-logic-flaws/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-13 Check-then-act race | Balance checks, single-use coupons, invite tokens, stock reservation | One atomic operation: conditional update, lock, or unique constraint (v5.0.0-15.4.2) | Release 20 identical requests at once; expect exactly one success | Tampering / CWE-367 / CAPEC-29 |
| TP-14 Replayable message | Webhooks, signed URLs, one-time codes, payment confirmations | Sign with a timestamp; reject stale timestamps and seen IDs; idempotent handlers | Resend a captured valid message; expect no second effect | Spoofing / CWE-294 |
| TP-15 Workflow step skipping | Multi-step checkout, onboarding, approval or KYC flows with one endpoint per step | Server-side state machine; each step checks the previous state (v5.0.0-2.3.1) | Call the last step directly for an object in an earlier state; expect 409 | Tampering / CWE-841 |

## Which integration and outbound-call patterns recur?

Every integration is a boundary in both directions. Deeper: /domains/appsec/secure-features/ssrf-egress-boundaries/ and /domains/appsec/secure-features/verify-webhook-deliveries/; fix card /fix/ssrf/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-16 Server-side request to a user-chosen URL | Webhook registration, URL previews, image fetchers, import-from-URL, PDF renderers | Allowlist destinations or block private ranges at connect time; no redirects (v5.0.0-15.3.2); egress proxy | Register metadata, loopback and redirecting URLs; expect every fetch refused | Elevation of privilege / CWE-918 / CAPEC-664 |
| TP-17 Unverified inbound callback | Payment, identity or Git provider callbacks that change state | Verify the provider signature over the raw body before parsing; fetch the authoritative state from the provider when it matters | Send unsigned, wrongly signed and modified callbacks | Spoofing / CWE-345 |
| TP-18 Trusting a partner API's responses | Data from a third-party API is stored, rendered or used in queries without the checks applied to user input | Validate and bound partner data like user input; time-outs and size limits; TLS with certificate validation; no blind redirects | Stub the partner to return oversized, malformed and script-bearing responses | Tampering / OWASP API10:2023 |

## Which data exposure and logging patterns recur?

Data leaks through responses, logs and caches more often than through the database itself. Deeper: /domains/runtime/detection/security-event-logging/, /domains/runtime/edge-defense/cache-poisoning-deception/ and /domains/identity-crypto/privacy/data-minimization-retention/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-19 Over-broad responses | Endpoints serialize whole objects; GraphQL exposes every field | Return only the fields each caller needs (v5.0.0-15.3.1); field-level authorization | Compare responses for each role with the documented fields | Information disclosure / CWE-213 |
| TP-20 Sensitive data in logs | Request logging, error handlers, analytics events | Log allowlists; redact tokens and personal data; encode values (v5.0.0-16.4.1) | Grep logs from a test run for tokens, passwords and seeded personal data | Information disclosure / CWE-532, CWE-117 |
| TP-21 Shared cache serving private data | CDN or reverse proxy in front of authenticated pages | Mark private responses `Cache-Control: private, no-store`; cache keys include what varies the response | Request a private page with a static-looking suffix, then fetch it without credentials | Information disclosure / CWE-524 / CAPEC-141 |

## Which secrets and supply-chain patterns recur?

Build and release paths hold the most powerful credentials in the organization. Deeper: /domains/identity-crypto/secrets/secrets-lifecycle/, /domains/supply-chain/ci-cd/untrusted-workflow-boundaries/, /domains/supply-chain/dependencies/dependency-confusion/ and /domains/supply-chain/provenance/artifact-signature-policy/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-22 Long-lived shared secrets | API keys in config files, CI variables shared by many jobs, one database password for all services | Workload identity and short-lived tokens; a secret per consumer; rotation | Scan repositories and images for secrets; list who and what can read each secret | Information disclosure / CWE-798 |
| TP-23 Signing keys within reach of the app or build | Code-signing, token-signing or encryption keys stored in files, CI variables or application memory | Keep keys in a KMS or HSM behind a narrow signing service, or use keyless signing; separate who can sign from who can build | List every principal that can use each key; try to sign from a normal CI job | Elevation of privilege / CWE-522 |
| TP-24 Untrusted code with release credentials | Pull requests from forks run in jobs that can publish, deploy or read secrets | Separate untrusted builds from privileged jobs; least-privilege tokens per job | Open a pull request from a fork that prints the environment; expect no secrets | Elevation of privilege / CWE-829 |
| TP-25 Dependency substitution | Internal package names resolvable from public registries; unpinned versions | Scoped names, registry configuration that pins sources, lockfiles with hashes | Resolve dependencies in a clean environment and compare sources with the expected registry | Tampering / CWE-427 / CAPEC-538 |
| TP-26 Unverified artifacts | Deploy pulls images or binaries by tag without checking signature or provenance | Verify signatures and provenance at admission or install; pin digests | Deploy an unsigned artifact to staging; expect rejection | Tampering / CWE-494 / CAPEC-185 |

## Which availability patterns recur?

Resource exhaustion is usually a missing limit rather than a flood. Deeper: /domains/runtime/api-security/resource-consumption-limits/ and /domains/runtime/availability/bounded-background-work/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-27 Unbounded work per request | Pagination without a maximum, batch endpoints, GraphQL depth, file and archive size | Limits on size, count, depth, time and cost enforced before work starts | Send the largest allowed and one-over-limit requests; measure time and memory | Denial of service / CWE-770 / CAPEC-130 |
| TP-28 Fan-out without backpressure | One request enqueues many jobs; retries multiply load | Bounded queues, per-tenant concurrency, retry budgets with backoff | Load test one tenant and confirm others keep their latency | Denial of service / CWE-400 |

## Which AI and agent patterns recur?

In LLM features, the model is not a security boundary: anything it reads can steer it, and anything it outputs is untrusted. Deeper: /domains/ai-emerging/llm-security/prompt-injection-boundaries/, /domains/ai-emerging/agent-security/tool-authorization/, /domains/ai-emerging/llm-security/untrusted-model-output/ and /domains/ai-emerging/llm-security/rag-tenant-isolation/.

| Pattern | Where it shows up in a design | Mitigations | How to verify | STRIDE / CWE / CAPEC |
| --- | --- | --- | --- | --- |
| TP-29 Untrusted content steering a model's tools | The model reads email, web pages, tickets or documents and can call tools | Tool gateway outside the model that authorizes each call against the user's rights and intent; no generic fetch or shell tools | Plant instructions in content the model will read; expect no unintended tool call | Elevation of privilege / CWE-1427 |
| TP-30 Standing agent credentials | Agents run with a service account that can do more than any one user | Act with the user's delegated, scoped token; approvals for destructive actions | List the agent's effective permissions and compare with the users it serves | Elevation of privilege / CWE-250 |
| TP-31 Model output used as code or markup | Output rendered as HTML, run as SQL or shell, or passed to `eval` | Treat output as untrusted data: escape, parameterize, sandbox | Ask the model to emit a script tag and a SQL fragment; expect them inert | Tampering / CWE-79, CWE-94 |
| TP-32 Retrieval without per-user filtering | One vector index for all tenants; filtering after ranking | Tenant and permission filters inside the vector query, before ranking | Ask about a document only another tenant can see; expect nothing from it | Information disclosure / CWE-863 |

## How do you keep the patterns machine-readable?

Keep the catalog in the repository as YAML so teams can add patterns from their own incidents and findings. Each entry names the element types it applies to (`applies_to`) and the tags an element must carry (`when`), plus the question to ask and its mappings. The file below holds twelve of the patterns as a starting point; add the rest the same way.

```yaml
# Threat pattern catalog: extend it with your own patterns and findings.
# applies_to: element types (process, store, external, flow); when: tags a DFD item must carry.
- id: TP-01
  name: Caller-supplied identity
  stride: Spoofing
  applies_to: [flow]
  when: [crosses_boundary]
  check: Is the caller's identity taken from a verified credential, never from a header, field or parameter the caller controls?
  cwe: [CWE-290, CWE-807]
- id: TP-05
  name: Object access by client-supplied key
  stride: Elevation of privilege
  applies_to: [process]
  when: [reads_by_id]
  check: Does every read and write of an object check the caller's right to that object, including exports and bulk routes?
  cwe: [CWE-639]
- id: TP-06
  name: Missing tenant filter in a shared store
  stride: Information disclosure
  applies_to: [store]
  when: [multi_tenant]
  check: Is the tenant condition applied inside every query, cache key and index lookup on this store?
  cwe: [CWE-863]
- id: TP-09
  name: Interpreter injection at the boundary
  stride: Tampering
  applies_to: [process]
  when: [builds_query]
  check: Is untrusted data passed as parameters rather than spliced into SQL, shell, template or query text?
  cwe: [CWE-89, CWE-78, CWE-1336]
  capec: [CAPEC-66, CAPEC-88]
- id: TP-13
  name: Check-then-act race
  stride: Tampering
  applies_to: [process]
  when: [limited_resource]
  check: Are the check and the state change one atomic operation (conditional update, lock or unique constraint)?
  cwe: [CWE-367]
  capec: [CAPEC-29]
- id: TP-14
  name: Replayable message
  stride: Spoofing
  applies_to: [flow]
  when: [inbound_callback]
  check: Is each message signed, time-bounded and checked against already-seen IDs, and is the handler idempotent?
  cwe: [CWE-294, CWE-345]
- id: TP-16
  name: Server-side request to a user-chosen URL
  stride: Elevation of privilege
  applies_to: [process]
  when: [fetches_url]
  check: Are destinations allowlisted or private ranges blocked at connect time, with redirects off and an egress proxy?
  cwe: [CWE-918]
  capec: [CAPEC-664]
- id: TP-20
  name: Sensitive data in logs
  stride: Information disclosure
  applies_to: [store]
  when: [logs]
  check: Are secrets, tokens and personal data kept out of logs, and are logged values encoded against log injection?
  cwe: [CWE-532, CWE-117]
- id: TP-24
  name: Untrusted code with release credentials
  stride: Elevation of privilege
  applies_to: [process]
  when: [ci_pipeline]
  check: Can code from a fork or pull request run in a job that holds publish or deploy credentials?
  cwe: [CWE-829]
- id: TP-27
  name: Unbounded work per request
  stride: Denial of service
  applies_to: [process]
  when: [batch_or_upload]
  check: Are size, count, depth, time and cost limits enforced before the work starts?
  cwe: [CWE-770]
  capec: [CAPEC-130]
- id: TP-29
  name: Untrusted content steering a model's tools
  stride: Elevation of privilege
  applies_to: [process]
  when: [llm_with_tools]
  check: Does a gateway outside the model authorize every tool call against the user's permissions and intent?
  cwe: [CWE-1427]
- id: TP-32
  name: Retrieval without per-user filtering
  stride: Information disclosure
  applies_to: [store]
  when: [vector_index]
  check: Are tenant and permission filters applied inside the vector query, before ranking?
  cwe: [CWE-863]
```

## How do you list the patterns for a diagram automatically?

Describe the diagram as elements and flows with tags, then let a script list the questions for each one. For an expense-claims service with an API, a receipt worker, an LLM assistant, a shared database, a policy index, logs and a payment-provider callback, the script lists twelve element-pattern pairs and two flows (API to database, worker to database) with no catalog match, which it marks for a STRIDE pass. The output is an agenda for the session, not a finding list. Tested with Python 3.14 and PyYAML 6.0.3.

Example tags for the diagram: `reads_by_id` and `builds_query` on the API, `fetches_url` and `batch_or_upload` on the worker, `llm_with_tools` on the assistant, `multi_tenant` on the database and the index, `vector_index` on the index, and `crosses_boundary` and `inbound_callback` on the payment callback flow. Run it with `python review_patterns.py patterns.yaml dfd.yaml`.

```python
"""List the threat patterns to review for each element and flow of a data-flow diagram.

Usage: python review_patterns.py patterns.yaml dfd.yaml
"""
import sys

import yaml


def matches(pattern, kind, tags):
    return kind in pattern["applies_to"] and set(pattern["when"]) <= set(tags)


def review(patterns, dfd):
    items = [(e["id"], e["name"], e["type"], e.get("tags", [])) for e in dfd["elements"]]
    items += [(f["id"], f"{f['from']} -> {f['to']}", "flow", f.get("tags", [])) for f in dfd["flows"]]
    for item_id, name, kind, tags in items:
        hits = [p for p in patterns if matches(p, kind, tags)]
        for p in hits:
            refs = ", ".join(p.get("cwe", []) + p.get("capec", []))
            print(f"{item_id:9} {name:22} {p['id']} {p['name']} ({p['stride']}; {refs})")
            print(f"{'':32} {p['check']}")
        if not hits and kind != "external":
            print(f"{item_id:9} {name:22} no catalog pattern: review with STRIDE per element")


if __name__ == "__main__":
    patterns = yaml.safe_load(open(sys.argv[1], encoding="utf-8"))
    dfd = yaml.safe_load(open(sys.argv[2], encoding="utf-8"))
    review(patterns, dfd)
```

## Guided practice

Review the design of a fictional document-signing service with the pattern library. Find the patterns that apply, decide which matter most, and write the verification for two of them.

### Supplied material

```text
Design: SignFlow (fictional)
Elements:
  Browser -> Edge proxy -> Documents API -> Postgres (shared by all customers)
  Documents API -> Render worker (queue) -> Object store
  Render worker fetches "letterhead_url" from the customer's template to build PDFs
  Edge proxy sets X-Account-Id after login; Documents API reads it
  Signing provider -> POST /callbacks/signed (marks a document as signed)
  "Ask SignFlow" assistant reads documents and can call send_reminder(email) and void_document(id)
Notes from the team:
  - Documents are fetched by /documents/{uuid}; "UUIDs are unguessable, so no check is needed"
  - Callbacks come from the provider's IP range, so they are trusted
  - Render jobs have no size limit; large PDFs "just take longer"
```

### 1. Map elements to patterns

Use the lookup table to list the patterns for each element and flow.

Worked answer: Edge to API flow: TP-01 (`X-Account-Id` is caller-supplied identity unless the API only accepts it from the edge over an authenticated channel). Documents API: TP-05 and TP-07. Postgres: TP-06. Render worker: TP-16 (`letterhead_url`) and TP-27 (no size limit). Callback flow: TP-14 and TP-17. Assistant: TP-29, TP-30 and TP-32.

### 2. Challenge the team notes

Respond to the UUID and IP-range assumptions.

Worked answer: An unguessable ID is not authorization: UUIDs leak through links, logs and shares, so TP-05 still needs a per-object check. An IP range is not proof of origin (shared cloud ranges, proxies); TP-17 needs signature verification over the raw body.

### 3. Rank the top three

Pick the three patterns to fix first and say why.

Worked answer: TP-16 (letterhead fetch from a worker inside the network can reach metadata endpoints and internal services), TP-17 with TP-14 (forged or replayed callbacks mark documents as signed, which is the product's core integrity claim), and TP-29 with TP-30 (a planted instruction in a document could void documents through the assistant).

### 4. Write two verifications

Write the verification steps for TP-16 and TP-17.

Worked answer: TP-16: set `letterhead_url` to the cloud metadata address, a loopback port and a public URL that redirects inside; expect every fetch refused and logged. TP-17: send an unsigned callback, a correctly signed one with a changed body and a replay of a valid one; expect no state change in all three.

### 5. Record the result

Add tags to the diagram so the review script selects these patterns next time.

Worked answer: API: `reads_by_id`; Postgres: `multi_tenant`; worker: `fetches_url` and `batch_or_upload`; callback flow: `crosses_boundary` and `inbound_callback`; assistant: `llm_with_tools`.

Deliverable: A table of applicable patterns per element, the top three with reasons, and verification steps for TP-16 and TP-17.

Scope: The design is fictional and incomplete; a real session would also cover repudiation (audit of signing events) and availability of the signing provider.

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [Threat Modeling Manifesto](https://www.threatmodelingmanifesto.org/)
- [OWASP Threat Modeling Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Threat_Modeling_Cheat_Sheet.html)
- [Microsoft: STRIDE threat categories (Threat Modeling Tool)](https://learn.microsoft.com/en-us/azure/security/develop/threat-modeling-tool-threats)
- [MITRE CAPEC: Mechanisms of Attack view (CAPEC-1000)](https://capec.mitre.org/data/definitions/1000.html). CAPEC 3.9.
- [MITRE CWE: CWE-441 Unintended Proxy or Intermediary (Confused Deputy)](https://cwe.mitre.org/data/definitions/441.html). CWE 4.20.
- [MITRE CWE: CWE-1427 Improper Neutralization of Input Used for LLM Prompting](https://cwe.mitre.org/data/definitions/1427.html). CWE 4.20.
- [OWASP ASVS 5.0.0: V15 Secure Coding and Architecture](https://github.com/OWASP/ASVS/blob/v5.0.0_release/5.0/en/0x24-V15-Secure-Coding-and-Architecture.md). Version 5.0.0.
- [OWASP Top 10:2025 A06 Insecure Design](https://top10.owasp.org/2025/A06_2025-Insecure_Design/)