Don't count vulnerabilities. Predict their blast radius.
Evidence: docs/EVIDENCE.md
Case study: BlastGuard · Andrey Lesnikov
- Traces CVE → package → image → workload → exposure into an explainable ALLOW / WARN / QUARANTINE / BLOCK decision
- Combines SBOM, vuln scanners, runtime reachability, and VEX-oriented inputs into a decision layer
- Supports what-if rehearsal and before/after remediation proof (
make demo,make rehearse,make prove)
- CVE dashboards count findings; operators need reachability and exposure to prioritize
- Remediation should prove the critical path is closed, not only that a ticket was filed
make demo
make rehearse
make proveflowchart LR
Ingest[internal/ingest SBOM/vuln] --> Graph[internal/graph]
Cluster[internal/cluster runtime] --> Graph
Graph --> Decide[internal/decide]
Decide --> CLI[cmd/blastguard]
CLI --> Out[ALLOW / WARN / QUARANTINE / BLOCK]
CI[.github/workflows/ci.yml] --> Rel[release.yml]
Ordinary pipeline:
CVE → scan → 147 findings → dashboard → ticket
BlastGuard:
CVE → Dependency Graph → Runtime Reachability → Exposure → Criticality → Decision → GitOps
Example:
CVE-2026-1234
│
▼
openssl
│
├── payment-api:v41 → prod/payment-api → internet exposed
├── report-worker:v4.2 → staging only
└── ml-builder:v2.1 → not running
┌──────────────────────────────────────────────┐
│ BLASTGUARD │
│ CVE-2026-1234 │
├──────────────────────────────────────────────┤
│ Affected artifacts 3 │
│ Running workloads 2 │
│ Internet exposed 1 │
│ Reachable 1 │
│ POLICY BLOCK │
│ Reason exploitable CVE +… │
│ FIX payment-api:v42 │
└──────────────────────────────────────────────┘
No fake Risk 83/100. Decisions are ALLOW / WARN / QUARANTINE / BLOCK with an explicit reason.
| Stage | What happens |
|---|---|
| Discover | SBOM (Syft/CycloneDX/SPDX), vulns (Grype/Trivy/OSV), optional cosign verify-attestation |
| Trace | CVE → package → image → workload → Service selector → Ingress; reachability ≠ exposure (docs) |
| Decide | Policy over CVSS/EPSS/VEX/runtime/exposure/criticality/fix availability |
| Enforce | CI gate via CLI exit codes (0 ALLOW, 1 WARN with --fail-on warn, 2 BLOCK/QUARANTINE). In-cluster admission (Kyverno / policy-controller) is out of tree — emit decisions into your policy engine |
| Verify | After patch: blast radius → 0 · REMEDIATION PROVED |
syft alpine:3.20 -o cyclonedx-json > alpine-sbom.json
grype alpine:3.20 -o json > alpine-vulns.json
blastguard ingest \
--sbom-cyclonedx alpine-sbom.json \
--vulns-grype alpine-vulns.json \
--output alpine-twin.jsonblastguard ingest --from-cluster --output cluster-twin.jsonblastguard ingest \
--sbom-cyclonedx alpine-sbom.json \
--vulns-grype alpine-vulns.json \
--policy config/decision-policy.json \
--trace CVE-2026-85091Output:
┌──────────────────────────────────────────────┐
│ BLASTGUARD │
│ CVE-2026-85091 │
├──────────────────────────────────────────────┤
│ Package zlib │
│ Severity HIGH │
│ CVSS 8.3 │
│ EPSS 0.00356 │
│ Running yes │
│ Internet exposed yes │
│ POLICY ● BLOCK │
│ Reason exploitable CVE on │
│ internet-exposed service │
└──────────────────────────────────────────────┘
make demo # trace CVE-2026-1234 → BLOCK on critical path
make rehearse # what-if for a CVE
make prove # before/after remediation evidence./bin/blastguard rehearse CVE-2026-1234
./bin/blastguard rehearse image ghcr.io/justrunme/payment-api:v42
./bin/blastguard demo CVE-2026-1234 --output json # exit 2 on BLOCK
./bin/blastguard demo CVE-2026-2345 --fail-on warn # exit 1 on WARNAsk before production changes: if this becomes exploitable, what is our blast radius?
Demo workload / fix-image defaults live in config/demo.json and fixtures/ — not in core matching logic.
Build-time truth + runtime truth on one graph:
Source → Build → Image → Package → SBOM → Deployment → Pod → Service → Ingress
+ CVE / VEX / Signature / Provenance / Runtime
SBOM says the package exists.
Kubernetes says it is running.
Ingress says it is exposed.
Reachability says the path is live.
VEX says which copies are not_affected.
147 findings become 1 exploitable path.
cmd/blastguard/ CLI
internal/decide/ Explainable policy engine
internal/graph/ Digital twin model
config/decision-policy.yaml BLOCK/WARN rules (no scores)
fixtures/demo-graph.json Offline twin for demos/CI
api/crds/ BlastRadius, SecurityDecision, SupplyChainPolicy
docs/assets/ Lifecycle visual
Integrations (not reimplemented here): Syft, Trivy/Grype, OSV, OpenVEX, Cosign/Sigstore, GUAC, OPA, OpenTelemetry, policy-controller/Kyverno.
BlastGuard owns: control plane, data model, decision engine, rehearsal, evidence.
| Project | Loop | Prove |
|---|---|---|
| Architecture Rehearsal | Predict → Rehearse → Gate → Verify | change safety |
| TwinOps | Declare → Compose → Detect → Reconcile | convergence |
| AI Infra Control Plane | Evaluate → Decide → Approve → Verify | governance |
| Kanister Recovery | Protect → Validate → Restore → Prove | recoverability |
| BlastGuard | Discover → Trace → Decide → Enforce → Verify | supply-chain safety |
Assumptions → evidence.
Terminal scan showing SBOM/provenance checks and a BLOCK policy decision for CVE-2026-1234, then before/after remediation prove:
make demo
make proveMeasured from CI (not estimates). Details: docs/results.md · docs/results.json.
| Scenario | Workload | Decision | Source run |
|---|---|---|---|
Fixture demo CVE-2026-1234 |
payment path | BLOCK | 38066443627 |
Kind e2e CVE-2026-93990 |
payment-api (exposed) |
BLOCK | same run (e2e-kind) |
Kind e2e CVE-2026-93990 |
internal-worker |
WARN | same run (e2e-kind) |
| Remediation prove | after fix | ALLOW / REMEDIATION PROVED | same run (ci) |
\n## License
Apache-2.0 © Andrey Lesnikov