Skip to content

Threat-model generator

pipeline_check --output threatmodel emits a self-contained Markdown threat-model document populated from the same scan output the JSON / HTML / SARIF reporters consume: findings, the component inventory, and any matched attack chains. Selecting the format auto-enables the inventory pass so a one-flag invocation produces a populated document.

Why STRIDE?

The OWASP CICD Top 10 mapping every rule already carries is the right vocabulary for a CI/CD audience but not the one auditors and threat modelers prefer. STRIDE has been the lingua franca of threat-modeling docs since Microsoft introduced it in 1999, and most compliance frameworks (SOC 2 CC, PCI 6.5, NIST SSDF PW.1) speak it natively.

The mapping is mechanical. There's nothing the reporter knows that isn't already in the rule registry. STRIDE classification is derived per-finding at report time, so re-policing is one table swap.

Quick start

# Single provider
pipeline_check --pipeline gitlab --gitlab-path .gitlab-ci.yml \
    --output threatmodel --output-file threatmodel.md

# Multi-provider (cross-provider chains land in the document too)
pipeline_check --pipelines github,oci --output threatmodel

Document layout

# Threat Model
  ## Scope                      providers in scope, scorer summary
  ## Trust boundaries           heuristics keyed off provider mix
  ## Assets                     the inventory, grouped by (provider, type)
  ## STRIDE analysis            failing findings bucketed
    ### S — Spoofing
    ### T — Tampering
    ### R — Repudiation
    ### I — Information Disclosure
    ### D — Denial of Service
    ### E — Elevation of Privilege
  ## Attack chains              optional, only when chains matched
  ## Implemented controls       passing-check counts per STRIDE bucket
  ## Risk register              top 25 failing findings, flat table
  ## Methodology                short footer pointing at the policy

Sample output

Running against the GitLab insecure fixture in this repo:

# Threat Model

_Generated by pipeline-check v1.0.4 on 2026-05-12 14:30 UTC._

## Scope

**Providers in scope:** gitlab (1)
**Region:** `us-east-1`

**Grade:** D
**Score:** 0/100
**Failed checks:** 35
**Passing controls:** 0

Severity breakdown:

- **CRITICAL:** 6 failed, 0 passing
- **HIGH:** 15 failed, 0 passing
- **MEDIUM:** 13 failed, 0 passing
- **LOW:** 1 failed, 0 passing

## Trust boundaries

- Pull-request author -> CI runner. Untrusted source-tree contents
  (PR-controlled YAML, scripts, dependency manifests) cross into a
  runner that holds CI secrets and (in privileged trigger modes)
  write-scope tokens.
- CI runner -> registry. Built artifacts (container images,
  packages, OCI manifests) cross from the runner into a registry
  whose downstream consumers trust the produced bytes.

## STRIDE analysis

### T -- Tampering

_Integrity of input, code, dependencies, or artifacts. Attacker
modifies what flows through the pipeline._

| Threat                                          | Severity | Affected | Mitigation                              |
|-------------------------------------------------|----------|----------|-----------------------------------------|
| `GL-010` Multi-project pipeline ingests upstream artifact unverified | CRITICAL | 1 | Add a verification step before...      |
| `GL-002` Script injection via untrusted commit/MR context | HIGH    | 1 | Read these values into intermediate... |
| ...                                             |          |          |                                         |

(The complete document is ~250 lines; see the sample output above for one rendered against the GitLab fixture.)

Mapping policy

The OWASP CICD Top 10 to STRIDE primary table:

OWASP STRIDE primary STRIDE secondary
CICD-SEC-1 Insufficient Flow Control T E
CICD-SEC-2 Inadequate IAM S E
CICD-SEC-3 Dependency Chain Abuse T
CICD-SEC-4 Poisoned Pipeline Execution T E
CICD-SEC-5 Insufficient PBAC E
CICD-SEC-6 Credential Hygiene I S
CICD-SEC-7 Insecure System Config E D
CICD-SEC-8 Ungoverned 3rd-Party T E
CICD-SEC-9 Improper Artifact Integrity T
CICD-SEC-10 Insufficient Logging R

CWE refinements that prepend to the head:

CWE Promotes to Why
CWE-200 I generic info exposure
CWE-522 I insufficiently protected creds
CWE-552 I files / dirs accessible externally
CWE-798 I hardcoded credentials
CWE-287 S improper authentication
CWE-290 S auth bypass by spoofing
CWE-345 T integrity check missing
CWE-78 T OS command injection
CWE-77 T generic command injection
CWE-494 T download of code without integrity
CWE-829 T functionality from untrusted sphere
CWE-1357 T reliance on uncontrolled component
CWE-400 D uncontrolled resource consumption
CWE-770 D alloc without limits
CWE-269 E improper privilege management
CWE-250 E execution with unnecessary privilege
CWE-778 R insufficient logging

Findings with no OWASP and no CWE tags default to Tampering, the most common CI/CD failure mode.

Use cases

  • SOC 2 / PCI evidence package: attach threatmodel.md next to the scan JSON. Auditors get a STRIDE-shaped narrative they can read directly; engineers get the JSON for tooling.
  • Architecture review: paste into a Confluence / Notion page as a starting draft. The Assets and trust-boundary sections give reviewers a concrete map of what's in scope.
  • Quarterly posture review: regenerate against the latest scan, diff against the prior quarter to see which STRIDE buckets gained / lost open risks.

Why no rule changes

The reporter never mutates the rule registry. STRIDE classification is a pure function of each finding's existing OWASP and CWE tags, evaluated at report time. The two policy tables (_OWASP_TO_STRIDE and _CWE_PREPEND in pipeline_check/core/threatmodel_reporter.py) are the single point where the policy lives, so re-policing for a different STRIDE flavor (LINDDUN, PASTA categories) is one table swap, not a rule-by-rule re-tag.