# DevSecOps

> DevSecOps builds security into the CI/CD pipeline: checks run as automated jobs on every change, and a failing check blocks the merge or deploy.

Also known as: Development, Security, and Operations, SecDevOps, DevOpsSec, Secure DevOps

Source: https://socket.dev/glossary/devsecops

Last updated: 2026-11-05

DevSecOps (development, security, and operations, sometimes written SecDevOps) is the practice of enforcing security inside the automated pipeline that turns a commit into a release. Security checks run as pipeline jobs, policies live in version control, and a failing check blocks the merge or deploy. It exists because teams that ship daily cannot wait on manual review.

The mechanism is simple: anything the pipeline can check automatically, it checks on every change, and the result gates the next stage. What most DevSecOps material leaves out is that the pipeline is also the most valuable target in the software supply chain, and every security tool added to it runs with the pipeline's credentials. In the March 2026 [Trivy GitHub Actions compromise](https://socket.dev/supply-chain-attacks/trivy-github-actions-compromise), attackers force-pushed 75 of 76 version tags of aquasecurity/trivy-action, the official GitHub Action for the Trivy vulnerability scanner. More than 10,000 workflow files referenced it. A DevSecOps control became an infostealer. Our position: judge a DevSecOps program by what code its privileged jobs execute, and whether anyone knows before they run, not by how many scanners it has added.

## What is DevSecOps?

DevSecOps is DevOps with security enforced by the same automation that builds, tests, and ships the code. To see why it emerged, start with what DevOps changed.

DevOps collapsed the hand-off between developers and operators into a pipeline. A commit triggers continuous integration (CI), which builds and tests it; continuous delivery (CD) packages and deploys it. The pipeline acts on the team's behalf, so it holds standing authority: a token that can push to the default branch, credentials that can publish to a registry, keys that can deploy to production. That design is what lets a team ship many times a day.

Security review did not fit that loop. A penetration test or an architecture review that takes two weeks cannot gate a change that ships in twenty minutes, so the review either became the bottleneck or got skipped. By early 2012, Gartner had published a report titled "DevOpsSec: Creating the Agile Triangle," arguing that security had to join the loop rather than sit at its end ([SiliconANGLE, April 2012](https://siliconangle.com/2012/04/04/3-hot-themes-in-devops-this-week/)). NIST later formalized the idea for microservices in [SP 800-204C](https://csrc.nist.gov/News/2022/nist-publishes-sp-800-204c), published March 8, 2022.

Mechanically, DevSecOps rests on three moves:

1. **Checks become pipeline jobs.** Static analysis, dependency analysis, secret detection, and infrastructure scanning run on every pull request and build, not on a release candidate.
2. **Policy becomes code.** Rules such as "no critical findings in production images" or "every deploy must be signed" live in the repository and are evaluated by the pipeline, which is [policy as code](https://socket.dev/glossary/policy-as-code-pac).
3. **Findings go to the author.** A failed check lands in the pull request of the person who introduced the problem, while the change is still in their head. This is the [shift left](https://socket.dev/glossary/shift-left-security) half of DevSecOps.

The trade-off is the one NIST names in its 2025 DevSecOps practice guide: the approach "relies on automated production flows, which can quickly propagate security risks directly into production" ([NIST SP 1800-44, initial preliminary draft](https://csrc.nist.gov/pubs/sp/1800/44/ipd)). Automation that ships a fix in twenty minutes ships a compromise in twenty minutes too.

## What is the difference between DevOps and DevSecOps?

The difference between DevOps and DevSecOps is where security decisions are made and enforced. DevOps automates build, test, and deploy; DevSecOps adds security checks to that automation and lets them block a change.

| Dimension | DevOps | DevSecOps |
| --- | --- | --- |
| Goal | Ship changes quickly and reliably | Ship quickly with security enforced by the pipeline |
| When security runs | Before release, by a separate team | On every commit, pull request, build, and deploy |
| Who fixes a finding | Security files a ticket against the release | The author of the change, in the pull request |
| Where policy lives | Documents and review checklists | Version-controlled rules the pipeline evaluates |
| What blocks a release | A manual sign-off | A failing automated check |
| Typical failure mode | Review becomes the bottleneck and gets bypassed | Alert fatigue, and the pipeline itself becomes the attack surface |

Verdict: any team that ships more often than its security team can review should run DevSecOps, which today means nearly every team. A manual gate still earns its place in slow, regulated release trains such as firmware or medical devices, but as an addition to the automated checks, never a replacement. The last row is the one this article is about.

## Anatomy of a DevSecOps pipeline

A DevSecOps pipeline places one or more checks at each point where code changes hands. Each check is good at a specific class of problem and blind to others. The table maps the usual control at each stage to what it catches and what it misses.

| Stage | Typical DevSecOps control | Catches | Misses |
| --- | --- | --- | --- |
| Code | IDE linters, pre-commit secret detection | Insecure patterns, committed secrets | Malicious IDE extensions, including compromised security extensions |
| Pull request | SAST, dependency diff, secret scanning, review | Injection flaws, new dependencies with known CVEs, leaked tokens | Malicious packages, which carry no CVE |
| Build | Dependency install, container and IaC scanning, SBOM generation | Vulnerable base images, misconfigurations | Install scripts that run before any scanner reads them, and scanner actions that run with build credentials |
| Test | DAST, integration tests | Runtime flaws in your application | Dependency behavior outside tested paths |
| Release | Signing and provenance | Tampering after the build | Compromise inside the build: provenance attests a poisoned build as faithfully as a clean one |
| Deploy and operate | Admission policies, runtime monitoring | Unsigned images, configuration drift | Credentials already exfiltrated upstream |

Read the right-hand column top to bottom and a pattern appears. The controls were designed to find flaws in code your team wrote or configured. The attacks that defined 2025 and 2026 arrived as code your team did not write, executing inside jobs your team trusts. As we put it in our [guide to hardening GitHub Actions against outside threats](https://socket.dev/blog/hardening-github-actions-against-outside-threats): "Scanning tells you what is in the tree. The install is where code executes."

## What tools are used in DevSecOps?

DevSecOps tools fall into a handful of categories, each run as a pipeline step:

- [Static application security testing (SAST)](https://socket.dev/glossary/static-application-security-testing) reads your source for injection, unsafe deserialization, and similar flaws.
- [Software composition analysis (SCA)](https://socket.dev/glossary/software-composition-analysis) inventories open source dependencies and matches them against vulnerability databases.
- Secret scanning finds credentials in commits and history.
- Container and infrastructure-as-code scanners check images, Terraform, Kubernetes manifests, and CloudFormation.
- Dynamic application security testing (DAST) probes a running build.
- Policy engines evaluate rules at merge and deploy time.

Every one of these tools is third-party code that the pipeline downloads and executes. Most are distributed exactly the way attackers like: as a [GitHub Action](https://socket.dev/glossary/github-actions-security) referenced by a mutable tag, a container image referenced by `latest`, or an IDE extension that auto-updates. And by design they read the most sensitive material a repository has: source, infrastructure definitions, and often secrets. That combination is why [TeamPCP's campaign against security tools](https://socket.dev/blog/teampcp-targeting-security-tools-across-oss-ecosystem) went after scanners.

So the useful question about a DevSecOps tool is not only what it detects. It is what it can reach. Four questions cover most of it:

1. **What credentials does the job running it hold?** A filesystem scanner needs to read the checkout. It does not need a publish token, a cloud key, or `id-token: write`.
2. **How is it referenced?** A tag can move. A full commit SHA or an image digest cannot.
3. **Where does it send data?** Most scanners download a vulnerability database. Few need to upload anything, so the job's egress allowlist can be short.
4. **Does it update itself?** IDE extensions and `latest` image tags change under you without a pull request.

Applied to a scanner, those answers produce a job like this one: no secrets, a read-only token, no persisted git credentials, and every action pinned to a commit.

```yaml
jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read          # no write token, no id-token
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
        with:
          scan-type: fs
          format: sarif
          output: trivy.sarif
```

Isolation does not make a compromised scanner safe. It makes the compromise far less useful: the scanner can still read your source and lie about the results, but it has no secret to steal and no token to push with. The SHAs above resolve to `v7.0.1` and `0.35.0` as of November 5, 2026. `0.35.0` is the one trivy-action tag the March 2026 attacker left untouched. It is also the only unprefixed version tag left in the repository; the others now exist only with a `v` prefix, so a workflow still referencing `@0.33.0` no longer resolves.

## When the security tool is the payload

The Trivy compromise is the clearest illustration of the DevSecOps blind spot, because the malicious code ran inside the security step itself. Each poisoned tag pointed to a new commit whose `entrypoint.sh` was 204 lines long, according to our [analysis of the trivy-action tag compromise](https://socket.dev/blog/trivy-under-attack-again-github-actions-compromise).

> Figure 1. The poisoned scanner step. Half the script steals, half scans, and the job stays green.
>
> A CI scan job with the trivy-action step expanded. The job holds a GITHUB\_TOKEN, repository secrets, cloud keys, and SSH keys, all readable by every step. The poisoned entrypoint.sh is 204 lines: lines 4 to 105 are the injected stealer, which collects secrets from runner memory and the filesystem, encrypts them, and exfiltrates them; lines 106 to 204 are the unmodified Trivy scan, whose normal output is all the developer sees.

Figure 1 shows a scan job with four steps: checkout, `npm ci`, `aquasecurity/trivy-action@0.33.0`, and a SARIF upload. Every step can read the job's `GITHUB_TOKEN`, repository secrets, cloud keys, SSH keys, and Kubernetes tokens. The scanner's `entrypoint.sh` is expanded beside it. Lines 4 to 36 collect secrets from runner memory and the filesystem, lines 39 to 68 encrypt them, and lines 70 to 105 exfiltrate them and clean up. Lines 106 to 204 are the unmodified Trivy scan, whose normal output and passing job are all the developer sees.

The stealer read runner process environments from `/proc/<pid>/environ`. On GitHub-hosted runners, where `sudo` needs no password, it dumped the memory of the `Runner.Worker` process and searched it for the pattern GitHub uses to hold secrets. It encrypted what it found with AES-256-CBC, wrapped the key with RSA-4096, and posted it to `scan.aquasecurtiy[.]org`, a typosquat of Aqua Security's domain. Then the real scan ran and printed normal results. The pipeline stayed green.

The forged commits copied each original commit's author, timestamps, and message, so `git log` looked normal. Three details gave them away: the commits were unsigned where the originals carried GitHub's web-flow signature, each claimed a 2021 or 2022 date while its parent was dated March 2026, and each touched only `entrypoint.sh`. GitHub's releases page even showed an "Immutable" badge next to the poisoned tags.

Trivy's [official advisory](https://github.com/aquasecurity/trivy/security/advisories/GHSA-69fq-xp46-6x23), CVE-2026-33634, puts the trivy-action exposure at roughly 12 hours, from about 17:43 UTC on March 19 to 05:40 UTC on March 20, 2026. The root cause was not a GitHub flaw. It was residual access from an earlier compromise of Trivy's own CI: the maintainers wrote that "credential rotation was performed but was not atomic." Our scanner began flagging the malicious actions at 19:15 UTC, roughly 90 minutes after the first tag moved, and generated 182 threat feed entries for the campaign. A pipeline that ran in that first window was already exposed.

Trivy was not the only security tool hit. Figure 2 and the table below it reconstruct the 2026 sequence from the advisory and our coverage, mapped to the channel through which each tool reached a pipeline.

> Figure 2. In 2026, attackers hit every channel through which a security scanner reaches a pipeline.
>
> Where 2026 attacks on security tooling landed in a DevSecOps pipeline. Editor: Trivy VS Code extension 1.8.12 and 1.8.13 in February, Checkmarx VS Code extensions in April. CI workflow: trivy-action and setup-trivy tags in March, Checkmarx KICS GitHub Actions in March, GhostAction security-audit.yml in October. Release binary: Trivy v0.69.4 in March. Container image: Trivy images 0.69.4 to 0.69.6 in March, checkmarx/kics images in April.

| Date (2026) | Event | Distribution channel |
| --- | --- | --- |
| Late February | An attacker exploits a GitHub Actions workflow in Trivy's repository and steals credentials; disclosed March 1 | CI pipeline |
| February 27 to 28 | Trivy VS Code extension 1.8.12 and 1.8.13 on Open VSX launch local AI coding agents in permissive modes to inventory credentials | IDE extension |
| March 19 to 20 | 75 of 76 trivy-action tags and every setup-trivy tag repointed; Trivy v0.69.4 binary and images published with the stealer; the binary was live about three hours | GitHub Actions, release binaries, container images |
| March 22 to 23 | Docker Hub images 0.69.5 and 0.69.6 carry the same payload | Container registry |
| By March 24 | TeamPCP claims Checkmarx KICS GitHub Actions, Open VSX extensions, and a trojanized LiteLLM release on PyPI | Actions, IDE extensions, PyPI |
| April 22 | Malicious images in the official checkmarx/kics Docker Hub repository, plus Checkmarx VS Code extensions that fetch and run a credential stealer | Container registry, IDE extensions |
| October 8 | GhostAction commits a workflow named security-audit.yml to 346 repositories | Stolen maintainer credentials |

Two of the targets were scanners: Trivy and Checkmarx's KICS. The [Checkmarx KICS compromise](https://socket.dev/blog/checkmarx-supply-chain-compromise) modified the bundled KICS binary to encrypt and exfiltrate an uncensored scan report, which for an infrastructure-as-code scanner means the credentials sitting in Terraform and Kubernetes files. The last row needed no compromised tool at all. The [GhostAction wave](https://socket.dev/blog/ghostaction-cloud-credentials) used stolen maintainer accounts to commit a workflow named `security-audit.yml`, with commit messages like "Add security audit workflow." It posted the repository's Actions secrets, plus cloud, AI service, and other API keys found anywhere in its git history, to a hardcoded IP address. In a team that has absorbed DevSecOps culture, a commit that adds a security audit is the kind nobody questions.

None of this is new in kind. A year earlier, the [tj-actions/changed-files compromise](https://socket.dev/blog/github-actions-supply-chain-attack-puts-thousands-of-projects-at-risk) repointed tags v1 through v45.0.7 on an action used by more than 23,000 repositories. What changed in 2026 is that attackers aimed at the tools teams added specifically to be safer.

## Why do DevSecOps pipelines miss supply chain attacks?

DevSecOps pipelines miss supply chain attacks because their checks look for known flaws, while supply chain attacks deliver working malicious code. A malicious package gets no CVE, because it is behaving exactly as its author intended. The CVE and NVD pipeline is also under strain, so even for real vulnerabilities the feed most scanners match against is not a complete source of truth.

The second gap is ordering. In a typical build job, dependency installation runs before any scanner step, and package managers have historically run [install scripts](https://socket.dev/glossary/postinstall-scripts) during that install. We ran a three-dependency install under npm 12.2.2 on November 5, 2026, to show two questions a pipeline needs answered:

```text
$ npm install esbuild@0.25.0 sharp@0.34.3 left-pad@1.3.0
added 14 packages, and audited 15 packages in 1s
...
1 high severity vulnerability
...
npm warn install-scripts 2 packages had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts   esbuild@0.25.0 (postinstall: node install.js)
npm warn install-scripts   sharp@0.34.3 (install: node install/check.js)
```

The audit line answers "which installed packages have known advisories": one, in sharp's bundled native libraries. The install-scripts lines answer a different question: "which packages would have executed code on this runner during install." That answer has nothing to do with CVEs. Under npm 10, which ships with Node 22, both scripts would have run before the next step started, with the job's environment in reach. npm v12 made that execution opt-in when it [shipped in July 2026 with install scripts off by default](https://socket.dev/blog/npm-12); pnpm made the same change in version 10. Default-on install scripts were a design mistake, and the fix is correct. It is not complete. Code can still run at import time or during tests, and an approval written by package name rather than pinned to a version carries over to the next release, compromised or not. npm 12 pins approvals to the exact version by default; we confirmed `npm install-scripts approve esbuild` writes `"esbuild@0.25.0": true`.

The third gap is noise. A pipeline that fails the build on every high-severity CVE trains developers to override it. Most CVE alerts describe code paths the application never calls, so [reachability](https://socket.dev/glossary/reachability-analysis), not severity score, should decide what blocks a merge. A gate developers trust is worth more than a gate that fires constantly.

The fourth gap is the pipeline's own dependencies. OpenAI's macOS app-signing workflow ran a compromised axios release on March 31, 2026, during the [axios compromise](https://socket.dev/blog/axios-npm-package-compromised). [OpenAI's disclosure](https://openai.com/index/axios-developer-tool-compromise/) traced the root cause to a workflow that "used a floating tag, as opposed to a specific commit hash, and did not have a configured minimumReleaseAge." Both are pipeline configuration, not application code, and neither is what a typical DevSecOps scanner checks.

## Securing the pipeline that secures the code

The controls that close these gaps treat the pipeline as a production system that executes third-party code by design, which is the core of [CI/CD security](https://socket.dev/glossary/ci-cd-security). Each one has a cost and a limit.

- **Separate the jobs that run untrusted code from the jobs that hold credentials.** Tests, builds, and scanners run without secrets; publishing runs in its own job behind a gated environment. This costs pipeline complexity, and the artifact passed between jobs becomes shared state that must be validated.
- **Pin what each job executes.** Use a workflow lockfile where your platform supports one, a full commit SHA elsewhere, and image digests for containers. Pinning decides which code runs, not whether it is safe: a SHA pinned to a malicious commit is reproducibly malicious.
- **Turn install-time execution off and slow intake down.** Keep install scripts disabled with an explicit allowlist, and add a [dependency cooldown](https://socket.dev/glossary/dependency-cooldown) so a version must be public for some days before the pipeline will resolve it. Most malicious versions are removed within hours. A cooldown delays legitimate security fixes by the same window.
- **Analyze dependencies and actions for behavior, not just CVEs.** In October 2025 we added [GitHub Actions scanning](https://socket.dev/blog/introducing-github-actions-scanning-support), which resolves each action tag to the commit it points to at scan time and statically analyzes the action's bundled JavaScript for malicious, obfuscated, and network-active code. It flags a moved tag on the next scan, not at the moment a runner resolves it, so it narrows the window rather than closing it.
- **Verify provenance downstream, but do not stop there.** Provenance proves where an artifact was built. It cannot prove what ran during the build, and attackers who hold a maintainer's credentials publish through legitimate pipelines with valid provenance.

None of these controls answers the hardest case. In the October 2026 [LiteLLM compromise](https://socket.dev/blog/litellm-attack-timeline), the attacker held a GitHub organization owner's token. Eleven days before the malicious release, they pushed secret-stealing workflows to throwaway branches and collected, among other things, the API key for the project's secret-scanning service. GhostAction worked the same way, through stolen maintainer accounts. Neither needed an injection or a retagged action. The attackers authenticated, and the pipeline did what it was told. DevSecOps automated the checks a security team used to run by hand. It has not yet automated the question a security team would ask first: what is this job about to execute, who changed it, and why does it need that credential?
