What DevSecOps is
Link to section “What DevSecOps is”In 30 seconds
Link to section “In 30 seconds”DevSecOps is the practice of building security into the software delivery pipeline and into the teams that run it. Security was often one review near release, by a separate team; with many small changes a day, that review became a bottleneck or a rubber stamp. DevSecOps replaces it with small, automated, proportionate controls between the editor and production, each placed where its finding is cheapest to act on, plus supply-chain evidence (bills of materials, signatures, build provenance) that proves what shipped. Developers own most of their code’s security; specialists set policy and coach. It is judged by outcomes: fewer exploitable defects reach production, those that do are fixed fast, and delivery does not slow down.
There is no single normative definition: NIST’s SP 800-204C expands the acronym (“Development, Security, and Operations”) and calls DevSecOps “a facilitating paradigm” built on CI/CD pipelines.[1] The definition above is this Atlas’s. This page is for developers, application-security (AppSec) engineers, platform owners, architects and engineering leaders. Last verified 2026-10-17.
At a glance
Link to section “At a glance”| Aspect | Summary |
|---|---|
| Goal | Ship changes that are secure enough for their risk, with evidence, at delivery speed |
| Main users | Developers, AppSec engineers, CI/CD and platform owners, security champions, release approvers |
| In scope | Threat modelling of changes; secret, SAST, SCA, IaC and image scanning; DAST and API tests; policy as code; SBOMs, signing, provenance; hardening CI/CD itself; champions; gates and exceptions |
| Out of scope (linked) | Runtime detection and response (SOC Ops, SecOps); estate-wide vulnerability prioritisation (VulnerabilityOps); admission and runtime controls in clusters (KubernetesSecOps); cloud posture (CloudSecOps); audit evidence programmes (ComplianceOps) |
| Prerequisites | DevOps basics: version control, CI/CD, trunk-based development |
| Key outputs | A pipeline with placed controls, a gate policy, an exceptions register, SBOMs and attestations per release, security metrics |
| Term status | Established: in the titles of NIST publications,[1][2] a NIST NCCoE project,[3] DoD guidance[4] and OWASP projects;[5][6] no single normative definition |
The problem it solves
Link to section “The problem it solves”What fails without it. Security that lives only at the end of delivery fails in three predictable ways:
- Late findings get waived. A pre-launch penetration test finds an injection flaw written six weeks earlier; the author has moved on, the launch date wins, and the finding becomes an accepted risk nobody revisits.
- Volume overwhelms reviewers. With dozens of deployments a day, a manual sign-off queues work for days or becomes a click-through.
- Nobody can answer “are we affected?” When a widely used component is found to be compromised, as XZ Utils versions 5.6.0 and 5.6.1 were in March 2024,[7] teams without an inventory of what is inside each deployed artefact have to inspect images one by one.
A fourth failure is newer: the delivery system itself becomes the target. In 2025 a widely used GitHub Action exposed the secrets of workflows that ran it; in 2026 a security scanner’s official GitHub Actions were hijacked (use cases). CI/CD holds production credentials.
Before and after (illustrative).
| Before: end-of-line security | After: DevSecOps | |
|---|---|---|
| When issues are found | Pre-release review or pen test | In the editor, pull request and build, minutes after writing |
| Who fixes them | A ticket for whoever is free | The author, in the same change, with a suggested fix |
| What blocks a release | Whatever the reviewer decides that week | A written gate policy |
| Third-party code | Unknown; searched for during incidents | SBOM per release; dependency updates continuous |
| Proof of what shipped | Release notes | Signed artefacts with build provenance, verified before deploy |
| Pipeline credentials | Long-lived keys in CI variables | Short-lived identities per job and environment |
| Security team role | Gatekeeper | Policy owner, coach, incident partner |
Measured value. The best independent evidence is survey-based. DORA’s 2022 report found that teams with low levels of security practices had 1.4 times greater odds of high burnout, and that SLSA-related practices predicted better organisational and delivery performance, an effect that “needs strong continuous integration capabilities in place to fully emerge”.[8] These are survey correlations: direction, not a business case.
What DevSecOps is not
Link to section “What DevSecOps is not”- Not a tool purchase. A “DevSecOps platform” is a vendor’s product category. Scanners without a gate policy, an exceptions process and owners for findings produce dashboards of unread results.
- Not “the security team does DevOps”. Delivery teams own security outcomes for their services; specialists set policy and build paved roads.
- Not shift-left only. Moving checks earlier is half the idea; the other half is evidence that survives to production (provenance, SBOMs, signatures) and feedback from production into the pipeline.
- Not runtime security operations. Attacks on running systems belong to SOC Ops, ResponseOps and SecOps; DevSecOps owns security before and at deployment, and pipeline compromise.
- Not a compliance programme. Pipeline controls produce audit evidence; deciding which obligations apply and managing auditors is ComplianceOps.
- Not “block everything above medium”. A gate that fails builds on low-confidence findings teaches developers to bypass gates. Proportionality is the core skill this page teaches.
Naming confusion. “DevSecOps”, “SecDevOps” and “DevOpsSec” are used interchangeably; the word order carries no agreed meaning. “Rugged DevOps” echoes the Rugged Software initiative (started 2010).[9] “DevSecOps engineer” is a job title with no standard scope (DevOps discusses such titles).
Where it comes from and where it sits
Link to section “Where it comes from and where it sits”Origins and evolution
Link to section “Origins and evolution”The idea predates the word. Secure development lifecycles such as Microsoft’s SDL, now described by Microsoft as its approach “to integrate security into DevOps processes (sometimes called a DevSecOps approach)”,[10] put security activities into each phase; DevOps made phases minutes long, so activities became automated checks. Milestones that can be traced to sources:
- 2010: the Rugged Software initiative (Joshua Corman, David Rice, Jeff Williams) asks developers to own their code’s resilience; a 2012 Rugged Handbook group included Gene Kim.[9]
- 2012: Gartner published a report titled DevOpsSec: Creating the Agile Triangle, as reported at the time.[11]
- 2022: NIST published the Secure Software Development Framework (SSDF) v1.1 as SP 800-218 (February), mapped to US Executive Order 14028,[12][13] and SP 800-204C (March) on DevSecOps for microservices.[1] DORA made software supply-chain security a primary theme of its 2022 report.[8]
- 2023–2024: SLSA v1.0, its first stable version, appeared on 19 April 2023.[14] NIST SP 800-204D (February 2024) covered supply-chain security in CI/CD pipelines.[2] The US DoD updated its DoD Enterprise DevSecOps Fundamentals in October 2024 (version 2.5 according to the file name of the linked PDF).[4]
- 2024–2026: regulation arrived, and some was withdrawn. The EU Cyber Resilience Act entered into force in December 2024, with obligations staged to December 2027;[15] in January 2026 the US OMB rescinded M-22-18, the memo behind the federal software attestation form.[16] The OWASP Top 10:2025 widened its supply-chain category, A03, to “all supply chain failures”;[17] SLSA v1.2 added a Source track;[18] CISA and 17 co-authoring organisations replaced the 2021 SBOM minimum elements in July 2026.[19]
Glossary and mental model
Link to section “Glossary and mental model”The mental model: placed controls plus a chain of evidence. Picture the path from editor to production as a conveyor lined with controls. Each has a position (how early it runs), a confidence (how often its findings are real to the developer), a cost (minutes and triage hours) and an action (block, warn or record). Alongside runs a chain of evidence: each artefact carries its bill of materials, signature and provenance. DevSecOps design is choosing position, confidence threshold and action per control, and keeping the chain unbroken.
| Term | Meaning | Example |
|---|---|---|
| Shift left | Run a check earlier, where the fix is cheaper | Pre-commit secret scanning, not a quarterly audit |
| SAST | Static application security testing: analyses source without running it | A rule finds user input flowing into a SQL string |
| SCA | Software composition analysis: inventories third-party components and matches them to known vulnerabilities | A dependency at a version listed in a CVE |
| DAST | Dynamic testing of a running application from the outside | A baseline scan of a test deployment for missing security headers |
| IaC scanning / policy as code | Checking infrastructure and deployment definitions against written rules | A rule rejects an unencrypted storage bucket |
| SBOM | Software bill of materials: “a nested inventory, a list of ingredients that make up software components”[20] | A CycloneDX or SPDX file per image |
| Provenance, attestation | A signed statement (attestation) of how and from what an artefact was built; attestations can also carry SBOMs | An in-toto statement naming the builder and source commit[21][22] |
| SLSA | Supply-chain Levels for Software Artifacts (“salsa”): a specification with a Build track (L0–L3) and a Source track (L1–L4)[23][24] | “Build L2”: provenance signed by a hosted build platform |
| OIDC, workload identity | OpenID Connect: a CI job proves where it runs (repository, branch, environment) with a short-lived signed token and exchanges it for temporary cloud credentials[25] | A deploy job assumes a cloud role with no stored key |
| Transparency log | A public log of signing events, so they can be audited; Sigstore’s is Rekor[26] | Anyone can see that your workflow signed an image |
| VEX | Vulnerability Exploitability eXchange: states whether a vulnerable component is exploitable in the product that uses it[27] | “Not affected: the vulnerable function is never called” |
| KEV, EPSS | CISA’s catalogue of vulnerabilities known to be exploited;[28] FIRST’s estimated probability that a CVE is exploited in the next 30 days[29] | Inputs for prioritising known vulnerabilities |
| Effective false positive | A finding the developer chooses not to act on, whatever the tool author thinks[30] | A “SQL injection” in a test fixture nobody will fix |
| Baseline | The set of findings that already existed before a change | Old findings tracked as backlog rather than blocking new work |
| Security champion | A developer with extra security skills and time, often the team’s first point of contact for security[31] | Reviews threat models for their squad |
How DevSecOps relates to other Ops
Link to section “How DevSecOps relates to other Ops”DevSecOps sits on top of DevOps, which owns the delivery system, CI/CD and the DORA delivery metrics, and feeds several security pages. The boundaries below are editorial distinctions chosen by this Atlas; no standard defines them.
Diagram source (Mermaid)
flowchart LR DEV["DevOps"]:::neutral CON["ContainerOps"]:::neutral DSO["DevSecOps"]:::core VUL["VulnerabilityOps"]:::neutral KSO["KubernetesSecOps"]:::neutral CSO["CloudSecOps"]:::neutral CMP["ComplianceOps"]:::neutral SOC["SOC Ops, SecOps"]:::neutral DEV -->|"pipelines to secure"| DSO CON -->|"images to scan, sign"| DSO DSO -->|"vulnerable components that shipped"| VUL DSO -->|"signatures, SBOMs, provenance to verify"| KSO DSO -->|"IaC rules shared with posture checks"| CSO DSO -->|"control evidence"| CMP DSO -->|"pipeline-compromise incidents"| SOC classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
Text equivalent: DevOps hands DevSecOps pipelines to secure; ContainerOps, images to scan and sign. DevSecOps hands VulnerabilityOps the vulnerable components that shipped; KubernetesSecOps signatures, SBOMs and provenance to verify; CloudSecOps the IaC rules it reuses on deployed resources (posture is CloudSecOps’s); ComplianceOps control evidence; SOC Ops and SecOps pipeline-compromise incidents.
| Neighbouring page | Shared scope | DevSecOps focus | The neighbour’s focus |
|---|---|---|---|
| DevOps | CI/CD, automation, culture | Which security controls sit in the pipeline, and how they gate | The delivery system and its metrics |
| VulnerabilityOps | SCA findings, CVSS, KEV, EPSS | Preventing new vulnerable code | Prioritising every vulnerability across the estate to a verified fix |
| KubernetesSecOps | Image signatures, policies | Producing signatures, SBOMs and provenance at build time | Verifying them at admission; runtime controls |
| ContainerOps | Images | Scanning, signing and attesting images | Building small, reproducible images |
| CloudSecOps | IaC misconfigurations | Catching them in the pull request | The deployed cloud control plane and drift |
| ComplianceOps | Evidence | Control evidence as a by-product | Mapping evidence to frameworks and auditors |
| SecOps, SOC Ops | Security incidents | Pipeline compromise and secret leaks | Runtime detection and response |
| ReleaseOps, GitLabOps | Release decisions; scanners in one product | Security inputs; which findings block | The release decision; configuring GitLab’s scanners |
Principles and operating model
Link to section “Principles and operating model”Published frameworks and this Atlas’s charter
Link to section “Published frameworks and this Atlas’s charter”Two kinds of published material exist; neither is “the official DevSecOps standard”:
- Outcome frameworks. NIST’s SSDF v1.1 has four practice groups: Prepare the Organization (PO), Protect the Software (PS), Produce Well-Secured Software (PW) and Respond to Vulnerabilities (RV). “The SSDF does not prescribe how to implement each practice. The focus is on the outcomes”.[32] Version 1.2 is a draft (as of October 2026).[33][34]
- Community manifestos. DevSecOps.org values “Data & Security Science” over fear, uncertainty and doubt;[35] the Threat Modeling Manifesto values “a culture of finding and fixing design issues” over checkbox compliance.[36]
This Atlas’s proposed charter (a proposal, not an industry standard):
Mission: every change that reaches production is as secure as its risk requires, and we can prove what it contains and how it was built, without making delivery slower than the risk justifies.
Principles:
- Place, don’t pile. Each control runs at the earliest point where its finding is actionable by the person who can fix it.
- Block on confidence, not on fear. A gate may stop work only when its findings are usually real, severe and fixable now.
- Every finding has an owner and a clock. Fixed, excepted with an expiry, or tuned away; never silently ignored.
- The pipeline is production. CI/CD identities, runners and third-party steps get production-grade controls.
- Evidence is a by-product. SBOMs, signatures and provenance are generated by the pipeline, not assembled for auditors.
- Security is a team skill. Delivery teams own their services’ security; specialists coach, set policy and build paved roads.
Non-goals: zero findings; scanning everything with every tool; replacing design review with scanners; owning runtime detection.
Invariants and trade-offs. Three things must stay true: artefacts are immutable and identified by digest; nobody, human or job, holds a production credential it does not need; every exception expires. Trade-offs: earlier checks see less context (a pre-commit scan cannot know whether a library is reachable), stricter gates cost time and trust, richer evidence costs storage.
Fundamentals and building blocks
Link to section “Fundamentals and building blocks”DevSecOps controls fall into seven families. The last column names the SSDF practice each family supports.[32]
| Family | What it checks | Typical position | SSDF outcome |
|---|---|---|---|
| Design and threat modelling | Data flows, trust boundaries, abuse cases | Start of a change | PW.1 design to meet security requirements |
| Code analysis (SAST, secret scanning) | Source code and history | Editor, pre-commit, pull request | PW.7 review and analyse human-readable code |
| Composition (SCA, licences, SBOM) | Third-party components | Pull request (diff), build (full), continuous | PW.4 reuse well-secured software; PS.3 archive releases |
| Configuration (IaC, Dockerfile, Kubernetes manifests) | Infrastructure and deployment files | Pull request, plan time | PW.7, as configuration-as-code is code (PS.1.1) |
| Dynamic testing (DAST, API tests, fuzzing) | A running build in a test environment | After deploy to test | PW.8 test executable code |
| Integrity (signing, provenance, attestations) | Artefacts and their build | Build and deploy | PS.1 protect code; PS.2 integrity verification |
| Platform hardening (CI/CD, identities, runners) | The pipeline itself | Continuous | PO.5 secure development environments |
Two cross-cutting blocks hold the families together: a findings store that de-duplicates results across tools (SARIF is the OASIS standard format for static-analysis results[37]) and a gate policy saying which results block, warn or are recorded.
Roles, ownership and RACI
Link to section “Roles, ownership and RACI”| Activity | Developer | Security champion | AppSec team | Platform / CI owner | Engineering manager |
|---|---|---|---|---|---|
| Threat model for a significant change | R | A | C | C | I |
| Fix findings in own change | A/R | C | C | I | I |
| Gate policy (what blocks) | C | C | A/R | C | C |
| Scanner rules and tuning | I | C | A/R | R | I |
| Pipeline identities, runners, third-party steps | I | I | C | A/R | I |
| Exceptions (approve, track expiry) | R (requests) | C | A (above a risk threshold) | I | A (below threshold) |
| SBOM, signing and provenance | I | I | C | A/R | I |
| Pipeline compromise response | C | C | R | A/R | I |
R = responsible, A = accountable, C = consulted, I = informed. This RACI is an Atlas proposal; adapt the exception threshold to your risk appetite, which RiskOps owns. In a pipeline compromise the IncidentOps lead coordinates and ResponseOps takes over if attacker activity is found (worked runbook).
Champions. OWASP’s Security Champions Manifesto lists ten principles, including “Nominate a dedicated captain”, “Reward responsibility” and “Anticipate personnel changes”;[38] OWASP’s Developer Guide says champions “can be the first point of contact for information security teams”.[31] Neither sets a time budget; this Atlas suggests 10–20%, protected in planning.
Team topology. What scales is an AppSec enabling team (policy, rules, coaching) plus a platform team that ships security in the paved road (PlatformOps), not a central team triaging every finding.
Lifecycle: Day 0, Day 1, Day 2
Link to section “Lifecycle: Day 0, Day 1, Day 2”- Day 0, design. Agree the gate policy and exception rules; choose a findings format (SARIF) and store; decide the identity model for CI/CD jobs; threat-model the pipeline itself (Lab 1).
- Day 1, rollout. Run controls report-only on a pilot service; measure effective false positives and added minutes; promote only rules that pass the bar; generate SBOMs and provenance from day one, because evidence is cheap to start and hard to backfill.
- Day 2, operate and improve. Review metrics monthly; retire noisy rules; renew or close exceptions; narrow pipeline identities; rehearse the pipeline-compromise runbook; feed incidents back into threat models and rules (SSDF RV.3: “Analyze Vulnerabilities to Identify Their Root Causes”).[32]
Operating model and governance
Link to section “Operating model and governance”Governance is a small set of written artefacts, versioned in Git and changed by pull request like code:
- Gate policy (machine-readable): severities and tags that block, warn or record; whether only new findings block.
- Exceptions register: one entry per excepted finding with owner, reason, ticket, grant date and expiry (maximum 90 days in this Atlas’s template; renewal needs a new decision).
- Pipeline security baseline: allowed third-party steps, pinning, token permissions, runner types per trust level.
- Rule catalogue: rules enabled at each position, with measured effective false-positive rates.
Cadence (Atlas proposal): champions fortnightly; metrics and exceptions monthly; pipeline baseline, identities and a one-page leadership scorecard quarterly (Templates). Exceptions above the risk threshold escalate to AppSec; disputes about risk acceptance go to the system owner (RiskOps owns appetite).
Diagram source (Mermaid)
stateDiagram-v2 [*] --> New: scanner reports New --> Triaged: owner assigned Triaged --> Fixing: real and fixable Triaged --> Excepted: real, not fixable now Triaged --> Dismissed: effective false positive Fixing --> Verified: rescan shows it gone Excepted --> Triaged: expiry date reached Dismissed --> RuleTuned: pattern repeats Verified --> [*] RuleTuned --> [*]
Text equivalent: a scanner reports a new finding, which is triaged and given an owner. A real, fixable finding moves to fixing and, once a rescan shows it gone, to verified and closed. A real finding that cannot be fixed now becomes an exception, which returns to triage when its expiry date is reached. A finding the developer will not act on is dismissed as an effective false positive; when the same pattern repeats, the rule is tuned.
Architecture and workflow
Link to section “Architecture and workflow”Reference architecture: where each control sits
Link to section “Reference architecture: where each control sits”The reference architecture is a sequence of positions, each with the controls actionable there. The design question for every control: who sees the result, how soon, and can they fix it? The pattern is vendor-neutral; the reference implementation shows it on one CI system.
Diagram source (Mermaid)
flowchart TB P1["1. Editor and pre-commit: secret scan, fast lint rules"]:::core P2["2. Pull request: SAST on changed lines, dependency diff, IaC policy, review"]:::core P3["3. Build on main: full SCA, image scan, SBOM, sign, provenance"]:::core P4["4. Test environment: DAST baseline, API tests"]:::core P5["5. Deploy: verify signature and provenance, policy check"]:::decision P6["6. Runtime: admission, monitoring, response"]:::neutral FB["Feedback: incidents and new CVEs"]:::risk P1 -->|"commits"| P2 P2 -->|"reviewed merge"| P3 P3 -->|"digest, SBOM, attestations"| P4 P4 -->|"tested release candidate"| P5 P5 -->|"verified digest only"| P6 P6 --> FB FB -->|"new rules and tickets"| P2 classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
Text equivalent: six positions from top to bottom, each listed with its controls in the table below: editor and pre-commit, pull request, build on main, test environment, deploy, runtime. Between them pass commits, a reviewed merge, an image digest with its SBOM and attestations, a tested release candidate and, into runtime, only a verified digest. Incidents and new CVEs feed back into the pull-request stage as new rules and tickets.
| Position | Controls | Typical action | Why here |
|---|---|---|---|
| 1. Editor, pre-commit | Secret scan, fast SAST rules | Block the commit locally (bypassable) | A secret never pushed never needs rotating |
| 2. Pull request | SAST on changed lines, SCA diff, IaC and manifest policy, server-side secret scan, review | Block merge on high-confidence, fixable, severe new findings | The author still has context; Tricorder showed results at code review, “on changed lines by default”[30] |
| 3. Build (main) | Full SCA, image scan, licences, SBOM, signing, provenance | Fail on policy; record the rest | Needs the artefact; provenance must come from the build platform[39] |
| 4. Test environment | DAST baseline, API security tests, fuzzing | Warn; block release on confirmed severe issues | Needs a running system |
| 5. Deploy | Verify signature, provenance, SBOM presence, policy | Refuse unverified artefacts | Last cheap, deterministic point before production |
| 6. Runtime | Admission, detection | Hand off | KubernetesSecOps, SOC Ops |
Alternatives and trade-offs. Centralised scanning (a security team scans every repository nightly) starts fast but yields late, ownerless findings; keep it as a backstop. Scanners in shared pipeline templates scale best but need their own versioning. All-in-one platforms cut integration work but make one weak scanner hard to swap.
Trust boundaries and identities in the pipeline
Link to section “Trust boundaries and identities in the pipeline”A pipeline is a set of jobs that run code (yours and third parties’) with identities that can change production. The OWASP Top 10 CI/CD Security Risks name the failure modes, from “Inadequate Identity and Access Management” (CICD-SEC-2) to “Poisoned Pipeline Execution (PPE)” (CICD-SEC-4) and “Insufficient Credential Hygiene” (CICD-SEC-6).[40] The defensive pattern is to split jobs by trust level and give each the narrowest identity. Permissions apply to a whole job, so one compromised step gets everything its job holds.
Diagram source (Mermaid)
flowchart TB PR["Zone 1, untrusted input: pull-request checks, scanners, third-party steps"]:::risk RES["Findings (SARIF) and gate result"]:::neutral BLD["Zone 2, main: build job pushes the image"]:::core SCN["Zone 2, main: scan job runs third-party scanners"]:::risk ATT["Zone 2, main: attest job runs GitHub-owned actions only"]:::core REG["Registry: image and attestations"]:::neutral DEP["Zone 3, approved environment: deploy job verifies, then deploys"]:::decision CLOUD["Production account"]:::neutral PR -->|"read-only token, no secrets"| RES BLD -->|"registry write"| REG BLD -->|"digest"| SCN SCN -->|"SBOM; read-only token"| ATT ATT -->|"OIDC signing identity, attestations"| REG REG -->|"attestations checked in the job"| DEP DEP -->|"OIDC role bound to the environment"| CLOUD classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
Text equivalent: in Zone 1 (untrusted input), the pull-request checks job runs scanners and third-party steps with a read-only token and no secrets, producing findings and a gate result. In Zone 2 (main), build pushes the image; scan runs third-party scanners with a read-only token and passes the SBOM on; attest, running only GitHub-owned actions, signs with an OIDC identity and pushes attestations. In Zone 3 (an approved environment), deploy checks the attestations, then assumes a cloud role bound to it.
Who controls a pull-request job? A pull request from a fork gets no secrets except a read-only GITHUB_TOKEN.[41] One
from a branch of the same repository runs in the context of its merge commit (unlike pull_request_target, which runs on the
default branch “rather than in the context of the merge commit”),[41] so the author’s edited workflow file runs and can
reference any secret the repository can reach: “Any user with write access to your repository has read access to all secrets
configured in your repository.”[42] Zone 1 holds no secrets only if none exist at repository or organisation level.
Rules that follow (each applied in the reference implementation):
- Third-party code never shares a job with a write token, a secret or a signing identity, unless the job cannot work without
it. Scanners run in Zone 1 and the read-only scan job; the attest job runs only GitHub-owned actions (Case 2 in
use cases shows why). The one deliberate exception is deploy’s
aws-actions/configure-aws-credentials, from GitHub’s AWS guide[43] and pinned to a reviewed SHA; compromised, it would reach the production role. The AWS CLI’sassume-role-with-web-identity, which needs no stored AWS credentials,[44] replaces it with a few lines of shell. - Deploy credentials live only in environments limited to the protected branch, and pipeline files need code-owner review.[42]
- Pin every third-party step to an immutable reference. GitHub says to pin actions to a full commit SHA because a tag “can be
moved or deleted if a bad actor gains access to the repository storing the action”.[42] GitHub’s workflow dependency
lockfiles (
.github/workflows/actions.lock) also pin transitive action dependencies; as of October 2026 they are “in public preview and subject to change”.[45] - Replace stored cloud keys with workload identity. With OIDC there are no stored cloud secrets to steal;[25] the trust policy must name which jobs may assume the role.
- Never run untrusted code with privileged triggers. GitHub warns that workflows using
pull_request_target“must not explicitly check out untrusted code, including from pull request forks”.[42]
End-to-end workflow
Link to section “End-to-end workflow”Trigger: a pull request. Actors: author, reviewer, CI, AppSec (by exception), release approver. Output: a deployed, attested artefact; findings fixed, excepted or tuned. Feedback: gate-outcome and escape metrics.
Diagram source (Mermaid)
%%{init: {"sequence": {"width": 100, "actorMargin": 40, "messageMargin": 30, "noteMargin": 6, "diagramMarginX": 6, "wrap": true, "wrapPadding": 6}}}%%
sequenceDiagram
participant Dev as Developer
participant Git as Git platform
participant CI as CI jobs
participant Reg as Registry
participant Dep as Deploy job
Dev->>Git: pull request
Git->>CI: checks, no secrets
CI-->>Git: findings, gate
Dev->>Git: fix, review, merge
Git->>CI: build, scan, attest
CI->>Reg: image, attestations
Note over Dep: approved
Dep->>Reg: fetch attestations
Reg-->>Dep: bundles
Note over Dep: verify identity
Note over Dep: OIDC role, deploy
Text equivalent: the developer opens a pull request; check jobs without secrets return findings and the gate result. The developer fixes them and, after review, merges. On main, CI builds, scans and attests the image and pushes it with its attestations to the registry. A reviewer approves the production environment. The deploy job fetches the attestation bundles for the digest and verifies them itself (signer identity, issuer, predicate type, runner type), then exchanges its OIDC token for the production role and deploys by digest. The registry stores evidence; it does not decide.
Exception paths.
- The author believes a blocking finding is wrong: they record a reason, the champion confirms, and the dismissal counts against the rule; three in a month trigger a tuning review.
- A real finding cannot be fixed in this change: an exception with owner, reason, ticket and expiry, in its own pull request.
- Verification fails at deploy: the deploy stops and nobody overrides it; rebuild from the protected branch, and if the signature or provenance is unexpected, follow the worked runbook.
- An emergency fix: a recorded break-glass merge with a second approver, never a skipped build or unsigned artefact, reviewed within two working days (Atlas proposal).
Practices, frameworks and tools
Link to section “Practices, frameworks and tools”Core practices and playbooks
Link to section “Core practices and playbooks”Six playbooks cover the practices that most often go wrong, followed by a note on API testing.
Playbook 1: threat-model a change in 45 minutes. Use the four questions of the Threat Modeling Manifesto: “What are we working on?”, “What can go wrong?”, “What are we going to do about it?”, “Did we do a good enough job?”[36]
- Trigger: a change that adds a data store, an external integration, a trust boundary or a new class of user data.
- Draw the data flow on one page: actors, processes, stores, flows and the trust boundaries they cross.
- For each flow across a boundary, walk STRIDE (Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege).[46] Write threats as “an attacker who … can … because …”.
- Decide per threat: mitigate, eliminate, transfer or accept; mitigations become tickets or tests.
- Acceptance: every threat has a decision and an owner. Contingency: if the design cannot be secured in time, scope the release down.
Playbook 2: keep secrets out of Git. Use a pre-commit secret scanner (fast, local, bypassable) and server-side protection (authoritative). GitHub’s push protection blocks pushes containing detected secrets, but by default anyone with write access can bypass it with a reason (“It’s used in tests”, “It’s a false positive”, “I’ll fix it later”);[47] restrict bypass to reviewers (delegated bypass) for production repositories. Plan limits (October 2026): repository push protection “Requires GitHub Secret Protection to be enabled”,[47] available for GitHub Team and Enterprise Cloud, with some features free for public repositories;[48] push protection for users, on by default, covers pushes to public repositories only.[47] When a secret is pushed, rotate first, clean history second: rewriting history does not un-leak a credential that was cloned or logged.
Playbook 3: introduce SAST without losing developers’ trust.
- Run the scanner report-only on main for two weeks; export SARIF.
- Sample 30 findings per rule and ask the owning developers whether they would fix each. The share they would not is the rule’s effective false-positive rate, the measure Google’s Tricorder team used.[30]
- Make blocking only rules whose rate is “essentially zero”, Tricorder’s bar for build-breaking checks; make review comments only rules under 10%, its bar for code-review analyses; leave the rest in a backlog report.[30]
- Show results on changed lines only, and baseline existing findings.
- Acceptance: blocking-rule override rate under 5% a month (Atlas target). Rollback: demote a rule the day its overrides spike.
Playbook 4: dependency hygiene. Keep dependencies few and current rather than chasing CVEs: commit lockfiles; let an update bot propose grouped weekly updates; check the dependency diff in pull requests and the full image on main; prefer reachability-aware SCA where available (Semgrep documents reachability for several languages[49]). Newly disclosed vulnerabilities in shipped components are prioritised by VulnerabilityOps with EPSS and KEV. Beware typosquatted and hallucinated package names: in one study, LLM-generated code referenced non-existent packages at an average of at least 5.2% for commercial and 21.7% for open-source models,[50] names an attacker can register.
Playbook 5: harden the pipeline against the OWASP CI/CD Top 10.[40]
| Risk | Control that addresses it |
|---|---|
| CICD-SEC-1 Insufficient Flow Control Mechanisms | Protected branches with required review and status checks; code owners for pipeline files; deploy only from the protected branch |
| CICD-SEC-2 Inadequate Identity and Access Management | Quarterly review of who can change pipelines, runners and environments |
| CICD-SEC-3 Dependency Chain Abuse | Lockfiles, internal proxy registries, scoped package names, review of new dependencies |
| CICD-SEC-4 Poisoned Pipeline Execution (PPE) | Same-repository PRs run their own workflow file: secrets only in branch-limited environments; gate rules from the base commit |
| CICD-SEC-5 Insufficient PBAC (Pipeline-Based Access Controls) | Per-job permissions; separate runners for untrusted and deploy jobs |
| CICD-SEC-6 Insufficient Credential Hygiene | OIDC instead of stored keys; masked logs; rotation runbook |
| CICD-SEC-7 Insecure System Configuration | Patch CI servers and runners; disable unused features; baseline per system |
| CICD-SEC-8 Ungoverned Usage of 3rd Party Services | Allow-list of actions, apps and integrations; pin by SHA or lockfile; review permissions they request |
| CICD-SEC-9 Improper Artifact Integrity Validation | Sign artefacts, generate provenance, verify before deploy |
| CICD-SEC-10 Insufficient Logging and Visibility | CI audit logs to the security data platform (SecDataOps); alerts on new admins, runners, disabled protections |
OpenSSF Scorecard automates several of these checks (Pinned-Dependencies, Token-Permissions, Dangerous-Workflow).[51]
Playbook 6: sign, attest and verify. On every protected-branch build: build once, generate the SBOM from the artefact, sign, attach provenance, push by digest. At deploy, verify the expected signer and builder; refuse anything else. Keyless signing with Sigstore “associates identities, rather than keys, with an artifact signature”: Fulcio issues a short-lived certificate for an OIDC identity, and the event is logged in Rekor.[26] For GitHub Actions the exact values are:[52]
certificate identity: https://github.com/{job_workflow_ref}
OIDC issuer: https://token.actions.githubusercontent.com
Verifying that identity, not “any valid signature”, is what makes the check meaningful.
API security testing. Linting and diffing the API contract in the pull request belongs to API Ops. In test, ZAP’s API scan imports an OpenAPI, SOAP or GraphQL definition and runs an active scan against the URLs it finds,[53] so point it only at test systems. Scanners rarely find authorisation flaws, yet three of the top five risks in the OWASP API Security Top 10 (2023, the latest edition listed in October 2026) are authorisation failures: API1, API3 and API5.[54] Write them as tests (Atlas recommendation): call each endpoint that takes an object ID with a second user’s token and expect a refusal; call admin functions as an ordinary user. For web applications, the OWASP Top 10:2025 guides SAST rules and champion training.[55]
Methods, standards and frameworks
Link to section “Methods, standards and frameworks”| Framework | What it is | Status (October 2026) | Mandatory? |
|---|---|---|---|
| NIST SP 800-218 SSDF v1.1 | Outcome-based secure development practices (PO, PS, PW, RV)[12] | Final (Feb 2022); v1.2 draft since Dec 2025[33] | Voluntary; mapped to EO 14028 §4e;[13] US attestation policy changed in 2026 (Quality) |
| NIST SP 800-204C, 800-204D | DevSecOps for microservices; supply-chain security in CI/CD[1][2] | Final (2022, 2024) | Guidance |
| NIST SP 1800-44 (NCCoE) | DevSecOps practices aligned with the SSDF[3] | Preliminary drafts | Guidance in progress |
| SLSA v1.2 | Build track L0–L3 and Source track L1–L4 for supply-chain integrity[24][56] | Approved | Voluntary |
| OWASP Top 10:2025 | Awareness list of web-application risk categories[55] | Current edition | Awareness only |
| OWASP ASVS | Requirements for testing web application security controls[57] | 5.0.0 latest listed | Voluntary |
| OWASP SAMM v2 | Maturity model: 5 business functions, 15 practices, 3 maturity levels each[58] | v2.0 | Self-assessment |
| BSIMM | Descriptive model of observed practices across 100+ organisations[59] | BSIMM16 (vendor page) | Benchmark |
| OWASP DSOMM | DevSecOps maturity model, an OWASP Lab project[6] | Lab | Self-assessment |
| SBOM minimum elements | 17 minimum data fields for an SBOM[19] | 2026 edition (July 2026) | Guidance, not regulation |
| EU Cyber Resilience Act | Product cybersecurity regulation; SBOM covering at least top-level dependencies[60] | In force; reporting from 2026-09-11, main obligations from 2027-12-11[15] | Legally binding for in-scope products on the EU market |
| CycloneDX, SPDX | SBOM formats: CycloneDX is ECMA-424; SPDX is ISO/IEC 5962:2021[61][62] | CycloneDX 1.7; SPDX 3.0 | Choose per consumer |
| SARIF 2.1.0 | Interchange format for static-analysis results[37] | OASIS Standard | Accepted by GitHub code scanning[63] |
Normative versus recommended. Only the CRA imposes legal duties, on in-scope products; the rest become mandatory when a customer, regulator or contract cites them.
Tooling landscape
Link to section “Tooling landscape”Choose the job first, then a tool; prices are omitted.
| Tool category | Job | Representative tools (checked 2026-10-16) | Selection criteria | Risks |
|---|---|---|---|---|
| Secret scanning | Find credentials in code and history | Gitleaks (CLI, pre-commit; “feature complete”, security patches only, maintainer moving to Betterleaks);[64] GitHub push protection[47] | Pre-push blocking, history scan, custom rules, baseline | Bypass culture; secrets in logs and images not covered |
| SAST | Find code flaws | CodeQL (“the code analysis engine developed by GitHub”);[65] Semgrep Code[49] | Languages, rule quality, diff-aware scans, SARIF output | Noise; long scan times on large repos |
| SCA and SBOM | Inventory and match components | Syft (SBOM generator);[66] Trivy;[67] Semgrep Supply Chain[49] | Ecosystems covered, reachability, SBOM formats, VEX support | Duplicate findings across tools; stale databases |
| Container and IaC scanning | Images, Dockerfiles, Terraform, Kubernetes manifests | Trivy (vuln, misconfig, secret, license scanners)[68] |
Targets, policy language, offline use | Base-image noise without --ignore-unfixed-style filtering |
| Policy as code | Custom rules for config | Conftest (deny, warn, violation rules in Rego)[69] |
Testable policies, shared libraries | Policy sprawl without tests |
| DAST | Test a running app | ZAP (“Free and open source”, branded “ZAP by Checkmarx”)[70] | Auth handling, API support, CI mode | Slow; flaky against unstable test environments |
| Signing and provenance | Integrity evidence | Sigstore cosign;[26] GitHub artifact attestations (Build L2 by itself, L3 with reusable workflows)[71] | Keyless support, verification tooling, private-repo handling | Weak verification policies; public log side effects |
Some tools phone home: Semgrep’s --config auto logs in to the Semgrep Registry
with the project URL and sends pseudonymous metrics by default;[72] check before scanning confidential code.
Automation and AI
Link to section “Automation and AI”What to automate. Deterministic checks (secret patterns, known-vulnerable versions, policy rules, signature verification). Judgement (threat modelling, risk acceptance, exploitability in context) stays with people.
AI as a source of risk. A 2021 study of an early GitHub Copilot model generated 1,689 programs from 89 security-relevant scenarios and found approximately 40% vulnerable;[73] read this as a reason to gate generated code like any other, not as a current rate. Package hallucination is a newer supply-chain vector (Playbook 4).[50] NIST SP 800-218A adds SSDF practices for developing generative-AI models;[34] securing LLM applications is covered on LLMOps.
AI as a helper. An AI-proposed fix is a code change: same review, same gates. Never let an assistant dismiss findings without a recorded human decision, or the effective false-positive metric starts measuring the model. Evaluating AI inside security operations is covered on AIOps for Security.
DevSecOps in practice
Link to section “DevSecOps in practice”Reference implementation
Link to section “Reference implementation”Design. One service (orders-service), one private repository in a GitHub Enterprise Cloud organisation, GitHub Actions, images
in GitHub Container Registry, deployment to AWS through OIDC. The structure maps to any CI system: jobs split by trust, a gate that
applies a written policy, verification before deploy. Assumptions: GitHub-hosted runners; scanners installed from pinned,
checksum-verified releases by a repository script. Constraints: replace <FULL_LENGTH_COMMIT_SHA> with the full SHA of the
release you reviewed; account IDs and names are fake. The YAML is only as strong as these settings (availability for a private
repository, October 2026):
| Setting | What it prevents | Plan |
|---|---|---|
production environment: deployment branches limited to main |
Other branches and pull_request jobs getting its OIDC subject (not pull_request_target jobs: see below) |
Pro, Team, Enterprise Cloud[74] |
production environment: required reviewers, self-review prevented, no administrator bypass |
Unreviewed or self-approved deploys | Enterprise Cloud (others: public repositories only)[74] |
| Artifact attestations | Unsigned provenance and SBOMs | Enterprise Cloud (others: public repositories only)[75] |
Ruleset on main: code-owner review; checks required, from GitHub Actions |
Merging past a red gate; unreviewed pipeline edits | Pro, Team, Enterprise Cloud[76] |
| No repository- or organisation-level deploy secrets | Same-repository pull requests reading them | All plans |
1. The pipeline.
Example status: YAML syntax-checked; not executed (requires a GitHub repository, a registry and an AWS account).
name: ci
on:
pull_request:
push:
branches: [main]
permissions: {} # nothing by default; each job asks for what it needs
jobs:
checks: # Zone 1: untrusted input, no secrets; a required status check
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
steps:
- uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA> # pinned; note the release
with:
fetch-depth: 0
persist-credentials: false
- name: Gate, scanner rules and installer from the base commit, not from this PR
run: |
mkdir -p "$RUNNER_TEMP/base"
git archive "$BASE_SHA" .ci | tar -x -C "$RUNNER_TEMP/base"
echo "CI_DIR=$RUNNER_TEMP/base/.ci" >> "$GITHUB_ENV"
- name: Install pinned scanners (checksums verified)
run: bash "$CI_DIR/install-scanners.sh"
- name: Secret scan of this pull request's commits
run: >
gitleaks git --log-opts="$BASE_SHA..HEAD" --redact --exit-code 0
--report-format sarif --report-path gitleaks.sarif
- name: SAST, new findings relative to the base commit
run: >
semgrep scan --config "$CI_DIR/semgrep/" --baseline-commit "$BASE_SHA"
--sarif --output semgrep.sarif
- name: Dependencies, secrets and IaC misconfiguration (whole tree)
run: >
trivy fs --scanners vuln,secret,misconfig
--format sarif --output trivy.sarif .
- name: Proportionate gate
run: >
python3 "$CI_DIR/security_gate.py"
--policy "$CI_DIR/gate-policy.json"
--baseline "$CI_DIR/baseline.json"
--exceptions "$CI_DIR/exceptions.json"
gitleaks.sarif semgrep.sarif trivy.sarif
- name: Kubernetes manifest policy (a deliberate hard gate)
if: ${{ !cancelled() }} # report it even when the gate has already blocked
run: conftest test deploy/ --policy "$CI_DIR/policy"
build: # Zone 2: main only; Docker and the reviewed Dockerfile
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA>
with:
persist-credentials: false
- name: Build and push by digest
id: push
env:
IMAGE: ghcr.io/example-org/orders-service
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$REGISTRY_TOKEN" \
| docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
docker build -t "$IMAGE:$GITHUB_SHA" .
docker push "$IMAGE:$GITHUB_SHA"
ref="$(docker inspect --format '{{index .RepoDigests 0}}' "$IMAGE:$GITHUB_SHA")"
echo "digest=${ref#*@}" >> "$GITHUB_OUTPUT"
scan: # Zone 2: third-party scanners with a read-only token
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
env:
REF: ghcr.io/example-org/orders-service@${{ needs.build.outputs.digest }}
steps:
- uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA>
with:
persist-credentials: false
- name: Install pinned scanners (checksums verified)
run: ./.ci/install-scanners.sh
- name: Full image scan (recorded) and SBOM from the pushed image
env:
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$REGISTRY_TOKEN" \
| docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
trivy image --format sarif --output image.sarif "$REF"
syft "$REF" -o cyclonedx-json=sbom.cdx.json
- uses: actions/upload-artifact@<FULL_LENGTH_COMMIT_SHA>
with:
name: scan-results
path: |
sbom.cdx.json
image.sarif
attest: # Zone 2: signing identity; GitHub-owned actions only
needs: [build, scan]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # push attestations to the registry
id-token: write
attestations: write
artifact-metadata: write
env:
IMAGE: ghcr.io/example-org/orders-service
steps:
- name: Registry login for push-to-registry
env:
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "$REGISTRY_TOKEN" \
| docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
- uses: actions/download-artifact@<FULL_LENGTH_COMMIT_SHA>
with:
name: scan-results
- name: Attest build provenance
uses: actions/attest@<FULL_LENGTH_COMMIT_SHA> # v4
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ needs.build.outputs.digest }}
push-to-registry: true
- name: Attest the SBOM
uses: actions/attest@<FULL_LENGTH_COMMIT_SHA> # v4
with:
subject-name: ${{ env.IMAGE }}
subject-digest: ${{ needs.build.outputs.digest }}
sbom-path: sbom.cdx.json
push-to-registry: true
deploy: # Zone 3: approved environment, verify before deploy
needs: [build, attest]
runs-on: ubuntu-latest
environment: production # main only, reviewers, no administrator bypass
permissions:
contents: read
packages: read
attestations: read
id-token: write
env:
REF: ghcr.io/example-org/orders-service@${{ needs.build.outputs.digest }}
steps:
- uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA> # for deploy/deploy.sh
with:
persist-credentials: false
- name: Verify provenance and signer identity
env:
GH_TOKEN: ${{ github.token }}
WORKFLOW: ${{ github.repository }}/.github/workflows/ci.yml
run: |
echo "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
gh attestation verify "oci://$REF" --bundle-from-oci \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "https://github.com/$WORKFLOW@refs/heads/main" \
--deny-self-hosted-runners
- name: Assume the production deploy role (OIDC, no stored keys)
uses: aws-actions/configure-aws-credentials@<FULL_LENGTH_COMMIT_SHA>
with:
role-to-assume: arn:aws:iam::111122223333:role/orders-deploy-production
aws-region: eu-west-1
- name: Deploy by digest
run: ./deploy/deploy.sh "$REF"
Why it is built this way:
permissions: {}plus per-job grants follow GitHub’s advice to giveGITHUB_TOKEN“the minimum required permissions”.[42] Third-party scanners run only where the token can at most read (checks,scan); that matters because Syft, for one, reads the registry credential thatdocker loginstores.[77]buildcan push but runs only Docker and the reviewed Dockerfile.- Residual risks. First, every job with
id-token: write(attest, anddeploywith its AWS action) could obtain a Sigstore certificate with the same identity,ci.yml@refs/heads/main.[52] The deploy job cannot publish an attestation (noattestations: writeorpackages: write). To give signing its own identity, move the attest steps into a reusable workflow and pin it at verification with--signer-workflow, also GitHub’s route to SLSA Build Level 3.[71][78] Second, an attestation vouches for who signed the SBOM, not for the generator’s honesty: a compromised Syft in the scan job could handattesta falsified SBOM. Pin and checksum-verify the generator, and weigh the SBOM attestation below the build provenance, whichattestgenerates itself.[79] - The gate script, policy, baseline and exceptions, the Semgrep rules, the Conftest policy and the scanner installer all come from
.ci/at the base commit, so a pull request cannot loosen them; an exception lands in its own reviewed pull request first. Bootstrap:git archivefails while the base commit has no.ci/, so merge.ci/in a reviewed pull request before makingchecksrequired. A pull request can still change what scanners report: by editingci.yml, with inline suppressions (nosemgrep,gitleaks:allow), or with ignore and config files read from the checkout (.semgrepignore,.gitleaksignore,.gitleaks.toml,.trivyignore,trivy.yaml).[80][64][81] So the ruleset requires code-owner review (CODEOWNERS below covers those files; reviewers treat a new inline suppression as an exception request) and makeschecksa required status check whose source is GitHub Actions (any writer “can set the state of any status check” otherwise).[76] CODEOWNERS cannot express a risk threshold, so AppSec approves every exception here, stricter than the RACI. - Untrusted values reach shell commands through environment variables, GitHub’s pattern against script injection.[42]
- Gitleaks scans only the pull request’s commits and exits 0 so the gate, not each tool, decides (it otherwise exits 1 on
leaks).[64] Trivy scans the whole tree, quick for lockfiles; the baseline stops old findings blocking. Conftest is the
deliberate exception, a hard gate: its few rules are deterministic and fixable in the same change. If they need baselines or
exceptions, write SARIF (
--output sarif) and pass it to the gate.[82] The image scan on main is recorded, not gated: the change is already merged. actions/attestfollows GitHub’s example,[75] plusartifact-metadata: write, which its README lists for registry storage records;[79] alone it gives SLSA Build Level 2.[71]gh attestation verifyenforces thehttps://slsa.dev/provenance/v1predicate by default,--cert-identitypins the exact certificate SubjectAlternativeName,--deny-self-hosted-runnersrejects self-hosted builds, and--bundle-from-ocireads the bundle from the registry, which needs a registry login.[78]
Example status: illustrative file; not executed.
# .github/CODEOWNERS (enforced by the ruleset's "Require review from Code Owners")
/.github/CODEOWNERS @example-org/appsec
/.github/workflows/ @example-org/appsec
/.ci/ @example-org/appsec
# Files that change what scanners report, in any directory
.gitleaks.toml @example-org/appsec
.gitleaksignore @example-org/appsec
.semgrepignore @example-org/appsec
.trivyignore @example-org/appsec
trivy.yaml @example-org/appsec
2. The cloud trust policy. The role trusts only tokens from jobs that reference the production environment of this repository.
Example status: JSON syntax-checked; not executed. Repositories created after 2026-07-15 use an immutable subject format that
includes owner and repository IDs; read the sub claim from a real token before writing the condition.[83]
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated":
"arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub":
"repo:example-org/orders-service:environment:production"
}
}
}
]
}
GitHub documents the environment subject format repo:ORG-NAME/REPO-NAME:environment:ENVIRONMENT-NAME.[83] Its AWS guide
also shows a StringLike wildcard such as repo:octo-org/octo-repo:*;[43] that form lets any branch, tag or pull-request
job in the repository assume the role, so never use it for a production role.
The environment subject is only as strong as the environment’s settings (table above). Protection rules must pass “before a job referencing the environment can proceed”,[74] so:
- With deployment branches at “No restriction”, a job on any branch, a same-repository pull-request branch included, can declare
environment: productionand receive this subject (“Protected branches only” lets every branch deploy if no branch has protection rules).[74] - A
pull_request_targetjob passes amain-only limit: since December 2025 GitHub evaluates its environment rules against the default branch.[84] Keep such workflows away from deploy environments. - On Free, Pro and Team, required reviewers “are only available for public repositories”, and “By default, administrators can
bypass the protection rules”.[74] Without reviewers, anyone who can merge to
maincan deploy.
To bind the role to one workflow file too, add job_workflow_ref to the subject template; the new format replaces the default for
the repository, so update every dependent trust policy.[83]
3. The gate policy, baseline and exceptions. Three JSON files under .ci/, read from the base commit; format in Lab 2. The
policy blocks new high or critical findings, warns on medium, and always blocks a new secret.
4. A manifest policy (Conftest).
Example status: not executed; Rego v1 syntax as in Conftest’s documentation,[69] plus OPA’s object.get built-in.
package main
deny contains msg if {
input.kind == "Deployment"
some c in input.spec.template.spec.containers
not contains(c.image, "@sha256:")
msg := sprintf("container %s must reference its image by digest", [c.name])
}
deny contains msg if {
input.kind == "Deployment"
pod := input.spec.template.spec
some c in pod.containers
not non_root(c, pod)
msg := sprintf("container %s must run with runAsNonRoot: true", [c.name])
}
# A container-level setting wins; without one, the pod-level setting applies.
non_root(c, _) if c.securityContext.runAsNonRoot == true
non_root(c, pod) if {
object.get(c, ["securityContext", "runAsNonRoot"], null) == null
pod.securityContext.runAsNonRoot == true
}
This catches the problem in the pull request. Enforcing the same rules at cluster admission, so nothing bypasses the pipeline, is KubernetesSecOps territory.
Hands-on labs
Link to section “Hands-on labs”Lab 1 needs only a whiteboard; Lab 2 was executed for this page; Labs 3 and 4 were not executed here, so their expected results are patterns.
Lab 1 (beginner): threat-model your own pipeline
Link to section “Lab 1 (beginner): threat-model your own pipeline”Objective: find which pipeline jobs can reach which credentials, and the three changes that most reduce risk. Prerequisites: read access to one repository’s CI configuration and its settings (secrets, environments, runners); 60–90 minutes; no cost or risk.
- List every job: trigger (push, pull request, schedule, manual), runner type (hosted or self-hosted), token permissions.
- For each job, list every secret and cloud role it can reach, including organisation-level secrets inherited by the repository.
- List every third-party step and how it is referenced: tag, branch or full commit SHA.
- Place each job in a trust zone from the trust-zone figure.
- For each job, ask the four questions of the Threat Modeling Manifesto[36] and check it against the OWASP CI/CD Top 10 (Playbook 5).[40]
Expected output: a table of jobs with trigger, zone, reachable secrets, unpinned steps and a risk note, plus three ranked actions. Validation: no job triggered by untrusted input can reach a secret (count repository- and organisation-level secrets too); no job holding a secret or write token runs an unpinned third-party step; every cloud role names a protected branch or environment. Troubleshooting: if you cannot tell which secrets a job can read, that is your first finding. Teardown: none; file the actions as tickets.
Lab 2 (intermediate): build and test a proportionate gate
Link to section “Lab 2 (intermediate): build and test a proportionate gate”Objective: turn a written gate policy into code and watch how baselines, severities, secrets and expiring exceptions change the decision. Prerequisites: Python 3.8 or later; no network, cloud account or cost; 45 minutes. Status: executed (output below).
- Save the script below as
security_gate.py. - Save the five JSON files that follow as
gate-policy.json,baseline.json,exceptions.json,sast.sarifandsca.sarif: minimal hand-written SARIF 2.1.0, with GitHub’ssecurity-severitybands (“over 9.0 iscritical”, 7.0–8.9 high, 4.0–6.9 medium) andpartialFingerprintsto match results across runs.[63][37] - Run the gate for 16 October 2026.
- Run it again “on 1 December 2026”, after the exception has expired.
- Simulate fixing the SQL injection and removing the secret, and run again.
Example status: executed with Python 3.8.10 (re-run on 2026-10-17 after line wrapping); output below is captured.
#!/usr/bin/env python3
"""Proportionate security gate: decide BLOCK / WARN / PASS from SARIF results.
Usage: python3 security_gate.py --policy gate-policy.json --baseline baseline.json \
--exceptions exceptions.json [--today YYYY-MM-DD] \
results1.sarif [results2.sarif ...]
Exit codes: 0 = pass (warnings allowed), 1 = at least one BLOCK, 2 = invalid input.
Standard library only (Python 3.8+).
"""
import argparse
import datetime as dt
import hashlib
import json
import sys
RANK = {"none": 0, "low": 1, "medium": 2, "high": 3, "critical": 4}
LEVEL_TO_SEVERITY = {"error": "high", "warning": "medium", "note": "low", "none": "none"}
def severity_from_score(score):
# GitHub code scanning bands: over 9.0 critical, 7.0-8.9 high, 4.0-6.9 medium,
# 0.1-3.9 low.
if score > 9.0:
return "critical"
if score >= 7.0:
return "high"
if score >= 4.0:
return "medium"
return "low" if score > 0 else "none"
def fingerprint(tool, result):
fps = result.get("partialFingerprints") or {}
if fps:
key = sorted(fps)[0]
return "%s:%s" % (tool, fps[key])
# Fallback: rule + file + line. Line numbers move when code is edited,
# so prefer tools that emit fingerprints.
loc = (result.get("locations") or [{}])[0].get("physicalLocation", {})
uri = loc.get("artifactLocation", {}).get("uri")
line = loc.get("region", {}).get("startLine")
raw = "%s|%s|%s|%s" % (tool, result.get("ruleId"), uri, line)
return "%s:sha1:%s" % (tool, hashlib.sha1(raw.encode()).hexdigest()[:16])
def load_json(path):
with open(path, encoding="utf-8") as fh:
return json.load(fh)
def load_findings(path):
sarif = load_json(path)
findings = []
for run in sarif.get("runs", []):
tool = run["tool"]["driver"]["name"]
rules = {r["id"]: r for r in run["tool"]["driver"].get("rules", [])}
for res in run.get("results", []):
rule = rules.get(res.get("ruleId"), {})
props = rule.get("properties", {})
if "security-severity" in props:
sev = severity_from_score(float(props["security-severity"]))
else:
default = rule.get("defaultConfiguration", {}).get("level", "warning")
level = res.get("level") or default
sev = LEVEL_TO_SEVERITY.get(level, "medium")
loc = (res.get("locations") or [{}])[0].get("physicalLocation", {})
findings.append({
"tool": tool,
"rule": res.get("ruleId", "?"),
"severity": sev,
"tags": set(props.get("tags", [])),
"where": "%s:%s" % (loc.get("artifactLocation", {}).get("uri", "?"),
loc.get("region", {}).get("startLine", "?")),
"fp": fingerprint(tool, res),
})
return findings
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--policy", required=True)
ap.add_argument("--baseline", required=True)
ap.add_argument("--exceptions", required=True)
ap.add_argument("--today", default=dt.date.today().isoformat())
ap.add_argument("sarif", nargs="+")
a = ap.parse_args()
try:
policy = load_json(a.policy)
baseline = set(load_json(a.baseline)["fingerprints"])
exceptions = load_json(a.exceptions)["exceptions"]
today = dt.date.fromisoformat(a.today)
findings = [f for p in a.sarif for f in load_findings(p)]
except (OSError, ValueError, KeyError) as err:
print("INVALID INPUT: %s" % err)
return 2
block_at = RANK[policy["block_at_or_above"]]
warn_at = RANK[policy["warn_at_or_above"]]
always = set(policy.get("always_block_tags", []))
hard_tools = {t.lower() for t in policy.get("always_block_tools", [])}
max_days = int(policy.get("max_exception_days", 90))
active, problems = {}, []
for ex in exceptions:
expires = dt.date.fromisoformat(ex["expires"])
granted = dt.date.fromisoformat(ex["granted"])
if (expires - granted).days > max_days:
problems.append("exception %s exceeds %d days"
% (ex["fingerprint"], max_days))
elif not ex.get("owner") or not ex.get("reason"):
problems.append("exception %s lacks owner or reason" % ex["fingerprint"])
elif expires < today:
problems.append("exception %s expired on %s"
% (ex["fingerprint"], ex["expires"]))
else:
active[ex["fingerprint"]] = ex
counts = {"BLOCK": 0, "WARN": 0, "EXCEPTED": 0, "BASELINE": 0, "INFO": 0}
for f in sorted(findings, key=lambda x: (-RANK[x["severity"]], x["tool"], x["rule"])):
rank, is_new = RANK[f["severity"]], f["fp"] not in baseline
hard = bool(f["tags"] & always) or f["tool"].lower() in hard_tools
if f["fp"] in active and not hard:
decision = "EXCEPTED"
elif hard and is_new:
decision = "BLOCK"
elif not is_new and policy.get("block_only_new", True):
decision = "BASELINE"
elif rank >= block_at:
decision = "BLOCK"
elif rank >= warn_at:
decision = "WARN"
else:
decision = "INFO"
counts[decision] += 1
print("%-8s %-8s %-15s %-14s %s"
% (decision, f["severity"], f["tool"], f["rule"], f["where"]))
for p in problems:
print("NOTE %s" % p)
print("SUMMARY " + " ".join("%s=%d" % kv for kv in counts.items()))
return 1 if counts["BLOCK"] else 0
if __name__ == "__main__":
sys.exit(main())
gate-policy.json, baseline.json and exceptions.json:
{
"version": 1,
"block_at_or_above": "high",
"warn_at_or_above": "medium",
"block_only_new": true,
"always_block_tags": ["secret"],
"always_block_tools": ["gitleaks"],
"max_exception_days": 90
}
{"fingerprints": ["example-sast:sast-c3", "example-sca:sca-e5"]}
{
"exceptions": [
{
"fingerprint": "example-sca:sca-f6",
"reason": "Vulnerable function not called; upgrade needs framework 5.x migration",
"owner": "team-orders",
"ticket": "SEC-123",
"granted": "2026-10-01",
"expires": "2026-11-30"
}
]
}
sast.sarif (a SAST run and a secrets run) and sca.sarif (fake CVE identifiers in the reserved-looking 2099 range):
{
"version": "2.1.0",
"runs": [
{
"tool": {"driver": {"name": "example-sast", "rules": [
{"id": "SQLI-001",
"properties": {"security-severity": "8.8", "tags": ["security"]}},
{"id": "XSS-002",
"properties": {"security-severity": "6.1", "tags": ["security"]}},
{"id": "WEAKHASH-003",
"properties": {"security-severity": "5.3", "tags": ["security"]}}
]}},
"results": [
{"ruleId": "SQLI-001", "level": "error",
"message": {"text": "User input reaches SQL query"},
"partialFingerprints": {"primaryLocationLineHash": "sast-a1"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "app/orders.py"},
"region": {"startLine": 42}}}]},
{"ruleId": "XSS-002", "level": "warning",
"message": {"text": "Unescaped output in template"},
"partialFingerprints": {"primaryLocationLineHash": "sast-b2"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "web/cart.html"},
"region": {"startLine": 17}}}]},
{"ruleId": "WEAKHASH-003", "level": "warning",
"message": {"text": "MD5 used for a token"},
"partialFingerprints": {"primaryLocationLineHash": "sast-c3"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "app/legacy.py"},
"region": {"startLine": 88}}}]}
]
},
{
"tool": {"driver": {"name": "example-secrets", "rules": [
{"id": "aws-access-key",
"properties": {"tags": ["security", "secret"]}}
]}},
"results": [
{"ruleId": "aws-access-key", "level": "warning",
"message": {"text": "Possible access key"},
"partialFingerprints": {"primaryLocationLineHash": "sec-d4"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "config/dev.env"},
"region": {"startLine": 3}}}]}
]
}
]
}
{
"version": "2.1.0",
"runs": [
{
"tool": {"driver": {"name": "example-sca", "rules": [
{"id": "CVE-2099-0001",
"properties": {"security-severity": "9.8", "tags": ["security"]}},
{"id": "CVE-2099-0002",
"properties": {"security-severity": "7.5", "tags": ["security"]}},
{"id": "CVE-2099-0003",
"properties": {"security-severity": "3.1", "tags": ["security"]}}
]}},
"results": [
{"ruleId": "CVE-2099-0001",
"message": {"text": "examplelib 1.2.0 is affected"},
"partialFingerprints": {"primaryLocationLineHash": "sca-e5"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "requirements.txt"},
"region": {"startLine": 12}}}]},
{"ruleId": "CVE-2099-0002",
"message": {"text": "parserlib 3.0.1 is affected"},
"partialFingerprints": {"primaryLocationLineHash": "sca-f6"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "requirements.txt"},
"region": {"startLine": 19}}}]},
{"ruleId": "CVE-2099-0003",
"message": {"text": "colourlib 0.9.0 is affected"},
"partialFingerprints": {"primaryLocationLineHash": "sca-g7"},
"locations": [{"physicalLocation": {
"artifactLocation": {"uri": "requirements.txt"},
"region": {"startLine": 23}}}]}
]
}
]
}
Example status: syntax-checked with bash -n; the commands were executed and printed the output shown below.
python3 security_gate.py --policy gate-policy.json --baseline baseline.json \
--exceptions exceptions.json --today 2026-10-16 sast.sarif sca.sarif; echo "exit=$?"
python3 security_gate.py --policy gate-policy.json --baseline baseline.json \
--exceptions exceptions.json --today 2026-12-01 sast.sarif sca.sarif; echo "exit=$?"
Expected output (step 3, captured):
BASELINE critical example-sca CVE-2099-0001 requirements.txt:12
BLOCK high example-sast SQLI-001 app/orders.py:42
EXCEPTED high example-sca CVE-2099-0002 requirements.txt:19
BASELINE medium example-sast WEAKHASH-003 app/legacy.py:88
WARN medium example-sast XSS-002 web/cart.html:17
BLOCK medium example-secrets aws-access-key config/dev.env:3
INFO low example-sca CVE-2099-0003 requirements.txt:23
SUMMARY BLOCK=2 WARN=1 EXCEPTED=1 BASELINE=2 INFO=1
exit=1
Step 4 prints BLOCK high example-sca CVE-2099-0002, a line NOTE exception example-sca:sca-f6 expired on 2026-11-30 and
SUMMARY BLOCK=3 … EXCEPTED=0, exit 1. Step 5 (remove the SQLI-001 result and the example-secrets run from sast.sarif) prints
SUMMARY BLOCK=0 WARN=1 EXCEPTED=1 BASELINE=2 INFO=1 and exit 0. A missing input file prints INVALID INPUT: … and exits 2; an
exception longer than 90 days prints NOTE exception … exceeds 90 days and is not applied.
Validation. Explain each line: why critical CVE-2099-0001 does not block (baseline debt), why a medium-severity secret does
(the secret tag), and why the exception stops applying after expiry. Troubleshooting. KeyError: 'fingerprints': the baseline
lacks that key. Everything blocks on an existing repository: generate a baseline from main first. A finding reappears as new after a
refactor: its fingerprint changed; prefer tools with stable partialFingerprints.
Teardown: delete the directory.
Lab 3 (intermediate): scan a throwaway repository locally
Link to section “Lab 3 (intermediate): scan a throwaway repository locally”Objective: produce SARIF and CycloneDX output from secret scanning, dependency scanning and SBOM generation, and feed it to Lab 2. Prerequisites: Gitleaks, Trivy and Syft from their official releases (verify checksums); Git; Python 3; network access for Trivy’s database; 30 minutes. Risk: none if you never push this repository. Status: not executed; flags from each tool’s documentation, 2026-10-16.[64][68][66]
Example status: syntax-checked with bash -n; not executed (the tools are not installed in the authoring environment).
#!/usr/bin/env bash
set -euo pipefail
mkdir lab3 && cd lab3 && git init -q
# A fake, token-shaped string generated locally; it grants nothing.
FAKE="ghp_$(python3 -c 'import random,string
print("".join(random.choices(string.ascii_letters + string.digits, k=36)))')"
printf 'GITHUB_TOKEN=%s\n' "$FAKE" > settings.env
printf 'pyyaml==5.3\n' > requirements.txt # deliberately old version
git add . && git commit -qm "lab 3 seed"
gitleaks git --report-format sarif --report-path gitleaks.sarif --redact \
|| echo "gitleaks exit=$?"
trivy fs --scanners vuln,secret --format sarif --output trivy.sarif . \
|| echo "trivy exit=$?"
syft . -o cyclonedx-json=sbom.cdx.json
python3 -c 'import json; d = json.load(open("sbom.cdx.json"))
print(d["bomFormat"], len(d.get("components", [])))'
Expected output (pattern, not captured). Gitleaks reports one leak in settings.env and exits 1.[64] trivy.sarif
lists vulnerabilities for the old PyYAML, which should include CVE-2020-14343 (critical below 5.4 in GitHub’s advisory
database[85]). The last line prints CycloneDX and a component count. Validation: feed both SARIF files to Lab 2’s
script with an empty baseline ({"fingerprints": []}): the secret must block (check the tool name and tags the SARIF carries).
Troubleshooting: Trivy cannot fetch its database behind a proxy: set proxy variables; Gitleaks finds nothing: was the file
committed? Teardown: cd .. && rm -rf lab3; never push it.
Lab 4 (advanced): sign, attest and verify in a sandbox repository
Link to section “Lab 4 (advanced): sign, attest and verify in a sandbox repository”Objective: attest provenance and an SBOM for an image, and prove verification rejects the wrong signer. Prerequisites: a
throwaway repository with the build, scan and attest jobs from the reference implementation, the
GitHub CLI, Docker. Side effects: in a public repository, attestations go to a public transparency log[71]
whose entries “cannot be removed later”;[86] private repositories need GitHub Enterprise Cloud for
attestations.[75] Status: not executed here.
-
Push the workflow and let the jobs run on
main; note the digest the build job prints. -
Log in to the registry (verifying an OCI URL requires it[78]) and verify the positive case (replace the placeholders):
docker login ghcr.io # a token that can read the package gh attestation verify "oci://ghcr.io/OWNER/REPO@DIGEST" --repo "OWNER/REPO" \ --cert-identity \ "https://github.com/OWNER/REPO/.github/workflows/ci.yml@refs/heads/main" -
Verify the SBOM attestation by adding
--predicate-typewith the SBOM’s predicate type (the default is the SLSA provenance type).[78] -
Negative test: repeat step 2 with
@refs/heads/otherin--cert-identity. Verification must fail. -
Tamper test: build a different image locally, push it under the same tag, and verify by tag. Verification must fail because no attestation exists for the new digest.
Expected output (pattern): step 2 succeeds and names the workflow; steps 4 and 5 exit non-zero. Validation: your deploy job
runs the same check and stops on failure. Troubleshooting: “no attestations found”: the tag now points at another digest, or
(with --bundle-from-oci) push-to-registry was off; an authentication error: no registry login. Teardown: delete the package
versions and repository; public log entries remain.
Use cases and scenarios
Link to section “Use cases and scenarios”Three sourced cases, two labelled illustrations.
Case 1 (sourced): a CI dependency leaks secrets into logs. Problem: in March 2025 the GitHub Action tj-actions/changed-files was compromised; CISA said the compromise allowed “information disclosure of secrets” and was “potentially enabled by a compromise of another GitHub Action, reviewdog/action-setup@v1”. Affected runs fell between 12 and 15 March 2025, and CISA told users to rotate the exposed secrets (the worked runbook follows its advice).[87] Method: pin actions by full SHA or lockfile,[42] give jobs that run third-party steps no secrets, use OIDC instead of stored keys, keep CI logs private. Trade-off: pinned steps need an update bot proposing reviewed SHA bumps. Success criteria: within two hours you can list which runs used which commit and which secrets they could read. Pitfall: rotating secrets without checking whether they were used meanwhile.
Case 2 (sourced): the security scanner is the attack vector. Problem: on 19 March 2026 an attacker force-pushed tags in the
trivy-action and setup-trivy repositories and published a malicious Trivy v0.69.4; Aqua contained it the same evening, and
malicious Docker Hub images followed on 22 March. Almost all trivy-action version tags were rewritten; the advisory gives two
reasons why one was not (it already pointed at the commit the attacker built on; immutable releases) and warns that GitHub’s
“Immutable” badge “does not prevent tag force-pushing”. Its instruction: “all secrets accessible to affected pipelines must be
treated as exposed and rotated immediately”.[88] Method: as Case 1, and treat security tools as untrusted code.
Trade-off: security teams like to give their own tools broad access; this case argues the opposite. Success criteria: a
compromised scanner can read source but reach no secret, write token or signing identity.
Case 3 (sourced): “are we running the backdoored version?” Problem: on 29 March 2024 CISA reported malicious code in XZ Utils 5.6.0 and 5.6.1 (CVE-2024-3094) and advised downgrading to an uncompromised version such as 5.4.6.[7] Method: with an SBOM per deployed artefact, the question becomes a query across stored SBOMs; without them, teams inspect images by hand. Trade-off: SBOMs did not prevent this: a malicious release is not a known vulnerability until disclosed. Their value is response speed. Success criteria: the list of affected deployed artefacts within hours; the fix tracked by VulnerabilityOps and rolled out via PatchOps or an image rebuild.
Measure, govern, pay
Link to section “Measure, govern, pay”Metrics and KPIs
Link to section “Metrics and KPIs”No standards body defines DevSecOps metrics. This Atlas proposes the set below around two questions: are fewer exploitable defects reaching production, and is the security machinery trusted and cheap enough to keep? Watch DevOps delivery metrics alongside them.
| KPI | Definition and formula | Data source | Pitfall | Review |
|---|---|---|---|---|
| Effective false-positive rate (per rule) | Findings dismissed or overridden without a fix ÷ findings triaged, last 30 days[30] | Findings store | Bulk dismissals by scripts | Monthly, per rule |
| Gate override rate | Blocked merges that were bypassed or broke glass ÷ blocked merges | CI and Git platform audit log | Overrides hidden as “re-run until green” | Monthly |
| New-finding fix time | Median and 90th percentile, first detection to verified fix, for findings introduced by a change | Findings store | Mixing in baseline debt | Monthly |
| Escape rate | Post-release security defects a pipeline control class could have caught ÷ all post-release security defects | Root-cause tags on incidents, pen tests, bug bounty (SSDF RV.3) | Fewer escapes can mean less testing | Quarterly |
| Provenance coverage | Production deploys whose artefact passed signature and provenance verification ÷ all production deploys | Deploy logs | Counting “signed” without “verified” | Monthly |
| Pipeline security overhead | Minutes added to the critical path by security steps (median, 90th percentile) | CI timings | Ignoring queue time | Monthly |
| Exception health | Expired-but-open exceptions; exceptions renewed more than once | Exceptions register | Renewals rubber-stamped | Monthly |
| Pinned third-party references | Third-party steps pinned to a full SHA or lockfile ÷ all third-party steps | Workflow files; OpenSSF Scorecard Pinned-Dependencies[51] | Pinned but never updated | Quarterly |
| Secret exposure and rotation | Secrets that reached a remote per month; median time from detection to rotation | Secret-scanning alerts | Counting detections, not rotations | Monthly |
Leading and lagging. Override rate, effective false positives, pinning and overhead lead; escape rate and fix time lag. The baseline backlog belongs to VulnerabilityOps.
Worked calculation (illustrative numbers for one month at one organisation).
| Input | Value |
|---|---|
| Pull requests merged | 1,240 |
| Pull requests blocked by the gate | 96 |
| Blocked pull requests overridden | 22 |
Findings triaged for SAST rule X-17 |
140, of which 61 dismissed without a fix |
| Production deployments / verified before deploy | 412 / 398 |
| Post-release security defects / of a class a pipeline control should catch | 9 / 3 |
- Gate override rate = 22 ÷ 96 = 22.9%. Far above a 5% target: the gate is not trusted.
- Effective false-positive rate of
X-17= 61 ÷ 140 = 43.6%. Far above the 10% bar for review comments (Playbook 3), so it should neither block nor comment; demote it to a backlog report and tune it. - Provenance coverage = 398 ÷ 412 = 96.6%. The 14 unverified deployments came from one legacy pipeline: a named, fixable gap.
- Escape rate = 3 ÷ 9 = 33.3%. Root-cause the three: was the control absent, mis-tuned, or overridden?
Together: the override rate and one noisy rule explain most of the friction; the fix is tuning, not more tools.
Target setting. Compare each service with its own history; use Playbook 3’s thresholds for rule confidence; agree overhead budgets with teams (for example, five added minutes at the 90th percentile).
Vanity metrics and traps. Scans run, findings found and “repositories with a scanner” rise with effort, not outcomes; “zero critical vulnerabilities” rewards suppressing or not looking.
Data limitations. Unstable fingerprints inflate “new” findings; dismissal reasons depend on culture; escapes depend on how hard you look after release. State these limits on every dashboard.
Quality, security, privacy and compliance
Link to section “Quality, security, privacy and compliance”Least privilege for pipeline identities. Bind every cloud role to the narrowest OIDC subject, a protected branch or a protected
environment (settings in the reference implementation), never a repository
wildcard.[83][43] GitHub repositories
created after 15 July 2026 use an immutable default subject that includes owner and repository IDs
(repo:OWNER@OWNER-ID/REPO@REPO-ID:…); older repositories keep the previous format unless they opt in (which changes the expected sub), and GitHub Enterprise Server does not offer it.[83]
Separation of duties. OWASP’s A03:2025 guidance says no single person should be able to write code and push it to production without oversight;[17] SLSA’s Source L4 is “Two-party review” of changes to protected branches.[56] Recorded peer review in the pull request usually meets this without a separate approval board (evidence on change approval: DevOps).
Privacy side effects of signing. Keyless signing writes events to Rekor “so that the events are publicly auditable”.[26] Email-based identities put the user’s email in the certificate,[52] which cosign warns “will be stored in public transparency logs and cannot be removed later”.[86] Sign from CI with workload identity, not personal accounts; GitHub’s private-repository Sigstore instance has no transparency log.[71]
Secrets in security output. Secret-scanning reports contain the secrets they found: redact (Gitleaks --redact)[64] and
treat SARIF artefacts as confidential.
Evidence retention. SSDF PS.3 is “Archive and Protect Each Software Release”, including integrity and provenance data.[32] Keep SBOMs, attestations and gate decisions for at least each release’s supported life. Under the EU CRA the SBOM need not be published, but market-surveillance authorities can request it.[60]
Regulation and jurisdiction. The CRA is binding EU law for in-scope products with digital elements: reporting obligations have applied since 11 September 2026; the main obligations, including the SBOM, apply from 11 December 2027.[15] In the US, the SSDF maps its practices to Executive Order 14028 §4e,[13] but OMB’s M-26-05 (January 2026) rescinded M-22-18, the 2022 memo behind the federal attestation form, and its companion M-23-16; agencies now set risk-based requirements and may still ask for the form or an SBOM, so read your contracts.[16] This is not legal advice; scope and evidence mapping belong to ComplianceOps.
Cost, capacity and sustainability
Link to section “Cost, capacity and sustainability”Cost drivers. Developer triage time (dominant when rules are noisy), runner minutes, licences, AppSec and champion time, evidence storage, and the cost of delay when gates block.
Worked example (illustrative). In the metrics example, rule X-17’s 61 dismissed findings at 15 minutes each cost about 15
developer-hours a month and changed nothing. Security steps adding a median 3.2 minutes to 1,240 pull requests use about 3,968
runner-minutes (66 hours) a month; whether that matters depends on whether people wait. Tuning X-17 is the cheapest improvement.
Capacity. Size AppSec by the rules and teams it supports, not by findings: a team that triages every finding grows its workload with every developer hired.
Efficiency and sustainability. Scan only what changed in pull requests where the tool supports it; run full image scans on main and on a schedule; cache vulnerability databases; skip documentation-only changes. Fewer, better rules use less compute and attention.
Assurance of the operating model
Link to section “Assurance of the operating model”- Quarterly trace audit. Trace ten random production deployments from the running digest back to provenance, SBOM, gate decision, review and commit. Any break in the chain is a finding.
- Exception audit. Sample ten exceptions: owner, a reason a peer would accept, ticket, expiry, and was the expiry honoured?
- Pipeline compromise drill. Run the worked runbook as a tabletop twice a year.
- Adversarial test. In a sandbox, try poisoned pipeline execution (a same-repository pull request that edits the workflow, a build script or an ignore file) and check that it reaches no secret and cannot pass the gate unreviewed.[40]
- Risk indicators to watch: rising override rate; exceptions renewed repeatedly; new unpinned third-party steps; new organisation-level secrets; scanners silently failing (zero findings for weeks is a signal, not a success).
When things go wrong
Link to section “When things go wrong”Common failures and troubleshooting
Link to section “Common failures and troubleshooting”| Symptom | Likely cause | Diagnosis | Remediation | Prevention |
|---|---|---|---|---|
| Developers routinely bypass the gate | Noisy rules block merges | Override rate per rule | Demote rules above the confidence bar; baseline old findings | Measure effective false positives before blocking |
| Every pull request fails on old findings | No baseline, or unstable fingerprints | Compare fingerprints across two runs of main | Baseline main; prefer stable partialFingerprints[63] |
Baseline at rollout |
| Builds fail overnight with no code change | New CVE for an existing dependency; gate blocks old findings | Is the finding new to this change? | Block only findings the change introduced; queue the rest | Separate “new in diff” from “newly disclosed” |
| Deploy verification fails after a CI refactor | Workflow file renamed, so the certificate identity changed | Compare the expected and actual SAN | Update the expected identity through a reviewed change | Keep expected identities in versioned config |
| A secret leaked despite push protection | Bypass with “I’ll fix it later”, or the secret was in logs or an image layer | Open push-protection alerts; image history | Rotate first[47] | Delegated bypass; scan images and logs |
| A cloud role was assumed from an unexpected branch | Wildcard sub, or an environment with no branch limit |
Cloud audit log for web-identity role assumptions | Narrow the condition; limit the environment to main |
Lint trust policies and environment settings |
| The SBOM misses components | Generated from source manifests, not the built image | Compare component counts with an image-based SBOM | Generate from the artefact after build[66] | Make SBOM generation part of the main-branch pipeline |
Worked runbook: a compromised pipeline dependency
Link to section “Worked runbook: a compromised pipeline dependency”Scenario (pattern of the 2025 and 2026 incidents in use cases): an advisory says a third-party CI step used by your organisation ran malicious code between two timestamps. Owner: platform on-call, with AppSec; the IncidentOps lead coordinates, and ResponseOps takes over the security workstream if attacker activity is found.
Diagram source (Mermaid)
flowchart TB
A["Advisory: step X malicious between T1 and T2"]:::risk
B["Contain: block step X, pause deploys from affected repos"]:::core
C["Scope: which runs used X in the window, and which commit"]:::core
U["Unknown run: log gone, never counted as unaffected"]:::risk
D{"Could those jobs read secrets or roles?"}:::decision
E["Rotate every reachable secret, revoke tokens"]:::core
F["Hunt: audit logs, role use, artefacts built in window"]:::core
G["Recover: pin a reviewed SHA or remove X, rebuild artefacts"]:::core
H["Learn: zones, allow-list, lockfile, drill"]:::neutral
A --> B --> C
C -->|"advisory SHA in log"| D
C -->|"log expired"| U
U --> D
D -->|"yes"| E --> F --> G
D -->|"no"| G
G --> H
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
Text equivalent: after an advisory that step X was malicious between two times, contain (block X, pause deploys), then scope which runs used X and at which commit. Runs whose log contains the advisory’s SHA, and unknown runs whose log has expired, lead to one question: could those jobs read secrets or roles? If so, rotate every reachable secret and hunt through audit logs, role use and artefacts from the window; otherwise go straight to recovery (pin a reviewed commit or remove X, rebuild). Then learn.
-
Contain (first 30 minutes). Block the step organisation-wide, disable affected workflows, pause production deploys from affected repositories. Keep logs: they are evidence.
-
Scope (first 2 hours). Find every reference to the step, then every run in the window and the action commit it ran. The API’s
head_shais your commit, not the action’s, so search each run’s log for the malicious commit SHA the advisory names. Logs are kept for 90 days by default (up to 400 in private repositories),[89] so a run whose log is gone counts as unknown, never as unaffected. A date-filtered run list returns at most 1,000 runs,[90] so the script asks one day at a time and stops at the cap.Example status: syntax-checked with
bash -n; executed against a stubgh(large logs, one missing log, a day at the cap), not against GitHub. Replaceexample-org, the repository, the days and the SHA.#!/usr/bin/env bash set -euo pipefail ORG="example-org" REPO="$ORG/orders-service" STEP="third-party/compromised-action" BAD_SHA="<malicious commit SHA from the advisory>" gh search code "$STEP" --owner "$ORG" --json repository,path --limit 200 \ > references.json # For each affected repository: every run created in the advisory window (all pages), # one day at a time, because a filtered list stops at 1,000 runs. FIELDS='.workflow_runs[] | [.id, .name, .head_branch, .event, .created_at] | @tsv' : > runs.tsv for DAY in 2026-03-19 2026-03-20; do gh api --paginate "repos/$REPO/actions/runs?created=$DAY..$DAY&per_page=100" \ --jq "$FIELDS" > day.tsv if [ "$(wc -l < day.tsv)" -ge 1000 ]; then echo "$DAY reached the 1,000-run cap: split it into hours" >&2 exit 1 fi cat day.tsv >> runs.tsv done # Save each log, then search the file. Do not pipe `gh ... --log` into `grep -q`: # grep exits at the first match, gh dies on SIGPIPE, and pipefail reports a miss. mkdir -p logs : > affected-runs.txt : > unknown-runs.txt for run in $(cut -f1 runs.tsv); do if ! gh run view "$run" --repo "$REPO" --log > "logs/$run.log"; then echo "$run" >> unknown-runs.txt # log expired or unavailable elif grep -qF "$BAD_SHA" "logs/$run.log"; then echo "$run" >> affected-runs.txt fi done wc -l references.json runs.tsv affected-runs.txt unknown-runs.txt -
Decide reach. For each affected or unknown run, list the secrets and roles its job could reach (Lab 1’s inventory makes this a lookup). In a public repository assume anything in logs is public; CISA noted leaked secrets could appear as a double-encoded base64 payload.[87]
-
Rotate (same day). Rotate every reachable secret whether or not you see misuse: CISA said such secrets “should be considered compromised”.[87] Revoke tokens and deploy keys those jobs used.
-
Hunt. Check cloud audit logs for role use in the window and registry pushes by affected runs; rebuild the window’s artefacts in a clean pipeline and re-verify them.
-
Recover. Re-enable workflows with the step pinned to a reviewed commit, or replaced.
-
Learn. Blameless review: which zone rule would have kept secrets out of reach? Adopt a lockfile where available.[45]
Exit criteria: every reachable secret rotated; no unexplained role use; the window’s artefacts rebuilt and verified; the step pinned or removed; actions owned.
Anti-patterns and myths
Link to section “Anti-patterns and myths”- Blocking on severity alone. A critical CVSS score in an unreachable code path blocks a merge the author cannot fix. Instead: block on confidence, severity, fixability and newness; route the rest to tracked work with EPSS and KEV as inputs.[29][28]
- “Shift left” as “developers do security alone”. Moving checks earlier without coaching, paved roads and tuned rules moves the burden, not the capability; DORA’s 2022 data points to culture as the biggest predictor of adoption.[8]
- Exceptions without expiry. They accumulate into an invisible risk register. Instead: maximum duration, owner, ticket, review.
- “Signed means safe.” A signature proves who built an artefact and from what, not that the code is benign: the malicious code in XZ Utils shipped inside versions 5.6.0 and 5.6.1 themselves,[7] and a signature would only have confirmed who published them. Instead: combine provenance with review and behaviour checks.
- The unfunded champion. A title without protected time tends to lapse (Atlas view). Instead: protected time and recognition (roles).
Deciding and adopting
Link to section “Deciding and adopting”When to use and when not to use
Link to section “When to use and when not to use”Use DevSecOps practices whenever you ship software through an automated pipeline, especially software that handles money, personal data or safety functions, when customers or regulators ask for SBOMs or attestations, or when many teams share CI/CD.
When not to (or not yet):
- No CI/CD yet. Pipeline-placed controls need a pipeline. Start with DevOps basics; add secret scanning and dependency updates from day one, the rest once changes flow through automation.
- Throwaway prototypes. Secret prevention and dependency updates are enough; a full gate policy costs more than the prototype.
- As a replacement for expert review. High-risk designs (authentication, payments, cryptography, safety) still need human design review and penetration testing. Scanners find classes of bugs, not design flaws.
- When you build nothing. Organisations that only consume SaaS need supplier assurance (contracts, SBOM and attestation requests), owned by ComplianceOps and procurement.
Hidden responsibilities you take on: owning rules and their tuning; running an exceptions register; governing CI identities and runners; retaining evidence; keeping scanners themselves patched and pinned; responding when the pipeline is compromised.
Decision framework
Link to section “Decision framework”Decision 1: should this check block a merge? The tree has two levels. The first question is about the rule, measured over its recent findings (not about the tool); the rest are asked of each finding a qualifying rule produces.
Diagram source (Mermaid)
%%{init: {"flowchart": {"wrappingWidth": 130, "nodeSpacing": 16, "rankSpacing": 28, "padding": 6}}}%%
flowchart TB
Q1{"Rule: effective false-positive rate?"}:::decision
R1["Record only; tune the rule"]:::neutral
R2["Warn as a review comment"]:::core
Q3{"Finding: author can fix it in this change?"}:::decision
R3["Ticket with owner and due date"]:::core
Q4{"New, and high severity or a secret?"}:::decision
R6["Warn as a review comment"]:::core
Q5{"Fast enough for the pull request?"}:::decision
R4["Block the merge"]:::risk
R5["Run on main or nightly"]:::neutral
Q1 -->|"10% or more"| R1
Q1 -->|"under 10%, not near zero"| R2
Q1 -->|"near zero"| Q3
Q3 -->|"no"| R3
Q3 -->|"yes"| Q4
Q4 -->|"no"| R6
Q4 -->|"yes"| Q5
Q5 -->|"yes"| R4
Q5 -->|"no"| R5
classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
Text equivalent: first, for the rule, ask its effective false-positive rate. At 10 percent or more: record only and tune the rule. Under 10 percent but not near zero: its findings become review comments. Near zero: ask of each finding whether the author can fix it in this change; if not, open a ticket with an owner and due date. If the author can, and the finding is not both new and high severity (or a secret), warn as a review comment. If it is, block the merge when the check is fast enough for the pull request; otherwise run it on main or nightly.
The thresholds come from Tricorder;[30] the rest of the tree is this Atlas’s proposal. Consequences: a newly disclosed vulnerability in an existing dependency never blocks an unrelated change (it becomes a ticket, escalated if listed in KEV[28]), and a check too slow for a pull request moves later instead of being skipped.
Decision 2: which control to add first. An Atlas worked example for a web-service team with no OIDC yet, third-party CI steps referenced by tag, no SBOMs and an existing SAST licence. Scores 1–5 (higher is better); weights sum to 100. Re-score for yours.
| Option | Risk reduced (30) | Confidence (25) | Low effort (20) | Evidence value (15) | Low friction (10) | Weighted score |
|---|---|---|---|---|---|---|
| Pipeline hardening (pinning, token permissions, OIDC) | 5 | 5 | 3 | 3 | 4 | 4.20 |
| Signing, provenance and deploy verification | 3 | 5 | 3 | 5 | 5 | 4.00 |
| Secret prevention (pre-commit and push protection) | 4 | 4 | 5 | 2 | 4 | 3.90 |
| Dependency updates and SCA diff | 4 | 4 | 4 | 3 | 3 | 3.75 |
| DAST baseline in test | 3 | 3 | 2 | 3 | 4 | 2.90 |
| SAST in blocking mode | 3 | 2 | 3 | 3 | 2 | 2.65 |
Weighted score = Σ(weight × score) ÷ 100; for example pipeline hardening = (30×5 + 25×5 + 20×3 + 15×3 + 10×4) ÷ 100 = 4.20. Run SAST report-only from week one, but blocking SAST is usually a poor first investment; hardening the pipeline that holds production credentials is a good one.
Maturity model
Link to section “Maturity model”This is this Atlas’s maturity model, not an official one. OWASP SAMM (three maturity levels per practice),[58] BSIMM (descriptive)[59] and OWASP DSOMM[6] are established external models; use one of them if you need comparability with other organisations.
Diagram source (Mermaid)
flowchart TB L1["Level 1: Ad hoc. Manual reviews, central scans, keys in CI"]:::risk L2["Level 2: Visible. Report-only scanners, one findings store, SBOMs"]:::neutral L3["Level 3: Proportionate. Gate policy, baselines, expiring exceptions"]:::core L4["Level 4: Verified. OIDC, pinned steps, provenance checked at deploy"]:::core L5["Level 5: Learning. Escapes feed rules and threat models"]:::core S3["2 to 3 often stalls: no AppSec time to tune rules"]:::decision S4["3 to 4 often stalls: legacy pipelines without workload identity"]:::decision L1 -->|"inventory; secret scanning everywhere"| L2 L2 -->|"80% on the template; false positives measured"| L3 L3 -->|"overrides under 5% for a quarter"| L4 L4 -->|"95% provenance coverage; drills"| L5 L2 -.- S3 L3 -.- S4 classDef core fill:#e8f3f2,stroke:#16756f,color:#122029 classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029 classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029 classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
Text equivalent: five levels in sequence: 1 ad hoc, 2 visible, 3 proportionate, 4 verified, 5 learning; each arrow carries the main exit criterion from the table below. Two notes mark typical stalls: from Level 2 to 3, no AppSec time to tune rules; from 3 to 4, legacy pipelines without workload identity.
| Level | Observable behaviour | Evidence | Exit criteria (to next level) |
|---|---|---|---|
| 1 Ad hoc | Reviews before big releases; central scans of some repositories; cloud keys stored in CI | Spreadsheets of findings | Inventory of repositories, pipelines and CI secrets; secret scanning everywhere |
| 2 Visible | Scanners in report-only mode on the paved-road template; SARIF into one store; SBOM per release | Findings store; SBOM archive | 80% of active repositories on the template; effective false positives measured per rule |
| 3 Proportionate | Written gate policy blocks new, high-confidence, severe findings; baselines; expiring exceptions; champions in most teams | Gate decisions; exceptions register | Override rate under 5% for a quarter; no exception overdue by 30 days |
| 4 Verified | OIDC instead of stored keys; third-party steps pinned; provenance and signer identity verified at deploy; SLSA Build L3 for critical artefacts[91] | Deploy verification logs; trust policies | Provenance coverage of 95% or more; pipeline-compromise drill run twice a year |
| 5 Learning | Escapes root-caused and turned into rules or design patterns; rules retired on data; cost per merged change tracked | Post-incident actions; rule changelog | Sustained: escape rate and fix time improve year on year |
Adoption roadmap: 30/60/90 days
Link to section “Adoption roadmap: 30/60/90 days”| Window | Actions | Owner | Milestone and metric |
|---|---|---|---|
| Days 1–30 | Lab 1 inventory of the ten most important repositories; push protection and pre-commit secret scanning; read-only default tokens; deploy secrets moved into branch-limited environments; pinned third-party steps where secrets are held; SBOMs on main; a findings store | Platform + AppSec | Inventory done; pinned-reference ratio measured; first SBOMs archived |
| Days 31–60 | Report-only scanners in the shared template; measure effective false positives; baseline main branches; gate policy and exceptions register; OIDC for two services; champion pilot; threat-model two significant changes | AppSec + champions | Rule catalogue with measured confidence; two services without stored keys |
| Days 61–90 | Promote passing rules to blocking; attest and verify at deploy for pilot services; pipeline-compromise tabletop; first monthly scorecard; plan source controls (two-party review) | AppSec + platform + engineering managers | Override rate under 10% (heading to 5%); provenance coverage for pilots 100%; scorecard reviewed by leadership |
Quick wins: read-only default tokens, pinning, push protection. Dependencies: a shared pipeline template (see PlatformOps) and an owner for CI identities. Longer horizon: SLSA Build L3 for release builds, Source-track controls, CRA readiness where relevant.
Toolkit
Link to section “Toolkit”Checklists
Link to section “Checklists”Quick start (first week)
- Push protection on every repository; bypass limited for production
- Default CI token permissions set to read-only; each job requests what it needs
- Third-party steps in secret-holding workflows pinned to full SHAs or a lockfile
- An inventory of CI secrets and the jobs that can read them
- SBOM generated for each release build and archived with it
Preflight for a new service pipeline
- Uses the shared pipeline template, at a pinned version
- No repository- or organisation-level deploy secrets; credentials only in environments
- Production environment:
mainonly, reviewers, self-review prevented, no administrator bypass, nopull_request_targetjob - Cloud access by OIDC with a trust policy naming that environment or a protected branch
- Third-party tools only in read-only jobs (deploy: one pinned credentials action at most); signing only in a job running first-party actions
- Gate and scanner rules read from the base commit; AppSec code owners for
.ci/,.github/workflows/and scanner ignore files; gate is a required check - Threat model done if the service adds a trust boundary or sensitive data
Release readiness
- Artefact built once on the protected branch and identified by digest
- SBOM and provenance attested; deploy job verifies signer identity and builder
- No open blocking findings; exceptions valid and unexpired
- DAST baseline run against the release candidate; confirmed findings triaged
Routine review (monthly and quarterly)
- Effective false-positive rate per rule; noisy rules demoted or tuned
- Override rate and exception health reviewed; expired exceptions closed or re-decided
- Pinned-reference ratio and new third-party steps reviewed (quarterly)
- CI identities, runner registrations and organisation secrets reviewed (quarterly)
- Every post-release security defect root-caused (SSDF RV.3) and mapped to a control class
Security and governance audit
- Trace audit and exception sample done (assurance)
- CI audit logs reach the security data platform, with alerts on admin and protection changes
- Evidence (SBOMs, attestations, gate decisions) retained for the release’s supported life
Pipeline incident readiness
- Runbook for a compromised third-party step tested in the last six months
- Rotation procedure documented for every CI secret, with an owner
- Ability to list all runs that used a given step and commit within two hours
Templates
Link to section “Templates”Exception request (one per finding)
Finding: <tool>:<fingerprint> (<rule id>, <file:line>, severity <x>)
Requested by: <name>, team <team> Date: YYYY-MM-DD
Why not fix now: <no fixed version | needs migration | not reachable because ...>
Compensating control: <WAF rule, feature flag off, input validation at ...>
Owner: <named person> Ticket: <link>
Expiry: YYYY-MM-DD (maximum 90 days from grant)
Risk decision: <approved by champion | approved by AppSec (above threshold)>
Review on expiry: fix, re-decide with new evidence, or escalate to the system owner
Architecture decision record: change to the gate policy
Title: Promote rule <id> from warn to block (or demote)
Status: proposed | accepted | superseded Date: YYYY-MM-DD
Context: effective false-positive rate <x%> over <n> triaged findings (period);
median fix time <t>; findings introduced by changes per month <n>
Decision: <block | warn | record> at <pull request | build | deploy>
Consequences: expected blocked merges per month <n>; added minutes <m>;
rollback trigger: override rate above <y%> for one week
Owner: <AppSec engineer> Consulted: <champions of affected teams>
Threat model one-pager
System / change: <name> Date: YYYY-MM-DD Participants: <names>
1. What are we working on? <data-flow sketch link; trust boundaries crossed>
2. What can go wrong? <one threat per line, with its STRIDE category:
"an attacker who ... can ... because ...">
3. What are we going to do about it?
<per threat: mitigate | eliminate | transfer | accept,
with a ticket or an exception>
4. Did we do a good enough job? <review date; open questions; who checked the model>
Quarterly DevSecOps scorecard (one page)
| Measure | Last quarter | This quarter | Target | Comment |
|---|---|---|---|---|
| Gate override rate | under 5% | |||
| Rules above 10% effective false positives | 0 blocking | |||
| New-finding fix time, median / 90th percentile | team-set | |||
| Provenance coverage of production deploys | 95% or more | |||
| Expired-but-open exceptions | 0 | |||
| Pinned third-party references | 100% in secret-holding workflows | |||
| Post-release security defects / escape rate | improving | |||
| Security steps, added minutes (90th percentile) | team-set |
Deep comparisons
Link to section “Deep comparisons”| Approach | Where security happens | Strength | Weakness | Choose when |
|---|---|---|---|---|
| Traditional AppSec gate | One review or pen test before release | Expert attention | Late; does not scale | Rare, high-risk releases, alongside DevSecOps |
| DevSecOps (this page) | Placed controls along the pipeline, plus evidence | Fast feedback; scales; evidence | Needs tuning, owners, pipeline hardening | Any team shipping through CI/CD |
| Compliance-driven security | Controls chosen to satisfy an audit | Clear obligations | “Compliant” is not “secure” | Regulated scope, with ComplianceOps |
| Runtime-first security | Detection and response in production | Sees real attacks | Does not stop vulnerable code shipping | Always, alongside (SOC Ops, SecOps) |
| Control type | Sees | Misses |
|---|---|---|
| SAST | Data flows in source, insecure APIs | Configuration, runtime behaviour, third-party code |
| SCA | Known-vulnerable and unwanted components, licences | Malicious releases until disclosed; your own code |
| DAST | Runtime behaviour, headers, some injections | Code paths not reached by the scan |
| Secret scanning | Credential patterns in code and history | Secrets in images, logs, tickets |
| IaC and policy as code | Misconfiguration before deployment | Drift after deployment (CloudSecOps) |
| Signing and provenance | Who built what, from which source | Whether the code is benign |
Is DevSecOps a role, a team or a practice? A practice; “DevSecOps engineer” titles have no standard scope. This page recommends an AppSec enabling team, a platform team and champions (roles).
We already have a SAST licence. Why not block on everything it finds? Because developers decide what a false positive is. Measure each rule against the two bars in Playbook 3 before promoting it.
Does an SBOM make us secure? No. It makes you answerable: after an XZ-style advisory you can list affected artefacts quickly.[7] Generate it from the built artefact, store it with the release, and check the minimum fields.[19]
What does “SLSA Level 3” actually mean? Usually the Build track: a hardened build platform where concurrent builds cannot influence each other and signing material is not accessible to user-defined build steps.[39] The Source track has its own levels.[56] Ask which track and which artefacts.
How do DevSecOps and GitOps interact? GitOps reconciles a cluster to the state in Git (GitOps). DevSecOps makes sure what Git points to is a verified artefact: digests in manifests, signature checks before or at admission (KubernetesSecOps).
Going further
Link to section “Going further”Advanced topics and emerging direction
Link to section “Advanced topics and emerging direction”- Source integrity (approved specification, early adoption). SLSA v1.2’s Source track runs from version control to two-party review.[56] GitHub’s CI dependency lockfiles are a vendor feature in preview.[45]
- SBOM quality, not presence (guidance). The 2026 minimum elements add fields such as component hashes, licences, SBOM author signature and generation context;[19] expect (speculation) SBOM consumers to check them.
- VEX and reachability (established formats, maturing practice). Both cut noise;[61][49] quality varies by language and vendor, so verify before using them in gates.
- AI in the loop (fast-moving, thin independent evidence). Keep humans accountable for dismissals.
- Attestations beyond the build (emerging). Test results, reviews and model provenance as signed in-toto attestations, the framework SLSA provenance already uses.[21][22]
Learning path
Link to section “Learning path”- Prerequisites: Git, CI/CD and trunk-based development (DevOps); basic web security (OWASP Top 10:2025); containers (ContainerOps).
- One-day primer: read this page’s first three parts; do Lab 1 on your own pipeline; read the SSDF practice list.[12]
- One week: Labs 2 and 3; threat-model one real change; pin every third-party step in one repository.
- 30 days: the first 30 days of the roadmap on one team; effective false positives for your SAST rules; OIDC for one deployment; Lab 4.
- Advanced specialisation: supply-chain integrity (SLSA, in-toto, Sigstore); CI/CD attack and defence; policy as code at scale; admission control with KubernetesSecOps.
- Portfolio projects: a sample repository at SLSA Build L3 with deploy-time verification; a tested gate-policy library; a before-and-after pipeline threat model.
Wrap-up
Link to section “Wrap-up”Executive recap and cheat sheet
Link to section “Executive recap and cheat sheet”| Decide | Do | Measure | Watch out for |
|---|---|---|---|
| What blocks, in writing (gate policy) | Place controls where the fix is cheapest; block only new, high-confidence, severe, fixable findings | Effective false positives; override rate | Blocking on severity alone |
| Who owns findings | Every finding fixed, excepted with expiry, or tuned away | Fix time; exception health | Exceptions without expiry |
| How the pipeline is trusted | Jobs split by trust; no reachable secrets for PR jobs; OIDC bound to branch-limited environments; pinned steps | Pinned-reference ratio; secret rotation time | Tags, wildcards, scanners with super-user access |
| What evidence ships with each release | SBOM from the artefact; provenance; verification at deploy | Provenance coverage | “Signed” without “verified” |
| How you learn | Root-cause escapes; drill the pipeline-compromise runbook | Escape rate | Counting scans, not outcomes |
Next actions: run Lab 1 this week; pin and de-privilege before you buy anything.
A judgement call: block on new high-severity dependency findings, or warn with a deadline?
Link to section “A judgement call: block on new high-severity dependency findings, or warn with a deadline?”Situation (illustrative). A product group of eight teams has just rolled out SCA in pull requests. The CISO proposes: “Block any pull request that introduces a dependency with a known high or critical vulnerability.” The engineering director proposes: “Warn, and fix within 14 days.” Both are sensible.
Assumptions. Version matching is accurate; there is no reachability analysis for the main language; about one pull request in six touches dependencies; a fixed version exists for most, not all, flagged vulnerabilities.
Option A, block. For: it stops new vulnerable components, exactly the debt a gate should stop, and the author who chose the dependency can usually pick another version. Against: with no fixed version the author cannot comply, so each such block becomes an exception request; without reachability, some blocks concern code never called.
Option B, warn with a 14-day deadline. For: no blocked work. Against: introduced debt is still debt, and deadlines slip. Google’s Tricorder team observed that “warnings shown when building are often ignored”,[30] so a warning without an owner and a tracked due date is weak.
Decision process. Apply the decision tree. Version matching passes the rule-level question only if developers act on its findings even in code never called, the ones they may dismiss without reachability analysis: measure that first (Playbook 3). If it passes, each finding is high severity, so the open question is fixability. So: block when a fixed version exists and the finding is new in this change; otherwise warn, open a ticket with a due date, and require an exception with an owner if the pull request merges. Escalate to a release block if the vulnerability appears in CISA’s KEV catalogue.[28]
Why the attractive choice can be wrong. “Block everything high” looks strict but turns unfixable findings into routine overrides, the culture DevSecOps exists to prevent; “warn only” lets the dependency tree rot. Uncertainty: the split depends on how often fixed versions exist in your ecosystem; measure the share of blocks that needed an exception in month one, then revisit.
Learning objectives and mastery check
Link to section “Learning objectives and mastery check”After this page you should be able to: explain what DevSecOps is and is not, and where it hands off to neighbouring practices; select the position and action (block, warn, record) for a given control; design a pipeline with trust zones, workload identity and deploy-time verification; operate a gate with baselines and expiring exceptions; and evaluate a DevSecOps programme with outcome and trust metrics.
1. A SAST rule finds 50 issues in a month; developers dismiss 9 without fixing. Should it block merges?
Show answer
Its effective false-positive rate is 9 ÷ 50 = 18%: above the “essentially zero” bar for blocking and the 10% bar for review comments (Playbook 3). Record it, sample the dismissals and tune the rule; dismissals, not the tool author, define false positives.
2. Your pull-request job runs a third-party scanner and has access to a cloud deploy key “just for convenience”. What is wrong, and what is the fix?
Show answer
The job runs untrusted input and third-party code, so it belongs in the no-secrets zone; a malicious scanner or pull request exposes the key, as in the tj-actions and Trivy incidents. Removing it from the job is not enough for a repository or organisation secret: a same-repository pull request can edit the workflow to read it. Delete the key; deploy from a separate job via OIDC bound to a protected environment; pin the scanner.
3. A colleague writes the AWS trust condition repo:example-org/orders-service:* “so all our workflows can deploy”. Explain the
risk in one sentence and give a better condition.
Show answer
The wildcard lets any job in the repository, including pull-request jobs and any branch, assume the production role; use
StringEquals on repo:example-org/orders-service:environment:production (or the immutable-ID form for repositories created after
2026-07-15), with the environment settings from the reference implementation, so only approved jobs
from main qualify.
4. A pull request adds an exception for its own blocked finding to .ci/exceptions.json. Why does the gate still block?
Show answer
The reference gate reads its rules from the base commit, so a change cannot approve itself. The exception goes in its own pull
request, which AppSec must approve as code owner of .ci/; once it is merged, update the original branch from main so the gate
runs against a base that includes it.
5. Your deploy job verifies that the image “has a valid Sigstore signature”. An attacker signs a malicious image with their own GitHub account. Does verification stop it?
Show answer
Not as described: any valid signature passes, an attacker’s included. Verification must pin the expected identity and issuer
(cosign verify takes --certificate-identity and --certificate-oidc-issuer[92]), plus provenance naming the
expected builder. For this page’s example:
certificate identity:
https://github.com/example-org/orders-service/.github/workflows/ci.yml@refs/heads/main
OIDC issuer:
https://token.actions.githubusercontent.com
6. Scenario: you have 90 days and one AppSec engineer for 15 teams. What do you do first, and what do you deliberately not do?
Show answer
First: inventory pipelines and secrets, read-only default tokens, pinned third-party steps, push protection, OIDC for one or two deployments, SBOMs on main. Then report-only scanners in a shared template, promoting only measured, high-confidence rules. Do not triage findings centrally for 15 teams, block on every rule, or buy a platform before a gate policy exists. Build champions to scale.
Conclusion
Link to section “Conclusion”DevSecOps is easy to describe and easy to get wrong, and the failures recur: gates that cry wolf until people stop listening, exceptions that never expire, and pipelines that hold the keys to production while running other people’s code.
The durable version of the practice has three parts. Placement: each control sits where its finding reaches the person who can fix it, and blocks only when it is usually right, severe and fixable now. Evidence: every artefact carries a bill of materials and a verifiable record of how it was built, checked before it runs. A pipeline treated as production: narrow identities, pinned dependencies, separate trust zones and a rehearsed plan for the day a trusted tool turns hostile. Judge a programme less by the number of its scanners than by whether its developers still trust the gate.
Sources and further reading
Link to section “Sources and further reading”Sources were first opened on 2026-10-16; each footnote gives the date it was last checked. Details and caveats are in the evidence
ledger (research/devsecops-ledger.md).
Further reading
- Cloud native security and Kubernetes — the Develop, Distribute, Deploy and Runtime phases; continue on KubernetesSecOps.
Changelog
- 2026-10-16 — First edition (research, outline against Blueprint v2, draft, diagrams; Lab 2 executed; validator and browser QA runs).
- 2026-10-17 — Round 1 (
reviews/devsecops-r1.md): environment OIDC preconditions and plan tiers; fork vs same-repository pull requests; build, scan and attest jobs split; deploy-time verification fixed; gate rules from the base commit; four figures redrawn; API testing; OMB M-26-05 and other fixes. - 2026-10-17 — Round 2 (
reviews/devsecops-score.md): runbook log search fixed and tested; deploy exception to the zone rule; all of.ci/from the base commit, suppression files code-owned; SBOM-generator and Conftest notes; three figures redrawn; repetition cut. - 2026-10-17, post-score polish (
reviews/devsecops-polish.md):pull_request_targetprecondition; runbook stops at the 1,000-run cap;.ci/bootstrap; judgement-call assumption; issuer value; figures 6 and 7 redrawn.
SP 800-204C, Implementation of DevSecOps for a Microservices-based Application with Service Mesh — NIST, March 2022. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
SP 800-204D, Strategies for the Integration of Software Supply Chain Security in DevSecOps CI/CD Pipelines — NIST, February 2024. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
SP 1800-44 (Initial Preliminary Draft), Secure Software Development, Security, and Operations (DevSecOps) Practices — NIST NCCoE, July 2025 (obsoleted by a later draft on 2026-03-24). Checked 2026-10-16. ↩︎ ↩︎
Pentagon to Advance DevSecOps Adoption With New Guidance — ExecutiveGov, 2024-10-28 (secondary report; the DoD PDF returned HTTP 403). Checked 2026-10-16. ↩︎ ↩︎
OWASP DevSecOps Guideline — OWASP Foundation (Incubator project). Checked 2026-10-16. ↩︎
OWASP DevSecOps Maturity Model — OWASP Foundation (Lab project). Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Reported Supply Chain Compromise Affecting XZ Utils Data Compression Library, CVE-2024-3094 — CISA, 2024-03-29. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Accelerate State of DevOps Report 2022 — DORA / Google Cloud, v. 2022-12. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Rugged Software — Rugged Software project, undated. Checked 2026-10-16. ↩︎ ↩︎
Microsoft Security Development Lifecycle (SDL) — Microsoft, undated page. Checked 2026-10-16. ↩︎
3 Hot Themes in DevOps This Week — SiliconANGLE (Klint Finley), 2012-04-04. Checked 2026-10-16. ↩︎
SP 800-218, Secure Software Development Framework (SSDF) Version 1.1 — NIST, February 2022. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Secure Software Development Framework project — NIST CSRC, undated page. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
SLSA v1.0 is now final! — SLSA blog, 2023-04-19. Checked 2026-10-16. ↩︎
Cyber Resilience Act — European Commission. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
M-26-05, Adopting a Risk-based Approach to Software and Hardware Security (PDF) — US Office of Management and Budget, 2026-01-23. Checked 2026-10-16. ↩︎ ↩︎
A03:2025 Software Supply Chain Failures — OWASP Foundation, 2025. Checked 2026-10-16. ↩︎ ↩︎
What’s new in SLSA v1.2 — SLSA. Checked 2026-10-16. ↩︎
2026 Minimum Elements for a Software Bill of Materials (SBOM) (PDF) — CISA and 17 co-authoring organisations, 2026-07-29 (copy hosted by NCSC NZ). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Software Bill of Materials (SBOM) — CISA. Checked 2026-10-16. ↩︎
SLSA v1.2 Build: provenance — SLSA. Checked 2026-10-16. ↩︎ ↩︎
in-toto — in-toto project (CNCF graduated). Checked 2026-10-16. ↩︎ ↩︎
SLSA — SLSA / OpenSSF (home page; expands the name). Checked 2026-10-16. ↩︎
SLSA specification — SLSA / OpenSSF, version 1.2 (Approved). Checked 2026-10-16. ↩︎ ↩︎
OpenID Connect — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎
Signing with cosign: overview — Sigstore. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Vulnerability Exploitability eXchange (VEX) — OWASP CycloneDX. Checked 2026-10-16. ↩︎
Known Exploited Vulnerabilities Catalog — CISA. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Exploit Prediction Scoring System (EPSS) — FIRST. Checked 2026-10-16. ↩︎ ↩︎
Tricorder: Building a Program Analysis Ecosystem — Sadowski et al., ICSE 2015 (full text: PDF copy). Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Security champions guide — OWASP Developer Guide. Checked 2026-10-16. ↩︎ ↩︎
NIST SP 800-218 (PDF) — NIST, February 2022. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
SP 800-218 Rev. 1 (Initial Public Draft), SSDF Version 1.2 — NIST, 2025-12-17 (comments closed 2026-01-30). Checked 2026-10-16. ↩︎ ↩︎
SSDF publications — NIST CSRC. Checked 2026-10-16. ↩︎ ↩︎
DevSecOps manifesto — DevSecOps.org, site © 2012–2023. Checked 2026-10-16. ↩︎
Threat Modeling Manifesto — Threat Modeling Manifesto working group, undated. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Static Analysis Results Interchange Format (SARIF) Version 2.1.0 Plus Errata 01 — OASIS, 2023-08-28. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Security Champions Manifesto — OWASP Security Champions Guide project, undated. Checked 2026-10-16. ↩︎
SLSA v1.2 Build: requirements for producing artifacts — SLSA. Checked 2026-10-16. ↩︎ ↩︎
OWASP Top 10 CI/CD Security Risks — OWASP Foundation (Lab project). Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
Events that trigger workflows — GitHub Docs. Checked 2026-10-17. ↩︎ ↩︎
Security hardening for GitHub Actions — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Configuring OpenID Connect in Amazon Web Services — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
assume-role-with-web-identity — AWS CLI Command Reference. Checked 2026-10-17. ↩︎
Workflow dependency lockfiles — GitHub Docs (public preview). Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Threats – Microsoft Threat Modeling Tool — Microsoft Learn, updated 2026-03-04. Checked 2026-10-16. ↩︎
About push protection — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
About GitHub Advanced Security — GitHub Docs. Checked 2026-10-16. ↩︎
Semgrep documentation — Semgrep. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs — Spracklen et al., USENIX Security 2025 (arXiv v3 2025-03-02). Checked 2026-10-16. ↩︎ ↩︎
OpenSSF Scorecard — OpenSSF. Checked 2026-10-16. ↩︎ ↩︎
OIDC usage in Fulcio — Sigstore. Checked 2026-10-17. ↩︎ ↩︎ ↩︎
ZAP API scan — ZAP project. Checked 2026-10-16. ↩︎
OWASP Top 10 API Security Risks – 2023 — OWASP API Security Project, 2023. Checked 2026-10-16. ↩︎
OWASP Top 10:2025 — OWASP Foundation, 2025 edition. Checked 2026-10-16. ↩︎ ↩︎
SLSA v1.2 Source track requirements — SLSA. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
OWASP Application Security Verification Standard — OWASP Foundation, 5.0.0 listed. Checked 2026-10-16. ↩︎
About OWASP SAMM — OWASP SAMM. Checked 2026-10-16. ↩︎ ↩︎
BSIMM maturity model — Black Duck, BSIMM16 (undated page). Checked 2026-10-16. ↩︎ ↩︎
Regulation (EU) 2024/2847 (Cyber Resilience Act), Annex I Part II — EUR-Lex, 2024. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
CycloneDX specification overview — OWASP CycloneDX (ECMA-424; version 1.7, 2025-10-21). Checked 2026-10-16. ↩︎ ↩︎
SPDX overview — SPDX / Linux Foundation, updated 2024-09-13. Checked 2026-10-16. ↩︎
SARIF support for code scanning — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
gitleaks/gitleaks README — Gitleaks project. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
About code scanning with CodeQL — GitHub Docs. Checked 2026-10-16. ↩︎
anchore/syft README — Anchore. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
aquasecurity/trivy README — Aqua Security. Checked 2026-10-16. ↩︎
trivy image CLI reference — Trivy documentation. Checked 2026-10-16. ↩︎ ↩︎
Conftest — Open Policy Agent project. Checked 2026-10-16. ↩︎ ↩︎
ZAP — ZAP project (“ZAP by Checkmarx”). Checked 2026-10-16. ↩︎
Artifact attestations — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Semgrep CLI reference — Semgrep. Checked 2026-10-16. ↩︎
Asleep at the Keyboard? Assessing the Security of GitHub Copilot’s Code Contributions — Pearce et al., arXiv, 2021 (v3 2021-12-16). Checked 2026-10-16. ↩︎
Deployments and environments — GitHub Docs. Checked 2026-10-17. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Using artifact attestations to establish provenance for builds — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Available rules for rulesets — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎
Private registries — Anchore open-source documentation, updated 2026-10-16. Checked 2026-10-16. ↩︎
gh attestation verify — GitHub CLI manual. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎
actions/attest README — GitHub. Checked 2026-10-16. ↩︎ ↩︎
Ignore files, folders, and code — Semgrep. Checked 2026-10-17. ↩︎
trivy filesystem CLI reference — Trivy documentation. Checked 2026-10-17. ↩︎
OpenID Connect reference — GitHub Docs. Checked 2026-10-16. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎
Actions pull_request_target and environment branch protections changes — GitHub Changelog, 2025-11-07 (effective 2025-12-08). Checked 2026-10-17. ↩︎
GHSA-8q59-q68h-6hv4 (CVE-2020-14343), PyYAML — GitHub Advisory Database. Checked 2026-10-16. ↩︎
sigstore/cosign README — Sigstore project. Checked 2026-10-16. ↩︎ ↩︎
Supply Chain Compromise of Third-Party tj-actions/changed-files (CVE-2025-30066) and reviewdog/action-setup@v1 (CVE-2025-30154) — CISA, 2025-03-18 (updated 2025-03-26). Checked 2026-10-16. ↩︎ ↩︎ ↩︎
Trivy supply chain attack: what you need to know — Aqua Security, 2026-03-22 (updated 2026-04-01). Checked 2026-10-16. ↩︎
Configuring the retention period for GitHub Actions artifacts and logs in your organization — GitHub Docs. Checked 2026-10-17. ↩︎
REST API endpoints for workflow runs — GitHub Docs. Checked 2026-10-17. ↩︎
SLSA v1.2 Build track basics — SLSA. Checked 2026-10-16. ↩︎
Verifying signatures — Sigstore. Checked 2026-10-16. ↩︎