Safeguard
DevSecOps

Policy-as-code for CI/CD: enforcing security gates withou...

How policy-as-code turns security gates from build-breaking friction into fast, git-versioned CI/CD checks — and where Safeguard's approach differs from JFrog's Xray and Curation model.

Priya Mehta
DevSecOps Engineer
8 min read

Every DevSecOps team eventually hits the same wall: a security gate that was supposed to stop the next Log4Shell instead stops Friday's release. On December 10, 2021, the Log4Shell vulnerability (CVE-2021-44228, CVSS 10.0) forced thousands of engineering teams into emergency patch cycles precisely because nothing in their pipelines was checking dependency risk automatically. Three years later, the xz-utils backdoor (CVE-2024-3094), found by chance on March 29, 2024, showed the opposite failure mode: a maintainer with commit access can poison a build long before any scanner sees a CVE. Policy-as-code is the industry's answer to both problems — codifying security rules as version-controlled, testable logic that runs inside the pipeline itself. Done well, it stops real threats in seconds. Done badly, it becomes the reason developers route around security entirely. This post looks at how policy-as-code actually works in CI/CD, where JFrog's approach fits, and what it takes to enforce gates that engineers don't fight.

What is policy-as-code, and why does CI/CD need it now?

Policy-as-code means writing security and compliance rules — "block critical CVEs with a public exploit," "reject unsigned containers," "fail builds using GPL-licensed packages in proprietary code" — as declarative, version-controlled code that a pipeline evaluates automatically, instead of a PDF a human reads during a change-approval meeting. Open Policy Agent's Rego language, introduced by Styra in 2016 and now a CNCF graduated project, became the de facto standard for expressing these rules outside any one vendor's UI. The urgency is structural: Sonatype's 2023 State of the Software Supply Chain report counted more than 245,000 malicious packages published that year alone — more than the combined total of the previous four years. Manual review cannot scale against that volume; only automated, codified policy evaluated at commit and build time can.

Why do security gates break builds instead of protecting them?

Security gates break builds when they run too late, return too many false positives, or block on rules nobody can inspect or override quickly — and each of those failure modes has a well-documented root cause. Running a full SCA and container scan only at the pre-production gate, after code has already merged, means a blocked build costs hours of rework instead of a single failed pre-commit check. Noisy scanners compound this: a policy that fails a build on every CVE regardless of reachability or exploitability trains teams to treat "build failed" as background noise, not a signal — the same alert fatigue dynamic that let the Codecov Bash Uploader backdoor go undetected for roughly two months after its January 2021 compromise. And opaque, UI-configured rules that only a security team member can edit create a bottleneck: when a legitimate hotfix needs a temporary exception, waiting on a ticket queue is often what pushes teams to disable the gate rather than fix the underlying process.

How does JFrog approach policy enforcement in CI/CD?

JFrog enforces security policy primarily through Xray's "Watches and Policies" model layered on top of Artifactory, plus JFrog Curation, launched in 2024 to block risky open-source packages before they ever enter the registry. In this model, a security admin configures policies through the JFrog platform UI (or its API) — thresholds for CVSS severity, license types, or operational risk — and attaches them to "watches" scoped to specific repositories or builds; Xray then blocks or flags artifacts that violate them, and Curation intercepts new package downloads from public registries like npm and PyPI before they land in Artifactory. It's an effective model for teams already standardized on the JFrog Platform as their artifact backbone, and it benefits from deep integration with Artifactory's binary metadata. The tradeoff is that policies live as configuration inside the JFrog platform rather than as plain-text files a developer can open, diff, and review in a pull request the same way they'd review application code — which matters once an organization has dozens of policies across multiple business units and needs an audit trail of who changed what, and why, without logging into a separate admin console.

What does a policy-as-code pipeline actually look like?

A working policy-as-code pipeline evaluates rules at three points — pre-commit, pull request, and pre-deploy — with each stage enforcing progressively stricter policy against progressively more complete information. At pre-commit, a lightweight check (secrets scanning, basic license rules) runs in under 5 seconds using a local hook. At the pull request stage, a CI job evaluates a Rego or Cedar policy bundle against the full dependency graph and SBOM, for example:

package supplychain.policy

deny[msg] {
  input.vuln.cvss_score >= 9.0
  input.vuln.exploit_maturity == "active"
  not input.package.has_vex_justification
  msg := sprintf("Blocking %s: active-exploit critical CVE %s with no VEX exception", [input.package.name, input.vuln.id])
}

At pre-deploy, the gate re-checks provenance — verifying an SLSA (Supply-chain Levels for Software Artifacts) attestation and cosign signature actually match the artifact being promoted, catching the class of tampering behind the 2020 SolarWinds Orion compromise, where a build-time injection went unnoticed for months because nothing verified build provenance downstream. The policy source lives in a git repository, changes go through the same PR review as application code, and a conftest test or opa eval run locally lets a developer reproduce a CI failure on their laptop in seconds instead of guessing from a build log.

How do you enforce gates without slowing developers down?

You enforce gates without slowing teams down by making policy evaluation fast, cached, and specific enough to fail only on things that actually matter — and by measuring it. A practical target: policy evaluation should add no more than 10-15 seconds to a PR check by caching SBOM generation and vulnerability database lookups instead of re-fetching them on every commit; anything that regularly pushes a gate past 90 seconds tends to be the threshold where teams start requesting bypasses. Precision matters as much as speed — scoping deny rules to reachable, exploitable, actively-exploited vulnerabilities (using VEX or reachability analysis) rather than every CVE above a raw CVSS score cuts false-positive block rates dramatically, since a large share of flagged CVEs in a typical dependency tree are in code paths never actually invoked. Finally, every hard gate needs a governed escape hatch: a time-boxed, logged break-glass override tied to a specific incident ticket, expiring automatically (for example, 72 hours) rather than becoming a permanent silent exception nobody revisits.

What should a security team measure to know a policy gate is working?

A policy gate is working if you can show three numbers trending the right direction: mean time to remediate a critical finding, the false-positive block rate, and the percentage of exceptions that expire on schedule versus linger indefinitely. Mean time to remediate should be measured from the moment a policy bundle is updated to include a new advisory (for instance, the day CVE-2024-3094 was disclosed) to the moment every affected pipeline enforces it — teams running policy as versioned code with automatic bundle distribution typically close that gap in hours; teams relying on manual policy updates in a central console often take days because someone has to remember to update every watch individually. False-positive rate should be tracked per policy rule, not just in aggregate, so a single overly broad license or CVSS threshold rule doesn't erode trust in the whole gate. And exception hygiene — what fraction of break-glass overrides actually expired versus were quietly renewed — is often the single clearest signal of whether "policy as code" is a real control or theater; a compliance auditor preparing a SOC 2 report will ask for exactly this evidence.

How Safeguard Helps

Safeguard treats policy-as-code as a first-class artifact, not a UI configuration layer: every gate — CVE severity thresholds, license rules, SLSA provenance requirements, package curation rules — is expressed as versioned, git-native policy that goes through the same pull-request review as the code it governs, with a full diff and blame history instead of an admin-console change log. Where JFrog's Xray watches and Curation rules live inside the JFrog Platform's own configuration model, Safeguard's policies are portable Rego/OPA-compatible bundles that plug into GitHub Actions, GitLab CI, Jenkins, or CircleCI without requiring Artifactory as the artifact backbone, and evaluate against cached SBOM and vulnerability data to keep PR checks under the 15-second range that keeps developers from routing around them. Safeguard scopes deny rules by exploitability and reachability, not raw CVSS score alone, cutting the false-positive block rate that drives gate fatigue, and pairs every hard block with a time-boxed, ticket-linked break-glass workflow that auto-expires and feeds directly into audit evidence for SOC 2 and FedRAMP reporting. For teams evaluating a move from JFrog's watch-based model or standing up policy-as-code for the first time, Safeguard's policy bundles ship with pre-built rules mapped to the incidents that matter — active-exploit CVEs, unsigned or unattested build artifacts, and known-malicious package indicators drawn from the same threat intelligence that flagged the 2024 xz-utils backdoor — so security teams can enforce real gates on day one instead of spending a quarter tuning thresholds from scratch.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.