On December 9, 2021, a Minecraft chat message triggered a stack trace that led researchers to CVE-2021-44228 — Log4Shell — a CVSS 10.0 remote code execution flaw in Apache Log4j that had been privately reported just two weeks earlier by Chen Zhaojun of Alibaba Cloud. It sat in a transitive dependency most teams didn't know they had. Two years and change later, on March 29, 2024, a different failure mode surfaced: CVE-2024-3094, a backdoor deliberately inserted into liblzma (XZ Utils) by a maintainer identity, "Jia Tan," that had spent roughly two years building commit history and community trust before shipping an SSH-authentication-bypass payload in versions 5.6.0 and 5.6.1. One incident was a dependency risk nobody scanned deeply enough; the other was a build-pipeline and identity risk no code scanner could have caught. Both point to the same conclusion: security bolted onto the end of a release process, or scattered across disconnected point tools, will always miss one of these categories. NIST's Secure Software Development Framework (SP 800-218, finalized February 3, 2022, with a v1.2 revision drafted for comment in late 2025) gives the structural backbone for closing that gap. This post turns it into eight concrete practices spanning threat modeling through CI/CD gating.
Why start with threat modeling instead of a scanner?
Threat modeling comes first because every downstream control — SAST rules, gate policies, runtime alerts — is only as good as the assumptions about what you're defending and against whom. SSDF's "Prepare the Organization" practice group (PO.1, "Define Security Requirements for Software Development") and the "Produce Well-Secured Software" group (PW.1, "Design Software to Meet Security Requirements and Mitigate Security Risks") call for defining security requirements and architecture before implementation, not after a scanner flags something. In practice this means each new service or major feature gets a lightweight data-flow diagram and a documented answer to "what does this component trust, and what happens if that trust is violated" — the same question that, applied to Log4j's JNDI lookup feature, would have flagged untrusted-input-to-remote-class-loading as a design-level risk years before CVE-2021-44228, independent of any single code review missing it.
What does "shift-left" actually require in the coding phase?
Shift-left requires running the same classes of checks a security team would run manually, but automatically, at commit time, with low enough false-positive rates that developers don't disable them. That means static analysis tracing untrusted input from a source (a request parameter, a CLI argument) to a dangerous sink (a SQL query, a command exec) rather than flagging every occurrence of a risky function name — Safeguard's own SAST engine, for example, is built around exactly this source-to-sink dataflow model with CWE/OWASP mapping, though as with any first-party AppSec engine still expanding its rule coverage, detection depth should be expected to grow over time rather than treated as exhaustive on day one. It also means secrets scanning at the pre-commit hook, not just in CI, since a credential that reaches a public commit history is compromised the moment it's pushed, regardless of whether it's later removed.
How do you catch supply-chain risk that lives in dependencies, not your code?
You catch it with software composition analysis (SCA) paired with a Software Bill of Materials (SBOM), because most exploitable risk in a modern application originates in code your team never wrote. Executive Order 14028 (May 2021) pushed SBOMs into federal procurement, and the CycloneDX and SPDX formats standardized how a bill of materials — every direct and transitive dependency plus its resolved version — gets generated and queried. This is precisely the Log4Shell lesson: teams with an up-to-date SBOM could query "do we have log4j-core 2.0 through 2.14.1 anywhere" in minutes on December 10, 2021; teams without one spent days grepping build artifacts across hundreds of repositories while the CVE was actively being exploited in the wild.
Why isn't "no known CVEs" the same as "safe to ship"?
Because raw CVE counts, without reachability or exploit context, produce a triage queue no team can actually work through — and because a clean CVE scan says nothing about a maliciously modified build process. Vendor studies from 2023–2024 have repeatedly found that a large majority of CVEs an SCA tool flags in a typical application sit in code paths never invoked at runtime, which is why reachability analysis (tracing whether your call graph actually reaches the vulnerable function) matters as much as the initial scan. CISA's Known Exploited Vulnerabilities (KEV) catalog solves a related problem by re-ranking findings around confirmed real-world exploitation rather than CVSS score alone. Neither approach, however, would have caught XZ Utils — the backdoored code had no CVE to match against until after disclosure, which is why provenance and build integrity are a separate pillar, not a substitute for vulnerability scanning.
How do you verify a build wasn't tampered with, given the XZ Utils precedent?
You verify it with signed provenance attestations and build-pipeline integrity standards like SLSA (Supply-chain Levels for Software Artifacts), because the XZ Utils backdoor was inserted not through a code review bypass but through control of the release tarball's build scripts — artifacts that didn't match the public Git history. Tools like Sigstore's Cosign and the CNCF Notary Project's Notation give you a way to cryptographically sign container images and artifacts so a downstream consumer can verify what actually built the thing they're running, not just trust the name on the registry tag. SLSA level 3 provenance requires the build to run on a hardened, ephemeral builder with a verifiable, non-forgeable record of the source and steps used — the category of control that makes a Jia-Tan-style insertion into a release artifact detectable, even when it evades human code review for years.
What does automated gating in CI/CD actually enforce, and where does it live?
Automated gating enforces policy as code at the moment risk would otherwise enter production, rather than as a report someone reads after the fact — and it needs multiple enforcement points, not one. A mature setup gates at commit (server-side checks before merge), in CI (a step that evaluates SBOM, CVE, license, and signature data and exits non-zero on a blocking violation), at the registry (rejecting unsigned pushes), and at Kubernetes admission (denying non-compliant pods before they schedule). Safeguard's own safeguard gate check CLI illustrates the pattern concretely: it evaluates a named policy (for example --policy production) against an SBOM or a scanned source tree, can fail the run on a chosen severity threshold (--fail-on critical), and returns a non-zero exit code a CI job can act on — for instance blocking a build over a KEV-listed critical CVE in log4j-core while still allowing a lower-severity license mismatch through — with results exportable in SARIF or JUnit format for the "produce evidence" side of SSDF's practice groups. The gate is only as trustworthy as the SBOM and signature data feeding it, which is why this pillar depends on the reachability and provenance work upstream of it.