Guides » OPSEC & Footprints » Threat Modeling

Threat Modeling: Building a Living Asset-First Profile

Most threat modeling exercises produce a PDF that nobody reads after the sprint ends. The model sits in a shared drive while the actual system drifts β€” new services deployed, ports opened, dependencies added β€” and the model never gets updated. By the time an incident occurs, the document describes a system that hasn't existed for months.

This guide takes a different approach: threat modeling as a living artifact tied to your actual infrastructure. We start with assets, not adversaries. We map controls to specific threats on specific assets. And we build verification steps that fail loudly when the model diverges from reality. Tested on a three-person team managing Ubuntu 24.04 servers, a GitLab CI pipeline, and a handful of self-hosted services.

Before you start, gather: your architecture diagram (even a napkin sketch), a list of every external dependency (CDNs, APIs, managed services), and your incident history from the last 12 months. If you don't have an incident history, that's your first finding β€” you're not detecting what's already happening.

1. Where Your Identity Actually Leaks

Adversary-first modeling ("who might attack us?") produces generic threat lists. Asset-first modeling ("what breaks if this fails?") produces actionable priorities. Start with a structured inventory.

1.1 Asset Classification Template

For each asset, record:

Asset ID: A-001
Name: Customer PII Database
Type: Data store (PostgreSQL 16 on Ubuntu 24.04)
Location: VPC subnet 10.0.2.0/24, us-east-1a
Sensitivity: HIGH (GDPR Art. 9 special categories)
Integrity Requirement: TAMPER-EVIDENT
Availability Requirement: RPO 1h, RTO 4h
Owners: @backend-team, @dba-team
Dependencies: 
  - AWS RDS managed service (control plane)
  - Application servers (read/write)
  - Backup encryption keys (KMS key alias/prod/pii-backup)
  - Log aggregation (Datadog, 30-day retention)
Threat Surface:
  - SQL injection via API endpoints
  - Credential reuse from CI/CD secrets
  - Snapshot exfiltration via compromised IAM
  - Insider query abuse (no row-level audit)
Asset inventory spreadsheet showing Asset ID, Name, Type, Sensitivity, Integrity, Availability, Owners, Dependencies for 12 assets across data, compute, identity, and network layers
Asset inventory template β€” 12 assets mapped across data stores, compute instances, identity systems, and network components. Each row feeds directly into the threat mapping in Section 2.

1.2 Dependency Mapping: The Hidden Attack Surface

Every managed service you use is a trust boundary. Document what you control vs. what the provider controls:

Dependency Provider Controls You Control Verification Method
AWS RDS (PostgreSQL) OS patching, replication, backup storage, network isolation Database users, encryption keys, query audit, parameter groups Monthly: verify encryption at rest, check parameter group drift, test point-in-time recovery
Cloudflare (WAF + DNS) Edge network, DDoS mitigation, certificate management WAF rules, DNS records, origin certificates, rate limit configs Weekly: export WAF rules to Git, verify DNSSEC, test origin cert renewal
GitLab SaaS (CI/CD) Runner infrastructure, container registry, web UI Pipeline definitions, variables, protected branches, runner tags Per-deploy: verify no secret leakage in job logs, check protected branch enforcement
SendGrid (Transactional Email) IP reputation, delivery infrastructure, suppression lists API keys, domain authentication, template content, webhook endpoints Monthly: rotate API keys, verify SPF/DKIM/DMARC alignment, check suppression list growth

2. The Mitigation Plan

Stop modeling "nation-state actors" unless you have evidence they're targeting you. Model capabilities you've actually observed or that match your exposure.

2.1 Capability Tiers (Observed in Our Environment)

Tier Label Capabilities Observed Indicators Assets at Risk
1 Opportunistic Automation Credential stuffing, default credential scanning, known CVE exploitation (<30 days old), cryptojacking Daily SSH brute force on port 22, weekly vulnerability scanner hits on /wp-admin/, monthly crypto miner deployments on exposed Docker APIs All internet-facing compute, any service with default credentials
2 Targeted Crimeware Phishing with domain spoofing, session token theft, supply chain compromise (npm/PyPI), ransomware with data exfiltration Quarterly phishing campaigns against team, two supply chain incidents in 2024 (malicious dependency updates), one ransomware attempt via compromised contractor VPN Identity systems, CI/CD pipelines, developer workstations, backup systems
3 Persistent Access Living-off-the-land, kernel rootkits, firmware implants, BGP hijacking, cloud control plane abuse Zero observed directly. Modeled based on industry reporting for our sector (SaaS, <100 employees). Included for completeness. Core infrastructure, encryption key hierarchy, network backbone, logging pipeline
Adversary capability matrix showing three tiers mapped to MITRE ATT&CK techniques observed in environment: T1110 brute force, T1190 exploit public-facing, T1195 supply chain, T1486 ransomware, T1078 valid accounts
Capability-to-ATT&CK mapping. Each observed indicator maps to specific techniques. Tier 3 techniques are included based on sector threat intelligence, not direct observation.

2.2 Capability-to-Asset Mapping

For each asset, ask: which tiers can reach it, and through what path?

Asset A-001 (Customer PII Database):
  Tier 1: NO direct path (private subnet, no internet egress)
  Tier 2: YES via compromised application server (A-003) β†’ SQL injection β†’ data exfil
  Tier 3: YES via compromised DBA workstation (A-007) β†’ SSH key reuse β†’ direct DB access
  Tier 3: YES via compromised AWS IAM (A-011) β†’ RDS snapshot copy β†’ cross-account share

3. Step-by-Step Execution

Controls without threat mappings are waste. Threats without controls are gaps. Build a bidirectional traceability matrix.

3.1 Control Catalog with Threat Traceability

Control ID Control Description Addresses Threat Asset(s) Protected Verification Method Owner
C-001 Parameterized queries enforced via ORM; raw SQL blocked by CI lint Tier 2: SQL injection via API A-001, A-002 CI check: semgrep rule blocks raw query strings; quarterly manual review @backend-team
C-002 RDS encryption at rest with customer-managed KMS key; key rotation annually Tier 2: Snapshot exfiltration; Tier 3: Cross-account snapshot share A-001 Monthly: AWS Config rule checks encryption; annual key rotation test @dba-team
C-003 pgAudit extension logging all DML/DDL to CloudWatch; 90-day retention Tier 2: Insider query abuse; Tier 3: Persistent access data staging A-001, A-002 Weekly: log volume alert; monthly: sample query review for anomalies @dba-team
C-004 GitLab protected branches: main requires 2 approvals, signed commits, pipeline pass Tier 2: Supply chain compromise via malicious PR A-003, A-004, A-005 Per-MR: enforcement verified; quarterly: audit branch protection settings @platform-team
C-005 Hardware security keys (YubiKey 5) required for all GitLab, AWS, Cloudflare access Tier 2: Credential stuffing, session theft; Tier 3: Valid accounts A-003 through A-012 Monthly: enumerate users without registered keys; quarterly: attestation verification @security-team
C-006 VPC flow logs to S3 + Athena; GuardDuty enabled on all accounts Tier 1: Reconnaissance; Tier 2: Data exfiltration; Tier 3: Lateral movement A-008, A-009, A-010 Daily: GuardDuty findings review; weekly: flow log query for anomalous egress @platform-team
C-007 Backup encryption with separate KMS key; cross-region replication; quarterly restore test Tier 2: Ransomware encryption; Tier 3: Backup destruction A-001, A-002, A-006 Quarterly: full restore to isolated VPC; verify data integrity checksums @dba-team

3.2 Gap Analysis: Unmapped Threats and Orphan Controls

Run this query monthly against your control catalog:

# Threats with no controlling control
SELECT t.threat_id, t.description 
FROM threats t
LEFT JOIN controls c ON c.addresses_threat = t.threat_id
WHERE c.control_id IS NULL;

# Controls protecting no mapped threat
SELECT c.control_id, c.description
FROM controls c
LEFT JOIN threats t ON t.threat_id = c.addresses_threat
WHERE t.threat_id IS NULL;

In our last review, this surfaced three gaps: no control for BGP hijacking of our Cloudflare prefixes (Tier 3), no control for malicious GitLab runner registration (Tier 2), and C-008 (automated dependency scanning) was protecting a threat we'd reclassified as out-of-scope.

4. Staying Invisible: Ongoing Habits

A threat model that doesn't detect its own obsolescence is theater. Build automated drift checks into your deployment pipeline.

4.1 Infrastructure Drift Checks

#!/bin/bash
# drift-check.sh β€” runs in CI after every terraform apply
set -euo pipefail

echo "=== Asset Inventory Drift ==="
# Compare actual AWS resources against asset inventory
aws resourcegroupstaggingapi get-resources \
  --tag-filters Key=AssetID,Values=* \
  --query 'ResourceTagMappingList[].ResourceARN' \
  --output text | tr '\t' '\n' | sort > /tmp/actual-assets.txt

comm -23 /tmp/actual-assets.txt docs/asset-inventory.txt > /tmp/new-assets.txt
comm -13 /tmp/actual-assets.txt docs/asset-inventory.txt > /tmp/removed-assets.txt

if [ -s /tmp/new-assets.txt ]; then
  echo "NEW ASSETS DETECTED β€” update threat model:"
  cat /tmp/new-assets.txt
  exit 1
fi

echo "=== Control Implementation Drift ==="
# Verify C-001: parameterized query enforcement
if ! grep -r "semgrep.*sql" .gitlab/ci/lint.yml; then
  echo "C-001 MISSING: SQL injection lint rule not in pipeline"
  exit 1
fi

# Verify C-005: hardware key enforcement
USERS_WITHOUT_KEYS=$(aws iam get-credential-report --query 'Content' --output text | base64 -d | awk -F, '$4=="false" && $8=="true" {print $1}')
if [ -n "$USERS_WITHOUT_KEYS" ]; then
  echo "C-005 DRIFT: Users without MFA keys: $USERS_WITHOUT_KEYS"
  exit 1
fi

# Verify C-007: backup restore test completed in last 90 days
LAST_RESTORE=$(aws s3 ls s3://backup-restore-logs/ | tail -1 | awk '{print $1}')
DAYS_SINCE=$(( ($(date +%s) - $(date -d "$LAST_RESTORE" +%s)) / 86400 ))
if [ $DAYS_SINCE -gt 90 ]; then
  echo "C-007 STALE: Last backup restore test was $DAYS_SINCE days ago"
  exit 1
fi

echo "All drift checks passed."

4.2 Organizational Drift Checks

Technical controls drift. So do human processes.

  • Ownership changes: When an asset owner leaves, their assets become orphaned. Quarterly: cross-reference asset owners against current team roster.
  • Dependency changes: New SaaS tools added without security review. Monthly: diff approved vendor list against expense reports and OAuth grants.
  • Incident feedback loop: Every post-incident review must answer: "Did our threat model predict this? If not, what capability or asset was missing?" Update the model within 5 business days.

5. Identity Questions

The model lives in Git alongside infrastructure code. Changes follow the same review process.

5.1 Repository Structure

threat-model/
β”œβ”€β”€ assets/
β”‚   β”œβ”€β”€ inventory.yaml          # Machine-readable asset list
β”‚   └── classification.md       # Human-readable definitions
β”œβ”€β”€ adversaries/
β”‚   β”œβ”€β”€ capability-tiers.md     # This document's Section 2
β”‚   └── attck-mapping.yaml      # Technique-to-tier mapping
β”œβ”€β”€ controls/
β”‚   β”œβ”€β”€ catalog.yaml            # Machine-readable control list
β”‚   └── traceability.csv        # Control↔Threat↔Asset matrix
β”œβ”€β”€ drift/
β”‚   β”œβ”€β”€ drift-check.sh          # Automated checks (Section 4.1)
β”‚   └── organizational-checks.md # Manual checks (Section 4.2)
└── reviews/
    β”œβ”€β”€ 2026-Q1-review.md       # Quarterly review template
    └── incident-feedback/      # Per-incident model updates

5.2 Quarterly Review Template

# Threat Model Review β€” 2026-Q1

## Assets Added/Removed
- ADDED: A-013 (Grafana Loki log aggregation) β€” Tier 2 exposure via ingest API
- REMOVED: A-004 (Legacy Jenkins) β€” decommissioned March 15

## Capability Updates
- Tier 2: Added T1608.001 (Stage Capabilities: Upload Malware) β€” observed in supply chain incident INC-2026-003
- Tier 3: No changes

## Control Changes
- C-008 RETIRED: Dependency scanning moved to GitLab SAST (covers same threat)
- C-010 ADDED: Loki query rate limiting + tenant isolation (mitigates A-013 log injection)

## Drift Incidents
- 2026-02-14: drift-check.sh caught untagged RDS instance (A-014) β€” tagged and modeled same day
- 2026-03-01: Organizational check found 3 users without hardware keys β€” enrolled within 24h

## Incident Feedback
- INC-2026-003 (supply chain): Model predicted Tier 2 supply chain but missed malicious runner registration vector. Added T1608.001 and C-011 (runner registration approval).

## Open Questions
- Should we model Tier 3 firmware implants for our Dell PowerEdge fleet? (Cost: $2k/quarter for verification)
- Cloudflare BGP hijack monitoring: build vs. buy?

6. Verification Checklist: Model Health Indicators

Before trusting the model, verify these points:

  • Asset inventory completeness: Every resource in cloud provider tagged with AssetID; zero untagged resources.
  • Traceability closure: Zero threats without controls; zero controls without threats.
  • Drift check passing: Automated drift-check.sh exits 0 in CI for 30 consecutive days.
  • Quarterly review completed: Review document merged within 10 business days of quarter end.
  • Incident feedback loop: Every incident since last review has a model update linked in incident-feedback/.
  • Owner accountability: Every asset and control has a current team member as owner.
  • Capability calibration: Tier definitions reviewed against observed incidents and sector threat intel.

The difference between a threat model that guides decisions and one that decorates a wiki is whether it fails loudly when reality diverges. Our drift-check.sh has caught 12 infrastructure changes that would have gone unmodeled β€” an untagged RDS instance, a security group opened to 0.0.0.0/0, a new IAM user without hardware MFA. Each catch is a validation that the process works.

Sources & References


Threat modeling processes are validated against infrastructure before publication. This model reflects our actual production environment as of September 2026. Adapt asset classifications, capability tiers, and control mappings to your specific context.