Skip to main content

In this article

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. SHA-pin validation applies the same expanded scope as the npm-command scan: it covers workflow files (.github/workflows/*.yml and .github/workflows/*.yaml) and composite action definitions (.github/actions/**/*.yml and .github/actions/**/*.yaml).

DevContainer Features: Lockfile Integrity​

DevContainer features declared in .devcontainer/devcontainer.json are pinned through a lockfile (devcontainer-lock.json) that records the exact version, OCI digest, and SHA-256 integrity hash for each feature. This follows the devcontainer lockfile spec, modeled after package-lock.json.

What Is Validated​

The devcontainer-lockfile-check.yml workflow enforces three checks during PR validation:

CheckFailure Condition
Lockfile existencedevcontainer-lock.json is absent from the repository
SHA-256 integrityA feature entry is missing resolved or integrity with sha256: prefix
Feature coverageA feature in devcontainer.json has no corresponding lockfile entry

Lockfile Format​

Each feature entry records the resolved OCI reference and integrity hash:

{
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "1.7.1",
"resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46...",
"integrity": "sha256:8c0de46..."
}
}
}

Regenerating the Lockfile​

Rebuild the dev container to regenerate the lockfile. In VS Code, use the Dev Containers: Rebuild Container command. Commit the updated devcontainer-lock.json alongside any changes to devcontainer.json.

pip: Exact-Version Staleness Validation​

The == exact-version convention applies to requirements*.txt and Pipfile-style dependency declarations. The pip scanner validates packages that are already pinned with the == operator, checking each pinned version for staleness against PyPI. It does not flag range specifiers (>=, ~=, <) as violations: its regex only matches package==version references, so range pins are simply not inspected. The scanner excludes virtual environment directories (.venv, venv, .tox, .nox, __pypackages__) to avoid false positives from installed package metadata.

# Validated for staleness (exact pin in requirements.txt / Pipfile)
requests==2.31.0
flask==3.0.0

# Not flagged (range pins are not matched by the scanner)
requests>=2.31.0
flask~=3.0

Lockfile-Backed Pinning for uv Skills​

Python skills managed with uv and pyproject.toml achieve exact pinning through uv.lock rather than == constraints in pyproject.toml. The uv.lock file records the exact resolved version and a per-package integrity hash for every dependency, so a range pin in pyproject.toml is equivalent in supply-chain terms to an == pin in requirements.txt. Range pins in pyproject.toml are therefore acceptable for uv-managed skills: prefer expressing compatible-version ranges in pyproject.toml and committing uv.lock to lock the resolved versions, syncing with uv sync --locked to verify against the lock.

NOTE

Test-DependencyPinning.ps1 does not flag range pins in pyproject.toml. Supply-chain security for uv-managed skills is enforced through uv.lock integrity hashes rather than == constraints, so no scanner violation is raised for ranged pyproject.toml dependencies.

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
  • .github/actions/**/*.yml and .github/actions/**/*.yaml (composite actions)

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
  • Security Model: Supply chain threats S-1, S-2, SC-1, SC-4, and SC-6
  • Branch Protection: The main ruleset, its required status checks, and how CI gates merges

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