cd /news/ai-safety/sip-five-immediate-software-supply-c… · home topics ai-safety article
[ARTICLE · art-99753] src=dev.to ↗ pub= topic=ai-safety verified=true sentiment=· neutral

SIP: Five Immediate Software Supply Chain Controls

A developer introduced SIP, a set of five immediate software supply chain controls for technical leaders, covering AI agent isolation, dependency freezing, hardened container builds, SBOM generation, and vulnerability scanning. The framework includes a portable Agent Skill and a sample repository implementing the controls in a GitHub Actions workflow.

read11 min views1 publishedAug 17, 2026

I talk about software supply chain security in different capacities, and I often get the same question: "If we can only do a few things, what should we do first?"

This is why I compiled a list of five immediate controls that a technical leader can implement rather quickly, perhaps in a few hours:

i. Isolate local AI agents.

ii. Freeze unvetted dependencies and disable lifecycle scripts.

iii. Use a hardened multi-stage container build.

iv. Generate an SBOM and maximum-level provenance for the container image.

v. Scan the SBOM attached to the exact image digest and block fixable Critical CVEs.

That's it. Five controls. And because I thought people would give it to their AI coding agents, I have also created a portable SIP Agent Skill. Tell your agent to use it:

Install the SIP skill: https://github.com/ContainerSecurity-dev/sip-skill

SIP follows the software supply chain in order:

AI agent → dependencies → container build → attestations → vulnerability gate

Here are the five controls, with more detail on each one, and how to implement them in a CI/CD workflow:

i. Isolate Local AI Agents.

sbx run

).sbx policy init deny-all

and store tokens in sbx

secret storage.ii. Freeze Unvetted Dependencies.

npm ci --ignore-scripts

and add min-release-age=5

to your npm configuration.iii. Harden Container Builds.

dhi.io/node:26

, with minimal attack surface and a non-root user by default.iv. Generate SBOM and Provenance Attestations.

v. Scan the Attested SBOM for Vulnerabilities.

You can find a working implementation of this framework in the SIP sample repository, which implements the five controls in a GitHub Actions workflow and with a sample Node.js application.

Install the SIP skill in your agentic client, then give the agent this prompt inside the repository you want to secure:

$sip SIP it up! Implement controls ii through v.

The skill is portable across clients that support Agent Skills. The exact installation and invocation mechanism differs between clients, but the security plan does not.

If you, like me, prefer to do things yourself, you can follow the instructions here to get an idea of how to implement SIP in your own machine and CI/CD workflow.

This is the only control that is not implemented in CI/CD and protects your own machine. As we are using AI coding agents increasingly, they are being targeted by attackers more and more. If you have a coding agent installed, you should treat it like a hacker's agent. Agents can be easily tricked into exfiltrating secrets or executing malicious code. The safest way to run an agent is inside a sandboxed microVM, such as Docker Sandboxes (sbx

).

Let's set up the sandbox with a deny-by-default network policy:

$ sbx policy init deny-all

Then allow only the network destinations the agent actually needs:

$ sbx policy allow network "api.openai.com,github.com,*.npmjs.org"

Store credentials using sbx

rather than exposing the raw token inside the sandbox:

$ sbx secret set openai
$ sbx secret set github

For Codex, OAuth can also remain entirely host-side:

$ sbx secret set openai --oauth
$ sbx run codex .

Other coding agents and their clients are similar. Docker Sandboxes runs the agent inside an isolated environment, supports deny-by-default network policies, and stores secrets in the host's credential store. Credentials are automatically injected through the host-side proxy without making their raw values readable to the agent.

Many supply chain attacks start with a dependency that is either malicious or compromised. In most cases, a compromised dependency is detected within the first few days after publication. A five-day cooldown period allows time for the community to discover and report malicious packages.

Also, many supply chain attacks rely on lifecycle scripts that execute automatically during installation. Disabling lifecycle scripts prevents automatic execution of scripts that could compromise the build or leak secrets.

For npm projects, add the following policies directly to the repository in .npmrc

:

min-release-age=5
ignore-scripts=true

min-release-age=5

tells npm not to resolve package versions published within the previous five days.ignore-scripts=true

prevents dependency lifecycle scripts from automatically executing during installation.In addition, you should also commit your lockfile (package-lock.json

) to the repository. This ensures that the exact versions of dependencies are installed in CI, rather than allowing npm to resolve new versions:

$ npm ci --ignore-scripts

One subtlety matters: npm ci

trusts versions already recorded in the lockfile,

so resolution-time cooldown settings do not re-check them. CI must therefore

validate the publication age of locked packages before installing them and fail

closed when registry metadata cannot be verified.

In GitHub Actions:

- name: Set up Node
  uses: actions/setup-node@<PINNED-SHA>
  with:
    node-version: 26

- name: Validate locked dependency age
  run: node scripts/validate-lockfile-age.mjs

- name: Install dependencies
  run: npm ci --ignore-scripts

- name: Test
  run: npm test

The validate-lockfile-age.mjs

script checks the publication date of each dependency in the lockfile and fails if any package is younger than five days. This ensures that only vetted dependencies are installed in CI/CD. The script is already available in the SIP skill repository.

If a package genuinely requires an installation script, do not globally re-enable scripts. Review the package and create an explicit exception.

Again, the example uses npm, but the same principle applies to other package managers. The immediate rule is:

No fresh dependencies. No automatic lifecycle scripts. No unlocked installations in CI.

Some package managers might not have a built-in cooldown mechanism. In that case, you can implement a custom script to check the publication date of each dependency before installation. You could perhaps ask your AI coding agent to write one for you, but make sure to run it inside a sandboxed microVM!

The application is now built from a constrained dependency graph. The next step is to control what ends up on the container image and goes into production. Believe it or not, many CVEs, if not most, come from your base images, not your application code. So, let's tighten things up.

Use a multi-stage Dockerfile and Docker Hardened Images.

A multi-stage build separates the build environment from the runtime environment. The build stage can include compilers, package managers, and other tools needed to build the application, while the runtime stage contains only the minimal set of files needed to run the application.

For example:


ARG BUILDKIT_SBOM_SCAN_STAGE=true

FROM dhi.io/node:<version>-<distro>-dev AS build

WORKDIR /app

COPY package*.json ./
RUN npm ci --ignore-scripts

COPY . .
RUN npm run build

FROM dhi.io/node:<version>-<distro>

COPY --from=build --chown=node:node /app /app

WORKDIR /app

CMD ["index.js"]

The Docker Hardened Images have near-zero number of exploitable CVEs. Also, they have two sets of images:

dhi.io/node:<version>-<distro>-dev

, which include compilers, package managers, and other build tools.dhi.io/node:<version>-<distro>

, have no package managers, no shell, and a non-root user by default. This reduces the attack surface of the final image.Before building in GitHub Actions, authenticate to DHI:

- name: Login to DHI
  uses: docker/login-action@v4
  with:
    registry: dhi.io
    username: ${{ vars.DOCKER_USERNAME }}
    password: ${{ secrets.DHI_TOKEN }}

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v4

Do not pass credentials using Docker build arguments. Use CI/CD secrets and the docker/login-action

to authenticate to the registry. This prevents credentials from being exposed in the build context or image layers.

Yet, we have one thing we did not explain about the Dockerfile:

ARG BUILDKIT_SBOM_SCAN_STAGE=true

We will explain it in the next section.

Now record what was built and how it was built.

BuildKit (being the thing that builds your Docker image) supports two particularly useful attestations:

Docker recommends max-level provenance when possible. SBOM generation must be explicitly enabled.

First authenticate to the target registry:

- name: Login to GHCR
  uses: docker/login-action@v4
  with:
    registry: ghcr.io
    username: ${{ github.actor }}
    password: ${{ secrets.GITHUB_TOKEN }}

Then build a candidate image identified by the Git commit:

- name: Build and attest candidate
  id: build
  uses: docker/build-push-action@v7
  with:
    context: .
    tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
    sbom: true
    provenance: mode=max
    outputs: type=image,push=true,oci-mediatypes=true,oci-artifact=true

BuildKit normally stores attestations alongside the image index. Setting oci-artifact=true

formats the attestation manifests as OCI artifacts and adds a subject

pointing back to the image manifest. This allows OCI-aware tooling and registries to discover the relationship between the image and its attestations.

The build action also returns the resulting image digest:

${{ steps.build.outputs.digest }}

Use that digest from this point onward. A tag can move; a digest identifies the exact artifact that was built.

The result is now:

Because BUILDKIT_SBOM_SCAN_STAGE=true

was declared in the Dockerfile, the SBOM also records packages from the relevant build stages instead of describing only the minimal final runtime image. This is important, because a vulnerability in a build-time dependency can still compromise the final image. And it shows why it's important to generate SBOMs during the build rather than after the fact.

The final stage is to scan the SBOM for vulnerabilities, before you can take a SIP of your favorite beverage.

Do not merely ask a scanner to look for an attached SBOM. Discovery can be

best-effort and fall back to inspecting image layers. The gate should explicitly

extract the attached SBOM from the exact image digest, validate it, and scan that

file.

Install a pinned version of Trivy in GitHub Actions:

- name: Install Trivy
  uses: aquasecurity/setup-trivy@<PINNED-SHA>
  with:
    version: <PINNED-TRIVY-VERSION>

Then retrieve and scan the attestation from the exact image digest created by

the build:

- name: Retrieve and scan attested SBOM
  run: |
    docker buildx imagetools inspect \
      "ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}" \
      --format '{{ json .SBOM.SPDX }}' > sbom.spdx.json

    jq -e \
      '.spdxVersion and (.packages | type == "array") and (.packages | length > 0)' \
      sbom.spdx.json > /dev/null

    trivy sbom \
      --scanners vuln \
      --severity CRITICAL \
      --ignore-unfixed \
      --exit-code 1 \
      sbom.spdx.json

This makes the attestation mandatory: a missing or malformed SPDX document

fails before Trivy runs.

The remaining policy is intentionally simple:

--severity CRITICAL
--ignore-unfixed
--exit-code 1

A fixable Critical vulnerability therefore fails the workflow.

Only after this gate succeeds should the candidate become a release image. For example:

- name: Promote image
  run: |
    docker buildx imagetools create \
      --tag ghcr.io/${{ github.repository }}:latest \
      ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

This promotes the already scanned digest rather than rebuilding the application after the security check.

The resulting GitHub Actions flow looks like this:

name: SIP Supply Chain

on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@<PINNED-SHA>

      - name: Set up Node
        uses: actions/setup-node@<PINNED-SHA>
        with:
          node-version: 26

      - name: Validate locked dependency age
        run: node scripts/validate-lockfile-age.mjs

      - name: Install locked dependencies
        run: npm ci --ignore-scripts

      - name: Test
        run: npm test

      - name: Login to DHI
        uses: docker/login-action@<PINNED-SHA>
        with:
          registry: dhi.io
          username: ${{ vars.DOCKER_USERNAME }}
          password: ${{ secrets.DHI_TOKEN }}

      - name: Login to GHCR
        uses: docker/login-action@<PINNED-SHA>
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up Buildx
        uses: docker/setup-buildx-action@<PINNED-SHA>

      - name: Build and attest candidate
        id: build
        uses: docker/build-push-action@<PINNED-SHA>
        with:
          context: .
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
          sbom: true
          provenance: mode=max
          outputs: type=image,push=true,oci-mediatypes=true,oci-artifact=true

      - name: Install Trivy
        uses: aquasecurity/setup-trivy@<PINNED-SHA>
        with:
          version: <PINNED-TRIVY-VERSION>

      - name: Retrieve and gate attached-SBOM Critical CVEs
        run: |
          docker buildx imagetools inspect \
            "ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}" \
            --format '{{ json .SBOM.SPDX }}' > sbom.spdx.json
          jq -e \
            '.spdxVersion and (.packages | type == "array") and (.packages | length > 0)' \
            sbom.spdx.json > /dev/null
          trivy sbom \
            --scanners vuln \
            --severity CRITICAL \
            --ignore-unfixed \
            --exit-code 1 \
            sbom.spdx.json

      - name: Promote image
        run: |
          docker buildx imagetools create \
            --tag ghcr.io/${{ github.repository }}:latest \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

This compact workflow shows the artifact chain. In production, trigger SIP on

pull requests and give SIP ii, SIP iii–iv, and SIP v separate jobs so failures

are visible. Keep credential-bearing PR jobs behind a protected environment;

fork PRs must not receive registry credentials. Promotion remains release-only.

SIP therefore creates one continuous chain:

Sandbox the agent → delay and constrain dependencies. → build with a hardened multi-stage Dockerfile. → attest every relevant build stage → scan that attested inventory → release only the artifact that passed.

The controls are small individually. Their value comes from the fact that each one strengthens the input to the next.

SIP is a five-step emergency plan for reducing software supply chain risk across AI agents, dependencies, containers, attestations, and releases. It is designed to be implemented quickly and effectively, providing immediate security benefits.

The presented GitHub Actions workflow is a working implementation of the SIP framework. It's rather simplistic for educational purposes, if you want a more robust implementation, check out the SIP sample repository.

── more in #ai-safety 4 stories · sorted by recency
── more on @docker sandboxes 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/sip-five-immediate-s…] indexed:0 read:11min 2026-08-17 ·