The Scan Matrix
Last reviewed · content updated
AdvancedWhat you'll learn
~18 min- Design the tool × phase × environment × control matrix that governs every gate
- Layer custom organizational policy over scanners - pre-deploy rules plus runtime enforcement
- Write the whole thing down as an ADR with honest negatives
What it is: one governed table deciding which security scanners run at which stage, in which environment, blocking or warning, mapped to the controls they support.
What it buys: the answer to ‘what supports this control?’ from a table filter instead of archaeology; predictable behavior when a scanner is down mid-release; the control column Module 5’s evidence pipeline keys on.
What to fund: roughly an engineer-week for a mid-sized estate to build the matrix and wire each cell to a real CI surface, then review it as an architecture decision record - not a new tool.
From a gate to a system
Lesson 3.4 wired one scanner into one gate. Zoom out and the real questions appear: which tools run at which phases against which environments enforcing which controls — and who decided, and where is that written? Most estates answer with archaeology: days of someone reading pipeline YAML every time an auditor asks ‘what supports this?’ or a scanner goes down mid-release. The matrix makes both a thirty-second lookup. That time, and that audit exposure, is what leadership is buying. The reference estate answers with the best single artifact in its portfolio: a scan matrix, recorded as an architecture decision record. This lesson builds yours.
The matrix
Four dimensions, one table — abbreviated here to show the shape:
TOOL × PHASE × ENVIRONMENT × outcome (per control family)─────────────────────────────────────────────────────────────────────────secretscan pre-commit + all BLOCK always (IA-5) - no PR environment relaxes secretscheckov PR + deploy dev WARN on MED, BLOCK HIGH+ (CM-6)checkov PR + deploy prod BLOCK MED+ (CM-6, SC-7)custom OPA deploy dev filtered: HIGH/MED onlycustom OPA deploy prod ALL severities enforcedruntime continuous all Azure Policy: audit dev, policy deny prod (the always-on floor)SAST/SCA PR + nightly all BLOCK on known-exploited; WARN new criticals for 48h, then BLOCK (SI-2)Read the design rules out of it: block-vs-warn is decided per cell, not per tool (the same scanner blocks harder in prod — 3.4’s severity logic, now governed); every row carries its control IDs (the compliance question “what supports CM-6?” becomes a column filter — and Module 5’s evidence pipeline will consume exactly this mapping); phases are chosen for feedback speed (secrets at pre-commit because seconds matter; SCA nightly because supply-chain moves daily); and one dimension most matrices forget — graceful degradation is specified per cell: what happens when the scanner itself is down (fail the gate? proceed with a logged waiver?). The reference ADR decides that explicitly per tool: secret scanning fails closed always; advisory layers may proceed-with-waiver during outages. Deciding it in the document beats deciding it during the outage.
IA-5, CM-6, SC-7, SI-2 are controls from NIST SP 800-53, the catalog every federal system (and anything FedRAMP-authorized) is assessed against. If your auditors grade you on SOC 2, ISO 27001, or CIS Controls instead, keep the column and fill it with your catalog’s IDs — but pick one primary catalog, because Module 5’s evidence pipeline keys on this column. Two honesty rules: a scanner supports evidence for a control, it never satisfies one (an assessor reads “enforces CM-6” as a claim about the whole control, and a scanner tests one condition of it); and Azure Policy here is the Azure name for a runtime policy engine; GCP Organization Policy is the closest equivalent, while on AWS the deny-mode floor is Service Control Policies — AWS Config detects and remediates but cannot refuse a deployment. For leadership: this column is what turns “are we compliant with X?” from a two-week project into a filter on a table.
The custom layer: your rules, two enforcement points
Scanners know the world’s best practices; they don’t know your rules (“every resource carries a cost-center tag,” “no public IPs in the data subnets,” “storage in sovereign regions only”). The estate’s custom layer is textbook two-point enforcement:
PRE-DEPLOY OPA/Conftest over the Terraform plan JSON - ~135 vendored Rego rules in the estate; runs in the pipeline as another gated step (same JUnit/SARIF surfacing as 3.4). Environment-scoped EXCEPTION FILES per the matrix: dev's filter admits only HIGH/MED rules; prod enforces everything - same rule set, declared filters, no forked policy code.RUNTIME Azure Policy as the always-on floor - audit-mode in dev, deny-mode in prod - catching whatever arrives OUTSIDE the pipeline: the portal edit, the imported resource, the drift.Two points because they fail differently: the pipeline gate gives fast feedback but only sees what flows through pipelines; runtime policy sees everything but after the fact (or, in deny-mode, at deployment against your tenant — which 3.4’s preflight now surfaces at plan). The pair, plus the environment-scoped exceptions living in declared files rather than divergent rule copies, is what “two-layer policy-as-code” means when it’s real. (Cloud Modernization’s policy lesson taught this stack’s silhouette; this is its wiring diagram.)
Write it down: the ADR
The matrix’s natural home is an architecture decision record, and the reference estate’s ADR collection is worth imitating for form alone — Context, Decision, Consequences, with the consequence section containing honest negatives. The scan-matrix ADR’s own negatives, paraphrased: pipeline time increases measurably; environment-scoped exceptions require review discipline or they drift into permanent relaxation; two policy layers mean two places a rule can live, so the matrix must name which layer owns each rule class. An ADR whose negatives section is empty is advocacy, not a decision record — the honest costs are what make the next reader trust the recorded benefits. (You’ve now seen this honesty rule as the five-value grading scale in Zero Trust and the per-phase ROI table in Cloud Modernization: same virtue, decision-record edition.)
Produce our scan-matrix ADR: (1) the full matrix for our estate - tools(secretscan, checkov, our OPA layer, runtime policy, SAST/SCA), phases,environments, block/warn per cell, control IDs per row, and a graceful-degradation column deciding each tool's outage behavior; (2) the two-layercustom policy design - which rule classes live pre-deploy vs runtime, theper-environment exception file format (with the reason-string requirement);(3) ADR form: context, decision, consequences WITH at least three honestnegatives; (4) the implementation checklist mapping each matrix cell tothe template step (3.4) or policy assignment that enforces it - a cellwith no implementing artifact is flagged ASPIRATIONAL, not silentlycounted as covered.That last flag is this training’s recurring honesty device pointed at the matrix itself: the reference estate’s biggest wart (3.4 mentioned it) is scans that exist but default off — a matrix full of aspirational cells. The checklist makes the gap enumerable, which makes it burn-downable.
Writing the ADR, the team debates where the 'no public IPs in data subnets' rule should live: the OPA pre-deploy layer, runtime Azure Policy, or both. The matrix's design principles answer this — how?
Practice status — among mature regulated delivery programs, commercial and federal
(a few rows carry a more specific status - principle, canon, suspended - where one of the five would mislead)
| Practice | Status | Also called |
|---|---|---|
| scan matrix as an ADR | reference-shop | security-gate decision record |
| severity-gated blocking per cell | common baseline | policy-as-code gates |
| two enforcement points (pre-deploy + runtime) | common baseline deny-mode engines: Azure Policy / GCP Org Policy / AWS SCPs (AWS Config = detect and remediate) | — |
| control-ID column on every row | strong optional | control mapping - supports evidence, never satisfies |
| outage behavior per cell | strong optional | fail-open / fail-closed policy |
Scale: required | common baseline | strong optional | reference-shop (seen only at organizations that publish their own practice) | emerging
Key takeaway
Govern the gates as a system: one matrix deciding tool × phase × environment × outcome with control IDs on every row and outage behavior on every cell; custom rules layered pre-deploy (fast, filtered per environment by declared exception files) and at runtime (the always-on floor, owning the hard invariants); all of it recorded as an ADR whose negatives are honest and whose aspirational cells are flagged, not counted. Module 3 is complete — your infrastructure has a paved, pinned, gated road. Module 4 turns to the software riding on it.
LEADERSHIP DECISION approve one governed matrix (an ADR) as the record of which gates run where - and fund nothing that bypasses itPRACTITIONER ACTION build the matrix, wire every cell to a real CI surface, encode outage behavior per cellSUCCESS MEASURE time to answer 'what supports control X?' drops from days of archaeology to a table filter; zero silently- unwired stacks