Search…
DevSecOps · Part 4

Software supply chain security

In this series (10 parts)
  1. What DevSecOps means
  2. Shift-left security
  3. SAST and DAST
  4. Software supply chain security
  5. Container security
  6. Kubernetes security in depth
  7. Secrets management in practice
  8. Cloud security posture management
  9. Compliance as code
  10. Incident response for DevSecOps

Modern applications contain more third-party code than first-party code. A typical Node.js project pulls in hundreds of transitive dependencies. Each one is an attack surface. Supply chain attacks exploit this trust relationship to inject malicious code into otherwise legitimate software.

Attack vectors

Dependency confusion

Package managers resolve names from multiple registries. If your organization uses a private package called @acme/auth-utils on an internal registry, an attacker can publish a package with the same name on the public npm registry. Depending on configuration, the package manager may prefer the public version.

The fix is explicit registry scoping:

# .npmrc
@acme:registry=https://npm.internal.acme.com

For Python, use --index-url for internal packages and --extra-index-url for public ones, never the reverse.

Typosquatting

Attackers publish packages with names similar to popular ones: colorsj instead of colors, requets instead of requests. A single typo in package.json or requirements.txt pulls in malicious code.

Mitigate this with lockfile review. Every dependency change in package-lock.json or poetry.lock should be reviewed as carefully as application code. Tools like Socket.dev analyze new dependencies for suspicious behaviors: install scripts, network calls, filesystem access.

Compromised maintainers

Even legitimate packages become dangerous when maintainer accounts are compromised. The event-stream incident demonstrated this: an attacker gained maintainer access and added a targeted backdoor that harvested cryptocurrency wallet credentials.

There is no perfect defense here. Reduce risk by pinning dependency versions, using lockfiles, and monitoring for unexpected updates.

Software Bill of Materials (SBOM)

An SBOM is a complete inventory of every component in your software. It answers: “What libraries, at what versions, with what licenses, are in this artifact?”

CycloneDX

CycloneDX produces SBOMs in JSON or XML format:

# Node.js
npx @cyclonedx/cyclonedx-npm --output-file sbom.json

# Python
cyclonedx-py poetry --output sbom.json

# Container image
syft alpine:latest -o cyclonedx-json > sbom.json

SPDX

SPDX is the ISO standard (ISO/IEC 5962:2021) for SBOMs. Many government contracts now require SPDX-format SBOMs:

syft alpine:latest -o spdx-json > sbom.spdx.json

Using SBOMs operationally

SBOMs become powerful when combined with vulnerability databases. When a new CVE drops, query your SBOMs to find every artifact affected:

# Scan an SBOM against known vulnerabilities
grype sbom:sbom.json

This answers “are we affected by CVE-2024-XXXXX?” in seconds instead of days.

Artifact signing with Sigstore

Sigstore provides keyless signing for software artifacts. It solves the key management problem that made traditional code signing impractical for most teams.

Cosign

Cosign signs and verifies container images:

# Sign an image (keyless, uses OIDC identity)
cosign sign ghcr.io/acme/api:v1.2.3

# Verify an image
cosign verify ghcr.io/acme/api:v1.2.3 \
  --certificate-identity=deploy@acme.com \
  --certificate-oidc-issuer=https://accounts.google.com

Keyless signing uses short-lived certificates tied to your identity provider. No long-lived keys to manage, rotate, or protect.

Signing in CI

# GitHub Actions
- name: Sign image
  run: cosign sign ${{ env.IMAGE_DIGEST }}
  env:
    COSIGN_EXPERIMENTAL: 1

GitHub Actions provides an OIDC token automatically. Cosign uses it to obtain a signing certificate from the Fulcio CA. The signature and certificate are stored in Rekor, a public transparency log.

The SLSA framework

SLSA (Supply-chain Levels for Software Artifacts) defines four levels of build integrity:

LevelRequirements
SLSA 1Build process is documented
SLSA 2Build service generates provenance
SLSA 3Build platform is hardened, provenance is non-falsifiable
SLSA 4Two-person review, hermetic builds
graph TB
  A[Developer pushes code] --> B[CI builds artifact]
  B --> C[Generate provenance attestation]
  C --> D[Sign attestation]
  D --> E[Publish artifact + attestation]
  E --> F[Consumer verifies provenance]
  F --> G{Provenance valid?}
  G -->|Yes| H[Deploy]
  G -->|No| I[Reject]

  style H fill:#2ecc71,color:#fff
  style I fill:#e74c3c,color:#fff

SLSA provenance flow. The build system generates an attestation proving what source code produced the artifact, and consumers verify it before deployment.

Generate SLSA provenance with the official GitHub Action:

- uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
  with:
    base64-subjects: ${{ needs.build.outputs.digest }}

This creates a signed provenance statement linking the artifact to a specific Git commit, build configuration, and builder identity.

Dependency management automation

Dependabot

GitHub’s Dependabot automatically creates pull requests for outdated dependencies:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    open-pull-requests-limit: 10
    groups:
      production-dependencies:
        patterns:
          - "*"
        exclude-patterns:
          - "@types/*"
          - "eslint*"

Grouping related updates into single PRs reduces review burden. Separate production and development dependency updates since they carry different risk levels.

Renovate

Renovate offers more granular control:

{
  "extends": ["config:recommended"],
  "packageRules": [
    {
      "matchPackagePatterns": ["^@aws-sdk/"],
      "groupName": "AWS SDK",
      "automerge": true,
      "matchUpdateTypes": ["patch"]
    },
    {
      "matchDepTypes": ["devDependencies"],
      "automerge": true,
      "matchUpdateTypes": ["minor", "patch"]
    }
  ]
}

Auto-merging patch updates for trusted packages with good test coverage dramatically reduces manual review work. Only auto-merge when your test suite gives sufficient confidence.

Building a supply chain security program

Start with visibility, then add controls:

  1. Generate SBOMs for every production artifact. Store them alongside the artifacts they describe.
  2. Enable dependency scanning in CI. Both Dependabot and Snyk detect known vulnerabilities in dependencies.
  3. Pin dependencies to exact versions. Use lockfiles. Review lockfile changes in PRs.
  4. Sign artifacts with Cosign. Enforce signature verification in your deployment pipeline.
  5. Track SLSA level for critical services. Most teams can reach SLSA 2 with minimal effort.

What comes next

The next article on container security covers securing the packaging format that most modern applications use. You will learn to scan images with Trivy, build minimal distroless images, and implement runtime protection with Falco.

Start typing to search across all content
navigate Enter open Esc close