Practical threat modeling. Covers STRIDE, DREAD, attack trees, data flow diagrams, threat libraries, and integrating threat modeling into development workflows.
Threat Modeling for Engineering Teams
Threat modeling for engineering teams is the practice of systematically identifying what can go wrong in the systems you build, and arranging countermeasures before deployment. It matters when you’re designing a new service, restructuring ownership of a data flow, onboarding a third‑party integration, or preparing for an audit. Unlike a one‑off security review, threat modeling is a repeatable craft that lives alongside your architecture diagrams, CI/CD pipelines, and incident playbooks. The following sections are organized around the concrete actions an engineering team can take today.
Before you can model threats, you need a stable reference for what you’re protecting. Start with an architecture diagram that shows components, cross‑network links, and trust boundaries. If your team uses Kubernetes, kubectl get all -n prod -o yaml gives you a live snapshot of services, endpoints, and ports. For a quick reachability check, curl -s -o /dev/null -w "%{http_code} %{time_total}" https://api.example.com/health will confirm status and latency. Keep this definition in version control alongside your code: a threat-model.md file in the same repository as the service it describes, so the model rotates with the architecture.
Threat modeling hinges on tracing how data moves from entry to storage to processing to exit. A practical way to capture this is to export your service map from your orchestration layer and run a diff against the previous version. For Docker Compose projects, docker compose config --resolve-images prints the resolved image names and exposed ports, which you can pipe into jq to extract high‑risk paths: docker compose config --resolve-images | jq '.services[] | select(.ports != null) | {name: .name, exposed: .ports[].target}'. If you’re working with PostgreSQL, psql -U app -d production -c "\dp" lists row‑level permissions, and psql -U app -d production -c "SELECT table_name, privilege_type FROM information_schema.table_privileges WHERE grantee = 'public';" surfaces overly broad grants. These commands don’t model threats themselves; they give you the factual surface area the model will reference.
With the surface area in hand, apply a structured mnemonic like STRIDE or PASTA. The goal is to answer, for each component, “can an attacker exploit this to spoof, tamper, repudiate, information‑disclose, deny service, or escalate?” A minimal threat‑list file might look like:
## Threats: auth-service
- **Spoofing**: forged JWT tokens if signing key is exposed
- **Tampering**: mutable query parameters routed to backend
- **Repudiation**: no audit log for password-reset requests
To validate coverage, run a simple grep across your repository: grep -R "Threats:" --include="*.md" . | wc -l ensures every service has at least one documented threat entry. If the count is zero, the model is incomplete. This is the kind of guardrail that catches the silent failure mode where a new microservice is shipped without any threat entry, and it stays invisible until an incident report references it.
Once threats are listed, each one needs a mitigation that’s traceable to implementation work. A common pattern is to add a mitigations: block in the same threat-model.md file, then reference those IDs in pull‑request templates or issue trackers. For example, if a threat reads “plaintext secrets in environment variables”, the mitigation entry might be:
- ID: TM-001
Threat: Secrets leaked via `docker inspect`
Mitigation: Move all secrets to a vault; update `docker run -e DATABASE_URL=$VAULT_SECRETS` CI scripts
Owner: @team-infra
Status: implemented in PR #402
To enforce that every open PR references a threat‑model entry, add a pre-commit hook that runs git diff --cached | grep -q "TM-[0-9]*" || exit 1. If a developer forgets to tie a code change to a threat mitigation, the hook aborts the commit with the error pre-commit hook failed: no threat-model reference found. This doesn’t prevent the vulnerability; it makes the omission visible at the point of change.
A threat model decays as fast as the architecture it describes. Schedule a monthly CI job that compares the current kubectl output against the stored diagram. A one‑liner like kubectl get ingress -A -o yaml > /tmp/current-ingress.yaml && diff -u threat-model/ingress.yaml /tmp/current-ingress.yaml will surface drift. If the diff is non‑empty, the CI job can post a comment on the pull request that merged the last architecture change, reminding the team to update the model. Similarly, psql -c "SELECT indexname, idxrelid::regclass FROM pg_indexes WHERE schemaname = 'public';" can be scheduled to flag indexes that haven’t been used in 30 days, because unused indexes often correlate with abandoned data paths that were never de‑thought.
The most common silent failure is model rot: the diagram and threat list exist, but they’re never consulted after the first release. Engineers assume the model is “someone else’s job”, usually the security team, and the file atrophies in a drawer. Another confusion is treating “risk” and “vulnerability” as synonyms. A vulnerability is a technical hole (e.g., an open port); risk is the probability‑and‑impact combination that decides whether it’s worth fixing right now. Threat‑modeling tools that output a single “risk score” often hide the underlying assumptions, so two teams using the same tool can arrive at different remediation priorities without realizing the scoring function differs. You only learn this after a production incident where the team says “we had it rated low, but the attacker chained three low‑rated issues to get in.” The sharp edge is that threat modeling is necessary but not sufficient; it must be paired with runtime verification, feature flags that can disable vulnerable paths, and a culture that treats the model as a living artifact, not a compliance checkbox.
This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy