$ cat owasp/a03.md
A03:2025
Software Supply Chain Failures
The code you did not write - packages, images, actions - is most of what you ship.
## 1 · why it happens
A typical app has hundreds of dependencies, each with their own dependencies. Loose version ranges (^, ~, latest) mean the code you deploy tomorrow may not be the code you tested today.
Attackers know this. They publish malicious versions from compromised maintainer accounts, typo-squat popular names, or upload a public package with the same name as your internal one (dependency confusion) and a higher version number.
# JavaScript
// package.json
"dependencies": {
"express": "^4.18.0",
"@acme/auth-utils": "*" // internal package, no registry scope
}
// CI
npm install // resolves whatever is newest today// package.json
"dependencies": {
"express": "4.21.2",
"@acme/auth-utils": "2.3.1"
}
// .npmrc - internal scope only from your private registry
@acme:registry=https://npm.internal.acme.example/
// CI
npm ci // installs exactly what the lockfile says# C#
<!-- .csproj -->
<PackageReference Include="Newtonsoft.Json" Version="13.*" />
<PackageReference Include="Acme.Internal.Auth" Version="*" /><!-- .csproj -->
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- nuget.config: packageSourceMapping sends Acme.* only to the private feed -->## 2 · how attackers find it from outside
| public source | what it tells them |
|---|---|
| Public GitHub repos | package.json / .csproj files list your exact dependencies and internal package names. |
| Job postings | "Experience with Angular 12, .NET 6, Elasticsearch 7" - your stack and how old it is. |
| JavaScript bundles | Library names and versions in comments and banners, e.g. jQuery v1.12.4. |
| HTTP headers | Framework and server versions, matched against public CVE lists. |
→ all OSINT sources · run them on yourself
## 3 · how it's abused
⚠ Recon your own assets, or targets you have written permission to test. Passive OSINT reads public data, but using it to target someone else's systems can still be illegal or against their terms. Examples on this site use example.com placeholders.
npm publish # name: "@acme/auth-utils", version: "99.0.0"Dependency confusion: a public package with your internal name and a higher version wins if your registry config allows it.
"postinstall": "node collect.js"Install scripts run on every developer machine and CI runner that installs the package.
FROM node:latestA mutable tag: whatever is published under "latest" next week goes straight into your build.
Payload examples adapted from PayloadsAllTheThings (MIT, © Swissky). Full lists:Dependency Confusion
## 4 · fix
- Commit lockfiles and install with npm ci / locked restore.
- Pin Docker images by digest and GitHub Actions by commit SHA.
- Scope internal package names to your private registry.
- Update through reviewed pull requests (Dependabot / Renovate), not silent auto-updates.
- Generate an SBOM and scan it on every build.
## 5 · detect
Trivy
Vulnerabilities and misconfigurations in images, dependencies and IaC.
fix at sourceGitleaks
Find secrets in your git history before someone else does.
Official description: OWASP A03:2025 Software Supply Chain Failures