{"slug": "sip-five-immediate-software-supply-chain-controls", "title": "SIP: Five Immediate Software Supply Chain Controls", "summary": "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.", "body_md": "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?\"\n\nThis is why I compiled a list of five immediate controls that a technical leader can implement rather quickly, perhaps in a few hours:\n\ni. Isolate local AI agents.\n\nii. Freeze unvetted dependencies and disable lifecycle scripts.\n\niii. Use a hardened multi-stage container build.\n\niv. Generate an SBOM and maximum-level provenance for the container image.\n\nv. Scan the SBOM attached to the exact image digest and block fixable Critical CVEs.\n\nThat'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](https://github.com/ContainerSecurity-dev/sip-skill). Tell your agent to use it:\n\n```\nInstall the SIP skill: https://github.com/ContainerSecurity-dev/sip-skill\n```\n\nSIP follows the software supply chain in order:\n\n**AI agent → dependencies → container build → attestations → vulnerability gate**\n\nHere are the five controls, with more detail on each one, and how to implement them in a CI/CD workflow:\n\ni. **Isolate Local AI Agents**.\n\n`sbx run`\n\n).`sbx policy init deny-all`\n\nand store tokens in `sbx`\n\nsecret storage.ii. **Freeze Unvetted Dependencies**.\n\n`npm ci --ignore-scripts`\n\nand add `min-release-age=5`\n\nto your npm configuration.iii. **Harden Container Builds**.\n\n`dhi.io/node:26`\n\n, with minimal attack surface and a non-root user by default.iv. **Generate SBOM and Provenance Attestations**.\n\nv. **Scan the Attested SBOM for Vulnerabilities**.\n\nYou can find a working implementation of this framework in the [SIP sample repository](https://github.com/ContainerSecurity-dev/sip), which implements the five controls in a GitHub Actions workflow and with a sample Node.js application.\n\nInstall the SIP skill in your agentic client, then give the agent this prompt inside the repository you want to secure:\n\n```\n$sip SIP it up! Implement controls ii through v.\n```\n\nThe skill is portable across clients that support Agent Skills. The exact installation and invocation mechanism differs between clients, but the security plan does not.\n\nIf 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.\n\nThis 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`\n\n).\n\nLet's set up the sandbox with a deny-by-default network policy:\n\n``` bash\n$ sbx policy init deny-all\n```\n\nThen allow only the network destinations the agent actually needs:\n\n``` bash\n$ sbx policy allow network \"api.openai.com,github.com,*.npmjs.org\"\n```\n\nStore credentials using `sbx`\n\nrather than exposing the raw token inside the sandbox:\n\n``` bash\n$ sbx secret set openai\n$ sbx secret set github\n```\n\nFor Codex, OAuth can also remain entirely host-side:\n\n``` bash\n$ sbx secret set openai --oauth\n$ sbx run codex .\n```\n\nOther 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.\n\nMany 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.\n\nAlso, 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.\n\nFor npm projects, add the following policies directly to the repository in `.npmrc`\n\n:\n\n```\nmin-release-age=5\nignore-scripts=true\n```\n\n`min-release-age=5`\n\ntells npm not to resolve package versions published within the previous five days.`ignore-scripts=true`\n\nprevents dependency lifecycle scripts from automatically executing during installation.In addition, you should also commit your lockfile (`package-lock.json`\n\n) to the repository. This ensures that the exact versions of dependencies are installed in CI, rather than allowing npm to resolve new versions:\n\n``` bash\n$ npm ci --ignore-scripts\n```\n\nOne subtlety matters: `npm ci`\n\ntrusts versions already recorded in the lockfile,\n\nso resolution-time cooldown settings do not re-check them. CI must therefore\n\nvalidate the publication age of locked packages before installing them and fail\n\nclosed when registry metadata cannot be verified.\n\nIn GitHub Actions:\n\n```\n- name: Set up Node\n  uses: actions/setup-node@<PINNED-SHA>\n  with:\n    node-version: 26\n\n- name: Validate locked dependency age\n  run: node scripts/validate-lockfile-age.mjs\n\n- name: Install dependencies\n  run: npm ci --ignore-scripts\n\n- name: Test\n  run: npm test\n```\n\nThe `validate-lockfile-age.mjs`\n\nscript 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.\n\nIf a package genuinely requires an installation script, do not globally re-enable scripts. Review the package and create an explicit exception.\n\nAgain, the example uses npm, but the same principle applies to other package managers. The immediate rule is:\n\n**No fresh dependencies. No automatic lifecycle scripts. No unlocked installations in CI.**\n\nSome 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!\n\nThe 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.\n\n**Use a multi-stage Dockerfile and Docker Hardened Images.**\n\nA 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.\n\nFor example:\n\n```\n# syntax=docker/dockerfile:1\n\nARG BUILDKIT_SBOM_SCAN_STAGE=true\n\n# Build stage\nFROM dhi.io/node:<version>-<distro>-dev AS build\n\nWORKDIR /app\n\nCOPY package*.json ./\nRUN npm ci --ignore-scripts\n\nCOPY . .\nRUN npm run build\n\n# Runtime stage\nFROM dhi.io/node:<version>-<distro>\n\nCOPY --from=build --chown=node:node /app /app\n\nWORKDIR /app\n\nCMD [\"index.js\"]\n```\n\nThe Docker Hardened Images have near-zero number of exploitable CVEs. Also, they have two sets of images:\n\n`dhi.io/node:<version>-<distro>-dev`\n\n, which include compilers, package managers, and other build tools.`dhi.io/node:<version>-<distro>`\n\n, 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:\n\n```\n- name: Login to DHI\n  uses: docker/login-action@v4\n  with:\n    registry: dhi.io\n    username: ${{ vars.DOCKER_USERNAME }}\n    password: ${{ secrets.DHI_TOKEN }}\n\n- name: Set up Docker Buildx\n  uses: docker/setup-buildx-action@v4\n```\n\nDo not pass credentials using Docker build arguments. Use CI/CD secrets and the `docker/login-action`\n\nto authenticate to the registry. This prevents credentials from being exposed in the build context or image layers.\n\nYet, we have one thing we did not explain about the Dockerfile:\n\n```\nARG BUILDKIT_SBOM_SCAN_STAGE=true\n```\n\nWe will explain it in the next section.\n\nNow record **what was built** and **how it was built**.\n\nBuildKit (being the thing that builds your Docker image) supports two particularly useful attestations:\n\nDocker recommends max-level provenance when possible. SBOM generation must be explicitly enabled.\n\nFirst authenticate to the target registry:\n\n```\n- name: Login to GHCR\n  uses: docker/login-action@v4\n  with:\n    registry: ghcr.io\n    username: ${{ github.actor }}\n    password: ${{ secrets.GITHUB_TOKEN }}\n```\n\nThen build a candidate image identified by the Git commit:\n\n```\n- name: Build and attest candidate\n  id: build\n  uses: docker/build-push-action@v7\n  with:\n    context: .\n    tags: ghcr.io/${{ github.repository }}:${{ github.sha }}\n    sbom: true\n    provenance: mode=max\n    outputs: type=image,push=true,oci-mediatypes=true,oci-artifact=true\n```\n\nBuildKit normally stores attestations alongside the image index. Setting `oci-artifact=true`\n\nformats the attestation manifests as OCI artifacts and adds a `subject`\n\npointing back to the image manifest. This allows OCI-aware tooling and registries to discover the relationship between the image and its attestations.\n\nThe build action also returns the resulting image digest:\n\n```\n${{ steps.build.outputs.digest }}\n```\n\nUse that digest from this point onward. A tag can move; a digest identifies the exact artifact that was built.\n\nThe result is now:\n\nBecause `BUILDKIT_SBOM_SCAN_STAGE=true`\n\nwas 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.\n\nThe final stage is to scan the SBOM for vulnerabilities, before you can take a SIP of your favorite beverage.\n\nDo not merely ask a scanner to look for an attached SBOM. Discovery can be\n\nbest-effort and fall back to inspecting image layers. The gate should explicitly\n\nextract the attached SBOM from the exact image digest, validate it, and scan that\n\nfile.\n\nInstall a pinned version of Trivy in GitHub Actions:\n\n```\n- name: Install Trivy\n  uses: aquasecurity/setup-trivy@<PINNED-SHA>\n  with:\n    version: <PINNED-TRIVY-VERSION>\n```\n\nThen retrieve and scan the attestation from the exact image digest created by\n\nthe build:\n\n```\n- name: Retrieve and scan attested SBOM\n  run: |\n    docker buildx imagetools inspect \\\n      \"ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}\" \\\n      --format '{{ json .SBOM.SPDX }}' > sbom.spdx.json\n\n    jq -e \\\n      '.spdxVersion and (.packages | type == \"array\") and (.packages | length > 0)' \\\n      sbom.spdx.json > /dev/null\n\n    trivy sbom \\\n      --scanners vuln \\\n      --severity CRITICAL \\\n      --ignore-unfixed \\\n      --exit-code 1 \\\n      sbom.spdx.json\n```\n\nThis makes the attestation mandatory: a missing or malformed SPDX document\n\nfails before Trivy runs.\n\nThe remaining policy is intentionally simple:\n\n```\n--severity CRITICAL\n--ignore-unfixed\n--exit-code 1\n```\n\nA fixable Critical vulnerability therefore fails the workflow.\n\nOnly **after this gate succeeds** should the candidate become a release image. For example:\n\n```\n- name: Promote image\n  run: |\n    docker buildx imagetools create \\\n      --tag ghcr.io/${{ github.repository }}:latest \\\n      ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}\n```\n\nThis promotes the already scanned digest rather than rebuilding the application after the security check.\n\nThe resulting GitHub Actions flow looks like this:\n\n```\nname: SIP Supply Chain\n\non:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read\n  packages: write\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@<PINNED-SHA>\n\n      # SIP ii — Freeze dependencies\n      - name: Set up Node\n        uses: actions/setup-node@<PINNED-SHA>\n        with:\n          node-version: 26\n\n      - name: Validate locked dependency age\n        run: node scripts/validate-lockfile-age.mjs\n\n      - name: Install locked dependencies\n        run: npm ci --ignore-scripts\n\n      - name: Test\n        run: npm test\n\n      # SIP iii — Harden the container build\n      - name: Login to DHI\n        uses: docker/login-action@<PINNED-SHA>\n        with:\n          registry: dhi.io\n          username: ${{ vars.DOCKER_USERNAME }}\n          password: ${{ secrets.DHI_TOKEN }}\n\n      - name: Login to GHCR\n        uses: docker/login-action@<PINNED-SHA>\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up Buildx\n        uses: docker/setup-buildx-action@<PINNED-SHA>\n\n      # SIP iv — Generate attestations\n      - name: Build and attest candidate\n        id: build\n        uses: docker/build-push-action@<PINNED-SHA>\n        with:\n          context: .\n          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}\n          sbom: true\n          provenance: mode=max\n          outputs: type=image,push=true,oci-mediatypes=true,oci-artifact=true\n\n      # SIP v — Scan the attested SBOM\n      - name: Install Trivy\n        uses: aquasecurity/setup-trivy@<PINNED-SHA>\n        with:\n          version: <PINNED-TRIVY-VERSION>\n\n      - name: Retrieve and gate attached-SBOM Critical CVEs\n        run: |\n          docker buildx imagetools inspect \\\n            \"ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}\" \\\n            --format '{{ json .SBOM.SPDX }}' > sbom.spdx.json\n          jq -e \\\n            '.spdxVersion and (.packages | type == \"array\") and (.packages | length > 0)' \\\n            sbom.spdx.json > /dev/null\n          trivy sbom \\\n            --scanners vuln \\\n            --severity CRITICAL \\\n            --ignore-unfixed \\\n            --exit-code 1 \\\n            sbom.spdx.json\n\n      # Promote exactly what was scanned\n      - name: Promote image\n        run: |\n          docker buildx imagetools create \\\n            --tag ghcr.io/${{ github.repository }}:latest \\\n            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}\n```\n\nThis compact workflow shows the artifact chain. In production, trigger SIP on\n\npull requests and give SIP ii, SIP iii–iv, and SIP v separate jobs so failures\n\nare visible. Keep credential-bearing PR jobs behind a protected environment;\n\nfork PRs must not receive registry credentials. Promotion remains release-only.\n\nSIP therefore creates one continuous chain:\n\n**Sandbox the agent\n→ delay and constrain dependencies.\n→ build with a hardened multi-stage Dockerfile.\n→ attest every relevant build stage\n→ scan that attested inventory\n→ release only the artifact that passed.**\n\nThe controls are small individually. Their value comes from the fact that each one strengthens the input to the next.\n\nSIP 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.\n\nThe 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](https://github.com/ContainerSecurity-dev/sip-sample-repository).", "url": "https://wpnews.pro/news/sip-five-immediate-software-supply-chain-controls", "canonical_source": "https://dev.to/docker/sip-five-immediate-software-supply-chain-controls-4836", "published_at": "2026-08-17 11:52:57+00:00", "updated_at": "2026-08-17 12:13:13.057491+00:00", "lang": "en", "topics": ["ai-safety", "ai-agents", "developer-tools"], "entities": ["Docker Sandboxes", "GitHub Actions", "SIP", "SIP Agent Skill", "SIP sample repository"], "alternates": {"html": "https://wpnews.pro/news/sip-five-immediate-software-supply-chain-controls", "markdown": "https://wpnews.pro/news/sip-five-immediate-software-supply-chain-controls.md", "text": "https://wpnews.pro/news/sip-five-immediate-software-supply-chain-controls.txt", "jsonld": "https://wpnews.pro/news/sip-five-immediate-software-supply-chain-controls.jsonld"}}