All Mermaid validated in Mermaid Live Editor. n8n JSON is structurally valid and importable.
Type: n8n workflow (importable JSON) Purpose: The primary visual — the full gate harness from PR input through parallel multi-scanner orchestration, aggregation, risk scoring, and gate decision.
{
"name": "S08 — SDLC Gate Harness",
"nodes": [
{ "name": "PR Input", "type": "n8n-nodes-base.manualTrigger", "position": [200, 300], "notes": "PR diff + repo snapshot + dependency manifests + IaC files" },
{ "name": "Parallel Scanners", "type": "n8n-nodes-base.set", "position": [440, 300], "notes": "Fan-out to 4 scanner types simultaneously" },
{ "name": "SAST (Semgrep+CodeQL)", "type": "n8n-nodes-base.executeCommand", "position": [680, 150], "notes": "Code weaknesses. ~90s." },
{ "name": "SCA (Snyk+DepCheck)", "type": "n8n-nodes-base.executeCommand", "position": [680, 280], "notes": "Vulnerable dependencies. ~30s." },
{ "name": "Secrets (Gitleaks+Truffle)", "type": "n8n-nodes-base.executeCommand", "position": [680, 380], "notes": "Committed credentials. ~10s." },
{ "name": "IaC (Checkov+OPA)", "type": "n8n-nodes-base.executeCommand", "position": [680, 470], "notes": "Insecure infra declarations. ~15s." },
{ "name": "Aggregation + Dedup", "type": "n8n-nodes-base.set", "position": [920, 300], "notes": "Normalize to unified schema. Cross-scanner dedup via dedup_key. Severity reconciliation." },
{ "name": "Cross-Scanner Triage (LLM)", "type": "n8n-nodes-base.set", "position": [1160, 300], "notes": "Correlate related findings across scanners. Upgrade/downgrade severity. Token-budgeted." },
{ "name": "Risk Score + Trend", "type": "n8n-nodes-base.set", "position": [1400, 300], "notes": "Score from finding counts + build-history trend. Single number 0-100." },
{ "name": "Gate Decision", "type": "n8n-nodes-base.set", "position": [1640, 300], "notes": "Apply hard-gate/soft-gate matrix. Block, warn, or pass." },
{ "name": "Build History Store", "type": "n8n-nodes-base.set", "position": [1400, 470], "notes": "Finding counts per build. Trend detection over N builds." }
],
"connections": {
"PR Input": { "main": [[{ "node": "Parallel Scanners", "type": "main", "index": 0 }]] },
"Parallel Scanners": { "main": [[{ "node": "SAST (Semgrep+CodeQL)", "type": "main", "index": 0 }], [{ "node": "SCA (Snyk+DepCheck)", "type": "main", "index": 0 }], [{ "node": "Secrets (Gitleaks+Truffle)", "type": "main", "index": 0 }], [{ "node": "IaC (Checkov+OPA)", "type": "main", "index": 0 }]] },
"SAST (Semgrep+CodeQL)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"SCA (Snyk+DepCheck)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"Secrets (Gitleaks+Truffle)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"IaC (Checkov+OPA)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"Aggregation + Dedup": { "main": [[{ "node": "Cross-Scanner Triage (LLM)", "type": "main", "index": 0 }]] },
"Cross-Scanner Triage (LLM)": { "main": [[{ "node": "Risk Score + Trend", "type": "main", "index": 0 }]] },
"Build History Store": { "main": [[{ "node": "Risk Score + Trend", "type": "main", "index": 0 }]] },
"Risk Score + Trend": { "main": [[{ "node": "Gate Decision", "type": "main", "index": 0 }]] }
}
}
Reading the diagram: A PR enters and fans out to four scanner types in parallel — SAST, SCA, secrets, IaC. Parallel execution bounds wall-clock time to the slowest scanner (~90s), not the sum. Results aggregate into a unified schema with cross-scanner dedup. The LLM triage layer correlates related findings across scanners (e.g. an SCA vulnerability upgraded because the vulnerable path is reachable per SAST), under a token budget prioritizing high-severity findings. The risk score combines finding counts with the build-history trend into a single number. The gate decision applies the hard-gate/soft-gate matrix: block, warn, or pass. The build history store holds finding counts per build for trend detection.
Type: Mermaid (flowchart) Purpose: Shows the structural difference between review-time checks (old) and creation-time guardrails (new), and why the shift is forced by AI coding agents.
flowchart LR
subgraph OLD["Review-time check (old)"]
C1["Commit"] --> SCAN["Scan overnight"]
SCAN --> DASH["Dashboard"]
DASH --> TICKET["Ticket, days later"]
TICKET --> PROD["Already in production"]
end
subgraph NEW["Creation-time guardrail (new)"]
C2["Developer creates"] --> GUARD["Guardrail runs<br/>synchronously"]
GUARD -->|"insecure"| BLOCK["Block + inline feedback<br/>(seconds)"]
GUARD -->|"secure"| MERGE["Proceed"]
end
AGENT["AI coding agent<br/>creates code/deps/IaC<br/>in seconds"]
AGENT -.->|"too fast for review-time"| OLD
AGENT -.->|"defense must match speed"| NEW
style OLD fill:#2a0d0d,stroke:#a00000,color:#f08080
style PROD fill:#2a0d0d,stroke:#a00000,color:#f08080
style NEW fill:#0d2818,stroke:#1e8449,color:#82e0aa
style BLOCK fill:#14141f,stroke:#5eead4,color:#5eead4
style AGENT fill:#2a1810,stroke:#a04000,color:#f0a868
Reading the diagram: The review-time model (left) commits, scans overnight, files a dashboard finding, opens a ticket days later — by which point the code is in production. Feedback latency is hours to days. This was tolerable when developers wrote code at human pace. AI coding agents (top) create code, dependencies, and infrastructure in seconds — far faster than review-time checks can process. The defense must match the speed: creation-time guardrails (right) run synchronously in the developer's (or agent's) path, blocking insecure artifacts with inline feedback in seconds. The shift is structural — the gate moves from after to before.
Type: Mermaid (flowchart) Purpose: Shows the decision logic for when to block vs warn, per scan type and finding characteristic.
flowchart TD
FIND["Finding (normalized)"]
TYPE{"Scan type +<br/>characteristic?"}
SECRET["Secrets<br/>(validated, high conf)"]
SAST_C["SAST critical<br/>(high conf)"]
SCA_KEV["SCA: CISA KEV<br/>or EPSS > 0.7"]
IAC_PUB["IaC: public exposure<br/>0.0.0.0/0, public S3"]
SAST_ML["SAST medium/low"]
SCA_LOW["SCA low EPSS,<br/>fix available"]
LIC["License<br/>unknown/copyleft"]
FIND --> TYPE
TYPE --> SECRET
TYPE --> SAST_C
TYPE --> SCA_KEV
TYPE --> IAC_PUB
TYPE --> SAST_ML
TYPE --> SCA_LOW
TYPE --> LIC
SECRET --> HARD["HARD BLOCK<br/>cannot merge"]
SAST_C --> HARD
SCA_KEV --> HARD
IAC_PUB --> HARD
SAST_ML --> SOFT["SOFT WARN<br/>proceed, surface for review"]
SCA_LOW --> SOFT
LIC --> SOFT
style HARD fill:#2a0d0d,stroke:#a00000,color:#f08080
style SOFT fill:#2a1810,stroke:#a04000,color:#f0a868
style TYPE fill:#14141f,stroke:#5eead4,color:#5eead4
Reading the diagram: The gate decision depends on scan type and finding characteristic. Hard block: validated secrets (active breach path), SAST critical with high confidence (exploit path ships), SCA matching CISA KEV or EPSS above 0.7 (known/likely exploited), IaC public exposure (immediate risk). Soft warn: SAST medium/low (surface for review, don't block), SCA with low EPSS and a fix available (triage and schedule), license issues (legal review). The matrix is the calibration — over-blocking trains developers to override without reading; under-blocking ships vulnerabilities. Block the immediately dangerous; warn the rest.
Type: Mermaid (flowchart) Purpose: Shows how build history feeds risk scoring and trend detection, producing a single number that gates PR approval.
flowchart LR
HIST["Build history<br/>(finding counts per build,<br/>last N builds)"]
CURRENT["Current PR<br/>(new findings introduced)"]
NET["Net direction<br/>(new introduced −<br/>existing fixed)"]
TREND{"Trend over<br/>last 10 builds?"}
SCORE["Risk score 0-100<br/>severity-weighted<br/>+ trend modifier"]
GATE1["Score < 30:<br/>pass"]
GATE2["Score 30-60:<br/>requires security<br/>reviewer approval"]
GATE3["Score > 60:<br/>BLOCKED until<br/>findings resolved"]
HIST --> NET
CURRENT --> NET
NET --> SCORE
HIST --> TREND
TREND -->|"rising → ×1.2"| SCORE
SCORE --> GATE1
SCORE --> GATE2
SCORE --> GATE3
style SCORE fill:#14141f,stroke:#5eead4,color:#5eead4
style GATE3 fill:#2a0d0d,stroke:#a00000,color:#f08080
style GATE2 fill:#2a1810,stroke:#a04000,color:#f0a868
style GATE1 fill:#0d2818,stroke:#1e8449,color:#82e0aa
Reading the diagram: The risk score combines the current PR's new findings (severity-weighted: critical=25, high=10, medium=3, low=1) with net direction (new introduced minus existing fixed) and a trend modifier (rising trend over 10 builds multiplies the score by 1.2). The single number gates approval: below 30, pass; 30-60, requires a security reviewer's explicit approval; above 60, blocked until findings are resolved. The thresholds are policy; the score is computed, consistent, and auditable — not a gut call. Trend matters: a PR that introduces 5 criticals but fixes 7 is net-positive; a PR that introduces 5 and fixes 0 is net-negative.
Type: Mermaid (flowchart) Purpose: Shows the pipeline from CVE feed ingestion to a prioritized remediation queue, using EPSS and CISA KEV.
flowchart TD
NVD["NVD CVE feed<br/>(thousands/year)"]
MATCH["Dependency matching<br/>(vs package.json,<br/>requirements.txt, go.mod)"]
EPSS["EPSS scoring<br/>(probability of exploit<br/>in 30 days, 0-1)"]
REACH{"Environment<br/>relevance?<br/>(vulnerable path reachable?)"}
QUEUE["Prioritized remediation queue"]
KEV["CISA KEV match"]
HIGH["EPSS > 0.7,<br/>reachable"]
VERIFY["EPSS > 0.7,<br/>not confirmed"]
BACKLOG["High CVSS,<br/>low EPSS"]
ACCEPT["Low CVSS,<br/>low EPSS"]
NVD --> MATCH --> EPSS --> REACH --> QUEUE
QUEUE --> KEV
QUEUE --> HIGH
QUEUE --> VERIFY
QUEUE --> BACKLOG
QUEUE --> ACCEPT
KEV_FIX["Fix: HOURS<br/>(active exploitation)"]
HIGH_FIX["Fix: DAYS"]
VERIFY_FIX["Verify reachability,<br/>then fix"]
BACKLOG_FIX["Schedule in backlog"]
ACCEPT_FIX["Accept risk / batch-fix"]
KEV --> KEV_FIX
HIGH --> HIGH_FIX
VERIFY --> VERIFY_FIX
BACKLOG --> BACKLOG_FIX
ACCEPT --> ACCEPT_FIX
style NVD fill:#2a1810,stroke:#a04000,color:#f0a868
style EPSS fill:#14141f,stroke:#5eead4,color:#5eead4
style KEV fill:#2a0d0d,stroke:#a00000,color:#f08080
style KEV_FIX fill:#2a0d0d,stroke:#a00000,color:#f08080
style HIGH fill:#2a1810,stroke:#a04000,color:#f0a868
style ACCEPT fill:#0d2818,stroke:#1e8449,color:#82e0aa
Reading the diagram: The NVD feed publishes thousands of CVEs per year. Dependency matching narrows to CVEs that affect the application's actual dependencies. EPSS scoring adds exploitation probability. Environment-relevance filtering removes CVEs in unreachable code paths. The output is a prioritized queue: CISA KEV matches first (confirmed active exploitation — fix in hours), then EPSS above 0.7 and reachable (fix in days), then EPSS above 0.7 but not confirmed reachable (verify first), then high-CVSS/low-EPSS (backlog), then low-CVSS/low-EPSS (accept risk or batch-fix). The pipeline turns hundreds of raw CVEs into three things to fix this week. EPSS and KEV are the signals that prevent CVSS-only prioritization — a CVSS 9.8 with EPSS 0.01 is less urgent than a CVSS 7.5 with EPSS 0.8.
# Diagrams — Module S08: SDLC Gate Harnesses
> All Mermaid validated in Mermaid Live Editor. n8n JSON is structurally valid and importable.
---
## Diagram 1 — The SDLC Gate Harness (n8n)
**Type**: n8n workflow (importable JSON)
**Purpose**: The primary visual — the full gate harness from PR input through parallel multi-scanner orchestration, aggregation, risk scoring, and gate decision.
```json
{
"name": "S08 — SDLC Gate Harness",
"nodes": [
{ "name": "PR Input", "type": "n8n-nodes-base.manualTrigger", "position": [200, 300], "notes": "PR diff + repo snapshot + dependency manifests + IaC files" },
{ "name": "Parallel Scanners", "type": "n8n-nodes-base.set", "position": [440, 300], "notes": "Fan-out to 4 scanner types simultaneously" },
{ "name": "SAST (Semgrep+CodeQL)", "type": "n8n-nodes-base.executeCommand", "position": [680, 150], "notes": "Code weaknesses. ~90s." },
{ "name": "SCA (Snyk+DepCheck)", "type": "n8n-nodes-base.executeCommand", "position": [680, 280], "notes": "Vulnerable dependencies. ~30s." },
{ "name": "Secrets (Gitleaks+Truffle)", "type": "n8n-nodes-base.executeCommand", "position": [680, 380], "notes": "Committed credentials. ~10s." },
{ "name": "IaC (Checkov+OPA)", "type": "n8n-nodes-base.executeCommand", "position": [680, 470], "notes": "Insecure infra declarations. ~15s." },
{ "name": "Aggregation + Dedup", "type": "n8n-nodes-base.set", "position": [920, 300], "notes": "Normalize to unified schema. Cross-scanner dedup via dedup_key. Severity reconciliation." },
{ "name": "Cross-Scanner Triage (LLM)", "type": "n8n-nodes-base.set", "position": [1160, 300], "notes": "Correlate related findings across scanners. Upgrade/downgrade severity. Token-budgeted." },
{ "name": "Risk Score + Trend", "type": "n8n-nodes-base.set", "position": [1400, 300], "notes": "Score from finding counts + build-history trend. Single number 0-100." },
{ "name": "Gate Decision", "type": "n8n-nodes-base.set", "position": [1640, 300], "notes": "Apply hard-gate/soft-gate matrix. Block, warn, or pass." },
{ "name": "Build History Store", "type": "n8n-nodes-base.set", "position": [1400, 470], "notes": "Finding counts per build. Trend detection over N builds." }
],
"connections": {
"PR Input": { "main": [[{ "node": "Parallel Scanners", "type": "main", "index": 0 }]] },
"Parallel Scanners": { "main": [[{ "node": "SAST (Semgrep+CodeQL)", "type": "main", "index": 0 }], [{ "node": "SCA (Snyk+DepCheck)", "type": "main", "index": 0 }], [{ "node": "Secrets (Gitleaks+Truffle)", "type": "main", "index": 0 }], [{ "node": "IaC (Checkov+OPA)", "type": "main", "index": 0 }]] },
"SAST (Semgrep+CodeQL)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"SCA (Snyk+DepCheck)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"Secrets (Gitleaks+Truffle)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"IaC (Checkov+OPA)": { "main": [[{ "node": "Aggregation + Dedup", "type": "main", "index": 0 }]] },
"Aggregation + Dedup": { "main": [[{ "node": "Cross-Scanner Triage (LLM)", "type": "main", "index": 0 }]] },
"Cross-Scanner Triage (LLM)": { "main": [[{ "node": "Risk Score + Trend", "type": "main", "index": 0 }]] },
"Build History Store": { "main": [[{ "node": "Risk Score + Trend", "type": "main", "index": 0 }]] },
"Risk Score + Trend": { "main": [[{ "node": "Gate Decision", "type": "main", "index": 0 }]] }
}
}
```
**Reading the diagram**: A PR enters and fans out to four scanner types in parallel — SAST, SCA, secrets, IaC. Parallel execution bounds wall-clock time to the slowest scanner (~90s), not the sum. Results aggregate into a unified schema with cross-scanner dedup. The LLM triage layer correlates related findings across scanners (e.g. an SCA vulnerability upgraded because the vulnerable path is reachable per SAST), under a token budget prioritizing high-severity findings. The risk score combines finding counts with the build-history trend into a single number. The gate decision applies the hard-gate/soft-gate matrix: block, warn, or pass. The build history store holds finding counts per build for trend detection.
---
## Diagram 2 — The Control Plane Shift
**Type**: Mermaid (flowchart)
**Purpose**: Shows the structural difference between review-time checks (old) and creation-time guardrails (new), and why the shift is forced by AI coding agents.
```mermaid
flowchart LR
subgraph OLD["Review-time check (old)"]
C1["Commit"] --> SCAN["Scan overnight"]
SCAN --> DASH["Dashboard"]
DASH --> TICKET["Ticket, days later"]
TICKET --> PROD["Already in production"]
end
subgraph NEW["Creation-time guardrail (new)"]
C2["Developer creates"] --> GUARD["Guardrail runs<br/>synchronously"]
GUARD -->|"insecure"| BLOCK["Block + inline feedback<br/>(seconds)"]
GUARD -->|"secure"| MERGE["Proceed"]
end
AGENT["AI coding agent<br/>creates code/deps/IaC<br/>in seconds"]
AGENT -.->|"too fast for review-time"| OLD
AGENT -.->|"defense must match speed"| NEW
style OLD fill:#2a0d0d,stroke:#a00000,color:#f08080
style PROD fill:#2a0d0d,stroke:#a00000,color:#f08080
style NEW fill:#0d2818,stroke:#1e8449,color:#82e0aa
style BLOCK fill:#14141f,stroke:#5eead4,color:#5eead4
style AGENT fill:#2a1810,stroke:#a04000,color:#f0a868
```
**Reading the diagram**: The review-time model (left) commits, scans overnight, files a dashboard finding, opens a ticket days later — by which point the code is in production. Feedback latency is hours to days. This was tolerable when developers wrote code at human pace. AI coding agents (top) create code, dependencies, and infrastructure in seconds — far faster than review-time checks can process. The defense must match the speed: creation-time guardrails (right) run synchronously in the developer's (or agent's) path, blocking insecure artifacts with inline feedback in seconds. The shift is structural — the gate moves from after to before.
---
## Diagram 3 — Hard Gate vs Soft Gate Decision Matrix
**Type**: Mermaid (flowchart)
**Purpose**: Shows the decision logic for when to block vs warn, per scan type and finding characteristic.
```mermaid
flowchart TD
FIND["Finding (normalized)"]
TYPE{"Scan type +<br/>characteristic?"}
SECRET["Secrets<br/>(validated, high conf)"]
SAST_C["SAST critical<br/>(high conf)"]
SCA_KEV["SCA: CISA KEV<br/>or EPSS > 0.7"]
IAC_PUB["IaC: public exposure<br/>0.0.0.0/0, public S3"]
SAST_ML["SAST medium/low"]
SCA_LOW["SCA low EPSS,<br/>fix available"]
LIC["License<br/>unknown/copyleft"]
FIND --> TYPE
TYPE --> SECRET
TYPE --> SAST_C
TYPE --> SCA_KEV
TYPE --> IAC_PUB
TYPE --> SAST_ML
TYPE --> SCA_LOW
TYPE --> LIC
SECRET --> HARD["HARD BLOCK<br/>cannot merge"]
SAST_C --> HARD
SCA_KEV --> HARD
IAC_PUB --> HARD
SAST_ML --> SOFT["SOFT WARN<br/>proceed, surface for review"]
SCA_LOW --> SOFT
LIC --> SOFT
style HARD fill:#2a0d0d,stroke:#a00000,color:#f08080
style SOFT fill:#2a1810,stroke:#a04000,color:#f0a868
style TYPE fill:#14141f,stroke:#5eead4,color:#5eead4
```
**Reading the diagram**: The gate decision depends on scan type and finding characteristic. Hard block: validated secrets (active breach path), SAST critical with high confidence (exploit path ships), SCA matching CISA KEV or EPSS above 0.7 (known/likely exploited), IaC public exposure (immediate risk). Soft warn: SAST medium/low (surface for review, don't block), SCA with low EPSS and a fix available (triage and schedule), license issues (legal review). The matrix is the calibration — over-blocking trains developers to override without reading; under-blocking ships vulnerabilities. Block the immediately dangerous; warn the rest.
---
## Diagram 4 — Risk Score and Trend
**Type**: Mermaid (flowchart)
**Purpose**: Shows how build history feeds risk scoring and trend detection, producing a single number that gates PR approval.
```mermaid
flowchart LR
HIST["Build history<br/>(finding counts per build,<br/>last N builds)"]
CURRENT["Current PR<br/>(new findings introduced)"]
NET["Net direction<br/>(new introduced −<br/>existing fixed)"]
TREND{"Trend over<br/>last 10 builds?"}
SCORE["Risk score 0-100<br/>severity-weighted<br/>+ trend modifier"]
GATE1["Score < 30:<br/>pass"]
GATE2["Score 30-60:<br/>requires security<br/>reviewer approval"]
GATE3["Score > 60:<br/>BLOCKED until<br/>findings resolved"]
HIST --> NET
CURRENT --> NET
NET --> SCORE
HIST --> TREND
TREND -->|"rising → ×1.2"| SCORE
SCORE --> GATE1
SCORE --> GATE2
SCORE --> GATE3
style SCORE fill:#14141f,stroke:#5eead4,color:#5eead4
style GATE3 fill:#2a0d0d,stroke:#a00000,color:#f08080
style GATE2 fill:#2a1810,stroke:#a04000,color:#f0a868
style GATE1 fill:#0d2818,stroke:#1e8449,color:#82e0aa
```
**Reading the diagram**: The risk score combines the current PR's new findings (severity-weighted: critical=25, high=10, medium=3, low=1) with net direction (new introduced minus existing fixed) and a trend modifier (rising trend over 10 builds multiplies the score by 1.2). The single number gates approval: below 30, pass; 30-60, requires a security reviewer's explicit approval; above 60, blocked until findings are resolved. The thresholds are policy; the score is computed, consistent, and auditable — not a gut call. Trend matters: a PR that introduces 5 criticals but fixes 7 is net-positive; a PR that introduces 5 and fixes 0 is net-negative.
---
## Diagram 5 — Vulnerability Triage at Scale (CVE → priority queue)
**Type**: Mermaid (flowchart)
**Purpose**: Shows the pipeline from CVE feed ingestion to a prioritized remediation queue, using EPSS and CISA KEV.
```mermaid
flowchart TD
NVD["NVD CVE feed<br/>(thousands/year)"]
MATCH["Dependency matching<br/>(vs package.json,<br/>requirements.txt, go.mod)"]
EPSS["EPSS scoring<br/>(probability of exploit<br/>in 30 days, 0-1)"]
REACH{"Environment<br/>relevance?<br/>(vulnerable path reachable?)"}
QUEUE["Prioritized remediation queue"]
KEV["CISA KEV match"]
HIGH["EPSS > 0.7,<br/>reachable"]
VERIFY["EPSS > 0.7,<br/>not confirmed"]
BACKLOG["High CVSS,<br/>low EPSS"]
ACCEPT["Low CVSS,<br/>low EPSS"]
NVD --> MATCH --> EPSS --> REACH --> QUEUE
QUEUE --> KEV
QUEUE --> HIGH
QUEUE --> VERIFY
QUEUE --> BACKLOG
QUEUE --> ACCEPT
KEV_FIX["Fix: HOURS<br/>(active exploitation)"]
HIGH_FIX["Fix: DAYS"]
VERIFY_FIX["Verify reachability,<br/>then fix"]
BACKLOG_FIX["Schedule in backlog"]
ACCEPT_FIX["Accept risk / batch-fix"]
KEV --> KEV_FIX
HIGH --> HIGH_FIX
VERIFY --> VERIFY_FIX
BACKLOG --> BACKLOG_FIX
ACCEPT --> ACCEPT_FIX
style NVD fill:#2a1810,stroke:#a04000,color:#f0a868
style EPSS fill:#14141f,stroke:#5eead4,color:#5eead4
style KEV fill:#2a0d0d,stroke:#a00000,color:#f08080
style KEV_FIX fill:#2a0d0d,stroke:#a00000,color:#f08080
style HIGH fill:#2a1810,stroke:#a04000,color:#f0a868
style ACCEPT fill:#0d2818,stroke:#1e8449,color:#82e0aa
```
**Reading the diagram**: The NVD feed publishes thousands of CVEs per year. Dependency matching narrows to CVEs that affect the application's actual dependencies. EPSS scoring adds exploitation probability. Environment-relevance filtering removes CVEs in unreachable code paths. The output is a prioritized queue: CISA KEV matches first (confirmed active exploitation — fix in hours), then EPSS above 0.7 and reachable (fix in days), then EPSS above 0.7 but not confirmed reachable (verify first), then high-CVSS/low-EPSS (backlog), then low-CVSS/low-EPSS (accept risk or batch-fix). The pipeline turns hundreds of raw CVEs into three things to fix this week. EPSS and KEV are the signals that prevent CVSS-only prioritization — a CVSS 9.8 with EPSS 0.01 is less urgent than a CVSS 7.5 with EPSS 0.8.