Skip to main content

Dependency Pinning

Overview

HVE Core enforces dependency pinning to mitigate supply chain attacks. Every dependency reference in the repository must resolve to a specific, immutable version. The Test-DependencyPinning.ps1 scanner validates all dependency types during CI and produces SARIF reports for GitHub code scanning integration.

Dependency TypePinning StrategyExample
GitHub ActionsFull 40-character commit SHAactions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
npmExact version (no ranges)"eslint": "9.18.0"
pipExact version with ==requests==2.31.0
Workflow npm commandsnpm ci enforcementnpm ci instead of npm install
Shell downloadsChecksum verificationsha256sum --check after download

npm: Exact-Version Enforcement

NOTE

npm dependencies use exact-version enforcement rather than SHA-pinning. Unlike GitHub Actions (where commit SHAs identify immutable source snapshots), npm packages are published as immutable registry artifacts. An exact version string like 9.18.0 is already a unique, deterministic reference.

What Is Validated

The scanner rejects any version string that contains range operators or wildcards:

{
"dependencies": {
"valid": "9.18.0",
"invalid-caret": "^9.18.0",
"invalid-tilde": "~9.18.0",
"invalid-wildcard": "9.*",
"invalid-range": ">=9.0.0 <10.0.0"
}
}

Validation Regex

^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$

This permits standard semver (1.2.3), pre-release tags (1.2.3-beta.1), and build metadata (1.2.3+build.42), while rejecting all range operators.

Why Not SHA-Pinning for npm

CriterionSHA-Pinning (GitHub Actions)Exact-Version (npm)
Registry modelGit repositories with mutable tagsImmutable package tarballs
Mutability riskTags can be force-pushed to different commitsPublished versions are permanently immutable
Audit toolingnpm audit cross-references semver, not SHAsFull compatibility with npm audit
Lockfile integrationN/Apackage-lock.json records integrity hashes
Human readability40-char hex strings obscure the actual versionVersion is self-documenting

GitHub Actions: SHA Pinning

GitHub Actions references must use full 40-character commit SHAs because action tags (like v4) are mutable Git references that can be retargeted to arbitrary commits.

# Rejected: mutable tag reference
- uses: actions/checkout@v4

# Accepted: immutable SHA reference
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.2.2

The scanner validates that the SHA is a real 40-character hexadecimal string and optionally checks staleness against the GitHub API.

pip: Exact-Version Pinning

Python dependencies must use the == operator for exact version pinning. The scanner excludes virtual environment directories (.venv, venv, .tox, .nox, __pypackages__) to avoid false positives from installed package metadata.

# Accepted
requests==2.31.0
flask==3.0.0

# Rejected
requests>=2.31.0
flask~=3.0

Workflow npm Commands: npm ci Enforcement

CI workflow YAML files are scanned for npm commands that modify the dependency tree at install time. These commands resolve version ranges against the registry, producing non-deterministic installs that can pull in compromised packages.

Detected Commands

The scanner inspects run: blocks in workflow YAML with indentation-aware parsing and flags these commands:

FlaggedReason
npm installResolves ranges from package.json, ignoring lockfile
npm iAlias for npm install
npm updateUpgrades to latest versions within semver ranges
npm install-testCombines install and test in a non-deterministic way

Safe Commands

These npm commands are not flagged because they do not modify the dependency tree:

  • npm ci: Installs exactly from the lockfile, removing node_modules first
  • npm run: Executes a script defined in package.json
  • npm test: Alias for npm run test
  • npm audit: Reports known vulnerabilities without installing
  • npx: Runs a package binary without modifying dependencies

Remediation

Replace flagged commands with npm ci for deterministic, lockfile-based installs:

# Rejected: resolves version ranges from the registry
- run: npm install

# Accepted: installs exactly what the lockfile specifies
- run: npm ci

File Scope

The scanner processes files matching .github/workflows/*.yml and .github/workflows/*.yaml.

Shell Downloads: Checksum Verification

Shell scripts that download files from the internet must verify checksums to prevent tampered or corrupted binaries from entering the build environment.

Detection

The scanner identifies download commands matching curl or wget with an HTTP/HTTPS URL. It then checks the next five lines for a checksum verification command. If no verification is found, the download is flagged.

Accepted Verification Commands

Any of these patterns within five lines of the download satisfies the check:

  • sha256sum: GNU coreutils checksum
  • shasum: macOS/BSD checksum utility
  • Get-FileHash: PowerShell checksum cmdlet
  • openssl dgst -sha256: OpenSSL digest
  • sha256sum -c: Checksum file verification

Examples

# Accepted — checksum verified immediately after download
curl -Lo tool.tar.gz https://example.com/tool-v1.0.tar.gz
echo "abc123... tool.tar.gz" | sha256sum --check

# Rejected — no checksum verification after download
wget https://example.com/tool-v1.0.tar.gz
tar xzf tool-v1.0.tar.gz

Scanned Files

The scanner processes files matching .devcontainer/scripts/*.sh and scripts/*.sh, excluding fixture directories used in tests.

CI Integration

The dependency pinning scanner runs in CI as part of the security validation workflow. It produces SARIF 2.1.0 output that integrates with GitHub code scanning.

Severity Mapping

Scanner SeveritySARIF LevelTrigger
HigherrorUnpinned or mutable dependency reference
MediumwarningStale pinned version with available update
LownoteInformational findings

Running Locally

# Full scan with SARIF output
./scripts/security/Test-DependencyPinning.ps1

# Results appear in logs/dependency-pinning-results.json

🤖 Crafted with precision by ✨Copilot following brilliant human instruction, then carefully refined by our team of discerning human reviewers.