Software Supply Chain Security

Secure your software supply chain. Covers SBOM, dependency scanning, SLSA framework, signed builds, artifact verification, and protecting against supply chain attacks.

Software Supply Chain Security ensures that every component in a software release—from source code to deployed artifact—is trustworthy, verifiable, and protected against tampering from origin to execution. It matters when a dependency, build script, or container image is compromised, and the resulting binary runs in production with undetected vulnerabilities, backdoors, or supply chain attacks like the SolarWinds breach.

Securing the Source: From Git to First Build

The foundation of software supply chain security begins with the source code repository. All changes must be traceable, authenticated, and validated before being accepted into the main branch. The primary mechanism is signed commits using GPG, with verified pull request (PR) merges and protected branches.

A PR must pass at least two checks: a CI pipeline that runs tests and linting, and a manual review with at least one approval. The CI system enforces that all commits are signed with a GPG key and that the PR is merged with a GPG-signed merge commit.


git log --format="%H %an %ae" HEAD~1..HEAD
# Output:
# a1b2c3d4e5f67890123456789012345678901234 Alice Author [email protected]
# 
# Check signature:
gpg --verify HEAD.asc HEAD
# Expected output:
# gpg: Signature made Mon Apr  5 10:30:00 2024 UTC
# gpg:                using RSA key 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B
# gpg: Good signature from "Alice Author <[email protected]>"
# gpg:                 Primary key fingerprint: 1A2B 3C4D 5E6F 7A8B 9C0D 1E2F 3A4B 5C6D 7E8F 9A0B

If the HEAD.asc file is missing or the signature fails, the CI pipeline fails with the error:

ERROR: GPG signature verification failed for merge commit
  Expected: 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B
  Got:      2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B1C

This failure indicates a missing or misconfigured GPG key, or a corrupted merge commit.

To enforce consistent builds, the CI system uses a build.yml file in .github/workflows/, which defines a job that runs make build on each push to main, with the Makefile including a build target that produces a dist/app.tar.gz.

build:
	@echo "Building application..."
	@mkdir -p dist
	@go build -o dist/app app/main.go
	@tar -czf dist/app.tar.gz -C dist app
	@sha256sum dist/app.tar.gz > dist/app.tar.gz.sha256
	@gpg --detach-sign dist/app.tar.gz
	@echo "Build completed: dist/app.tar.gz"

The dist/app.tar.gz.sha256 file contains a single line:

a1b2c3d4e5f67890123456789012345678901234567890123456789012345678  dist/app.tar.gz

The CI job uploads this file, along with the GPG signature, as artifacts.

Verifying Build Artifacts: From CI to Container Images

The next step is verifying that the build artifacts are produced by a trusted pipeline and are reproducible. The pipeline must sign the output with a digital signature and generate a provenance record.

The build job outputs a provenance.json file, which includes:

{
  "subject": {
    "name": "app.tar.gz",
    "digest": "sha256:a1b2c3d4e5f67890123456789012345678901234567890123456789012345678"
  },
  "builder": {
    "name": "Makefile",
    "version": "4.3"
  },
  "build": {
    "command": "make build",
    "startedAt": "2024-04-05T10:30:00Z",
    "finishedAt": "2024-04-05T10:35:00Z",
    "inputs": [
      {
        "name": "app/main.go",
        "digest": "sha256:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
      },
      {
        "name": "Makefile",
        "digest": "sha256:fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
      }
    ],
    "outputs": [
      {
        "name": "dist/app.tar.gz",
        "digest": "sha256:a1b2c3d4e5f67890123456789012345678901234567890123456789012345678"
      }
    ]
  },
  "signatures": [
    {
      "keyId": "1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B",
      "signature": "-----BEGIN PGP SIGNATURE-----\n...\n-----END PGP SIGNATURE-----"
    }
  ]
}

The provenance.json is signed with the same GPG key used for the source commit. The signing key must be registered in a trusted keyring, and the pipeline validates that the signing key is in the set of known keys.

During deployment, the runtime environment loads the provenance.json and verifies the signature using gpg --verify provenance.json.asc provenance.json.

If the key is not trusted, the system fails with:

ERROR: GPG key not in trusted keyring
  Expected: 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B
  Found:    2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B1C

This indicates a misconfigured keyring or a key not distributed to the deployment system.

Packaging and Distributing: From Artifact to Runtime

Once the build artifact is verified, it is packaged into a container image and published to a registry. The registry must support content trust through Notary or a similar system.

The build pipeline pushes the image with docker build and docker push, and then signs it using cosign.

# Build and push image
docker build -t registry.example.com/app:v1.2.3 .
docker push registry.example.com/app:v1.2.3

# Sign the image with cosign
cosign sign registry.example.com/app:v1.2.3
# Prompts for OIDC login, then uploads signature to the registry

The signature is stored as a cosign artifact in the image’s manifest. The image now has a cosign field in its manifest.json:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
  "config": {
    "mediaType": "application/vnd.docker.container.image.v1+json",
    "digest": "sha256:abc123def456...",
    "size": 1024
  },
  "layers": [
    {
      "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
      "digest": "sha256:xyz789...",
      "size": 2048
    }
  ],
  "cosign": {
    "signatures": [
      {
        "payload": "eyJ0aW1lIjogIjIwMjQtMDQtMDVUMTA6MzA6MDAiLCAiZGF0YSI6ICJ0aGlzIGlzIGEgYXJ0aWNsZSIsICJ2ZXJzaW9uIjogMS4yLjMiLCAiZGlzdCI6ICJhcnRpY2xlLmRhdGEifQ==",
        "signature": "MEQCIBF6c1b2sK1pL0H1qK1vT4Xc3jY3zD9c9B2vE1YBnCnC",
        "keyid": "1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B",
        "created": "2024-04-05T10:30:00Z"
      }
    ]
  }
}

The payload is a JSON structure containing the image’s metadata, including the version, the time of signing, and the original dist/app.tar.gz digest.

When the runtime pulls the image, it must verify the signature using cosign verify.

cosign verify registry.example.com/app:v1.2.3
# Output:
# Verification for registry.example.com/app:v1.2.3
# The image signature is valid and issued by 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B
# The payload contains:
#   version: 1.2.3
#   time: 2024-04-05T10:30:00Z
#   dist: app.tar.gz

If the payload does not match the expected values, or the key ID is not in the trusted keyring, the system logs:

ERROR: Image signature verification failed
  Expected version: 1.2.3
  Got: 1.2.4
  Key not in trusted keyring: 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B

This error points to a mismatch between the build and deployment configurations, or a missing key.

Deploying with Trust: From Image to Execution

Deployment systems must enforce that only verified images are run. This is achieved by a trusted deployment pipeline that pulls images and verifies their signatures before starting containers.

The deployment configuration includes a deploy.yaml file that defines the image to run and the verification policy.

image: registry.example.com/app:v1.2.3
trusted: true
verify:
  - type: "cosign"
    keyring: "/etc/trusted-keys.gpg"
    expectedVersion: "1.2.3"
    expectedDigest: "sha256:a1b2c3d4e5f67890123456789012345678901234567890123456789012345678"

The deployment system reads this file and runs:

# Verify the image
cosign verify \
  --keyring /etc/trusted-keys.gpg \
  --expected-version 1.2.3 \
  --expected-digest sha256:a1b2c3d4e5f67890123456789012345678901234567890123456789012345678 \
  registry.example.com/app:v1.2.3

If any verification step fails, the deployment aborts with a clear error:

ERROR: Deployment failed: image verification
  - Signature invalid: key 1A2B3C4D5E6F7A8B9C0D1E2F3A4B5C6D7E8F9A0B not in keyring
  - Expected version 1.2.3, got 1.2.4
  - Expected digest sha256:a1b2c3d4e5f67890123456789012345678901234567890123456789012345678, got sha256:b2c3d4e5f6789012345678901234567890123456789012345678901234567890

The deployment system logs all verification details and provides the full cosign verify output for debugging.

Continuous Verification: Post-Deployment and Auditing

After deployment, the system continuously audits the supply chain. A periodic job runs cosign verify-all on all deployed images and logs the results.

# Verify all images in a deployment manifest
cosign verify-all \
  --keyring /etc/trusted-keys.gpg \
  --output-format json \
  --output-file /var/log/supply-chain/verify-all.json \
  --manifest-file /etc/deployment/manifest.yaml

The manifest.yaml file contains a list of images and their expected versions.

- image: registry.example.com/app:v1.2.3
  expectedVersion: "1.2.3"
  expectedDigest: "sha256:a1b2c3d4e5f67890123456789012345678901234567890123456789012345678"
- image: registry.example.com/db:v1.1.0
  expectedVersion: "1.1.0"
  expectedDigest: "sha256:c3d4e5f678901234567890123456789012345678901234567890123456789012"

The verify-all.json output includes a list of verification results, with one entry per image, including the raw GPG output, the verification status, and any discrepancies.

This log is used for incident response and compliance audits. When a security team investigates a breach, they can trace back the supply chain from the running process to the original Makefile and provenance.json files.

Failure Modes and Sharp Edges

  WARNING: No provenance.json found in build directory
  Using default version: 1.0.0

These subtle but critical details ensure that the software supply chain is not just a pipeline, but a trusted, auditable, and resilient system.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.