The programSecurity by DesignPart 3 of 5
  1. Frictionless SDLC & buy-inPublished
  2. Threat modelling as a design ritualPublished
  3. 3Wiring threat models into DevSecOpsYou're reading this
  4. Cloud threat modelling & controlsComing Oct 2026
  5. Govern, measure & sustainComing Nov 2026
DevSecOps9 Sep 202616 min read

Security by Design · Part 3 — Wiring Threat Models into DevSecOps: From Whiteboard to Failing Build

Where design becomes enforcement: turn the ranked threats from Part 2 into concrete pipeline checks, tests and guardrails, so a mitigation decided on a whiteboard becomes a control that fails the build if it regresses.

This is Part 3 of the Security by Design program — the seam where the design activity becomes running software. Part 1 built the culture; Part 2 made threat modelling a continuous ritual that produces a ranked, recorded list of threats, each with a chosen mitigation, an owner, and an intended verification. That list is a set of promises. This part is about keeping them — turning "we will verify the tenant claim server-side" from a line in a YAML file into a test that fails the build the day someone removes the check. Without this step, threat modelling degrades into documentation: a team identifies a threat, agrees a mitigation, ships it, and then, three refactors later, the mitigation quietly disappears and nobody notices until it is an incident. The pipeline is what makes a mitigation durable. It is also what makes the whole program honest: a threat marked "mitigated" in Part 2 is only truly mitigated when something automated checks, on every change, that the mitigation is still there.

This is the point where Security by Design meets the DevSecOps Program, and the relationship is symbiotic. The DevSecOps Program builds the pipeline machinery — SAST, SCA, secret scanning, IaC scanning, the CI/CD integration. This program supplies the priorities: a generic pipeline checks for generic problems, but a threat-model-driven pipeline checks for your system's actual risks, weighting and targeting its controls at the threats you found rather than spraying effort evenly. Threat modelling without a pipeline is advice; a pipeline without threat modelling is undirected. Together they are a system that finds your real risks and then guarantees the fixes stay fixed.

Scope of this phase

Every part of this program is scope-bounded so you know exactly when you are done.

  • In scope: the traceability from a recorded threat to a specific control; the four kinds of control the pipeline provides (targeted tests, configured scanners, policy-as-code guardrails, and runtime checks) and how to choose between them per threat; how to keep these controls non-blocking (Part 1) while still guaranteeing durability; and how threat-model coverage drives what the pipeline enforces.
  • Explicitly out of scope: the base mechanics of building the pipeline stages themselves — SAST/SCA/secret-scanning tool selection and CI wiring — which the DevSecOps Program covers in depth and which this part uses rather than rebuilds; cloud-specific controls (Part 4); and organization-wide measurement (Part 5).
  • Definition of done: the exit checklist at the end of this part. When every box is ticked, every high-priority threat from your models has a corresponding, durable control in the pipeline, and a regressed mitigation cannot ship silently.

From threat to durable control

Targeted test

Abuse-case tests for app-logic threats

Configured scanner

Tuned SAST / SCA / secret scanning

Policy-as-code

Guardrails for config invariants

Runtime check

Detections for live-only threats

Every high-priority threat maps to a control that fails the build if the mitigation regresses.

The core idea is traceability: every high-priority threat should map to a control, and every control should trace back to a threat (or a baseline policy). That two-way link is what makes the security program auditable and proportionate — you can show why each control exists, and you can see which threats still lack one. Build the mapping first; the specific control types are just the menu you choose from.

From threat to control: the mapping

The artifact from Part 2 was a threats.yaml entry with a verified_by field naming the intended verification. Part 3 makes that field real. For each high-priority threat, you choose the control type that best guarantees the mitigation, wire it into the pipeline, and link it back to the threat ID so the traceability is explicit. The result is that your threat register and your pipeline are two views of the same truth: the register says what should be controlled, the pipeline proves it is.

There are four kinds of control, and choosing the right one per threat is the skill of this part:

Control typeGuaranteesBest for
Targeted testA specific mitigation behaves correctly and keeps behavingApplication-logic threats: authz, tenant isolation, input validation
Configured scannerA class of weakness is absent across the codebaseBroad threats: injection, known-vulnerable dependencies, secrets
Policy-as-code guardrailAn infrastructure/config invariant holdsDeployment threats: public storage, open ports, missing encryption
Runtime checkA property holds in the running systemThreats only observable live: anomalous behaviour, drift

Control type 1 — Targeted tests

The most precise control for an application-logic threat is a test written specifically to verify its mitigation. Threat TM-014 from Part 2 — "attacker forges a token to impersonate another tenant" — is mitigated by validating the tenant claim server-side; the durable guarantee is a test that asserts a request carrying tenant A's identity cannot read tenant B's data. That test lives with the code, runs on every change, and fails the build the moment the isolation check is weakened. It is precise, it doubles as documentation of the threat, and — because you only write these for the threats that ranked high — it is proportionate.

This is where threat modelling most obviously earns its keep in the pipeline: a team without a threat model writes the tests it thinks of; a team with one writes tests for the specific abuse cases it found. The verified_by field closes the loop — it names the test, so anyone reviewing the threat register can confirm the mitigation is actually enforced, and Part 5 can measure how many high-priority threats have a live verification. A minimal example of the kind of abuse-case test a threat drives:

test_jwt_tenant_isolation.py
def test_tenant_cannot_read_across_boundary(client):
    # Threat TM-014: forged/confused tenant claim must not grant cross-tenant access.
    token = issue_token(tenant="tenant-a")
    resp = client.get("/api/tenant-b/records", headers={"Authorization": f"Bearer {token}"})
    assert resp.status_code == 403          # server validates tenant claim, denies by default
    assert "records" not in resp.text

Control type 2 — Configured scanners

Some threats are not about one specific behaviour but a whole class of weakness — injection anywhere in the codebase, a known-vulnerable dependency, a committed secret. These are the province of the pipeline scanners the DevSecOps Program sets up (SAST, SCA, secret scanning), and the Security by Design contribution is to tune and target them using the threat model rather than running them at generic defaults. If your threat model flags that a particular service parses untrusted input into a query, you raise the SAST sensitivity for injection on that service; if a service handles regulated data, you tighten its dependency policy. The threat model tells the scanner where to care.

Crucially, these run under Part 1's non-blocking policy: a new injection finding on an internal, low-risk service informs and is tracked as debt; the same finding on the internet-facing service that your threat model flagged as high-risk sits in the narrow critical tier that blocks. Same scanner, different consequence, and the difference is set by the threat model. This is how you get the security of strict scanning without the friction of blocking everything — the threat model is the arbiter of what is critical, so the blocking tier stays small and always-correct (the credibility condition from Part 1).

Control type 3 — Policy-as-code guardrails

Many threats are mitigated not in application code but in configuration: a threat of public data exposure is mitigated by a storage bucket that cannot be made public; a threat of unencrypted traffic by a policy that denies non-TLS listeners. These mitigations are enforced with policy-as-code — the same discipline the Cloud Security Program's governance phase uses — evaluating infrastructure changes at plan time and failing the pipeline if an invariant the threat model requires is violated. A threat like "misconfigured bucket exposes customer exports" becomes a policy that no bucket in this service may disable public-access blocking, checked on every infrastructure change:

threat_tm022_no_public_buckets.rego
package sbd.tm022

# Threat TM-022: customer-export bucket must never be publicly readable.
deny[msg] {
    r := input.resource_changes[_]
    r.type == "aws_s3_bucket_public_access_block"
    r.change.after.block_public_acls == false
    msg := sprintf("TM-022: public access block disabled on %q", [r.address])
}

The point is the same traceability: the policy is named for the threat it enforces (TM-022), so the control is self-documenting and auditable. When Part 4 threat-models your cloud architecture, most of its mitigations will land here as policy-as-code, which is why this control type is the bridge to the cloud part.

Control type 4 — Runtime checks

A few threats cannot be verified at build time because they only exist in the running system — anomalous authentication volume, configuration drift after deployment, a behaviour that emerges under real traffic. For these the "control" is a detection wired into your monitoring (the Cloud Security Program's detection phase, or your APM/SIEM), with the threat model specifying what to watch for. Threat modelling makes these detections targeted: instead of generic alerting, you monitor for the specific abuse the model predicted — the out-of-pattern decrypt volume, the tenant-isolation check firing in production, the drift on the TM-022 bucket policy. The threat register's verified_by can point at a detection as legitimately as at a test; what matters is that the mitigation has a live guarantee somewhere.

Keep these proportionate: runtime checks cost operational attention, so reserve them for the threats that genuinely cannot be caught earlier. The hierarchy is deliberate — eliminate by design where you can (Part 2), test at build time where you can, enforce by policy where it is a config invariant, and only fall back to runtime detection when the threat is inherently a live-system property. Cheaper and earlier is always better.

Keeping it non-blocking and proportionate

The risk in this part is re-introducing the friction Part 1 worked to remove: wire enough controls and you can rebuild the blocking gate that teams route around. The safeguards are the ones already established. Non-blocking by default: most controls inform and track debt; only the narrow critical tier blocks, and the threat model decides what is critical. Proportionality: you build controls for the high-priority threats, not all of them — a low-impact, low-likelihood threat that got accepted in Part 2 does not need a pipeline control, and building one is wasted effort that dilutes the signal. Traceability both ways: every blocking control traces to a high-priority threat, which is what lets you defend the blocking tier as always-correct. Get this balance right and the pipeline is a safety net that catches regressions without slowing delivery; get it wrong and you have taught the organization, again, that security is the thing that breaks the build.

Definition of done — Wiring threat models into DevSecOps exit checklist

You are ready for Part 4 when every one of these is true:

  • Traceability: every high-priority threat from your models maps to a specific control (test, scanner rule, policy, or runtime check), and every blocking control traces back to a high-priority threat or a named baseline policy.
  • Targeted tests: application-logic mitigations for high-priority threats have abuse-case tests that run on every change and fail on regression, named in the threat register's verified_by.
  • Configured scanners: the DevSecOps pipeline's scanners are tuned and targeted by the threat model — sensitivity and blocking raised on the services and weakness classes the model flagged as high-risk.
  • Policy-as-code: config/infrastructure mitigations are enforced as policy-as-code at plan time, named for the threats they enforce.
  • Runtime checks: threats that can only be verified live have targeted detections wired to monitoring, referenced from the threat register.
  • Non-blocking preserved: controls inform and track debt by default; the blocking tier remains narrow, threat-model-driven, and free of false blocks that would damage trust.

Tick every box and your threat models are no longer documentation — they are a living contract enforced on every change, where a decided mitigation cannot silently disappear. Skip the traceability box and you will accumulate controls nobody can explain and threats nobody enforces, which is how a security program becomes both heavy and ineffective at once.

What's next

Part 4 — Cloud threat modelling & controls takes the method and the wiring you now have and points them at the cloud. Threat modelling changes when your "system" is a set of managed services spread across AWS, GCP and Azure rather than code you wrote — the trust boundaries move, the threats shift toward identity and configuration, and the mitigations are overwhelmingly the cloud-native guardrails the Cloud Security Program builds. Part 4 shows how to run the four-question ritual on a cloud architecture and how to map what you find to the specific native control that closes it, so your cloud threat models flow into policy-as-code exactly as this part described. It ships next month.

If you would like experienced hands to wire your threat models into your pipelines — build the abuse-case tests, tune the scanners, and author the policy-as-code that makes mitigations durable — that is precisely what Axelia's consultants do, and ISMShed links each control back to its threat and to the ISO 27001, ENS, NIS2, DORA, SOC 2 and GDPR obligations it satisfies, so your pipeline becomes continuous audit evidence. Make your mitigations durable now, and Part 4 extends the same discipline to the cloud.

Coming soon
Cloud threat modelling & controls
Coming Oct 2026

Talk to an expert

Don't wait for the guide. Book a call with our AI-CISO team and get a tailored roadmap to ISO 27001, NIS2, ENS or DORA compliance.