--- name: supply-chain-security description: Software supply chain security guidance covering SBOM generation, SLSA framework, dependency scanning, SCA tools, and protection against supply chain attacks like dependency confusion and typosquatting. allowed-tools: Read, Glob, Grep, Task --- # Supply Chain Security Comprehensive guidance for securing the software supply chain, including dependency management, SBOM generation, vulnerability scanning, and protection against supply chain attacks. ## When to Use This Skill - Generating Software Bill of Materials (SBOM) - Implementing SLSA framework compliance - Setting up dependency vulnerability scanning - Protecting against dependency confusion attacks - Configuring lock files and integrity verification - Implementing code signing with Sigstore - Verifying software provenance - Evaluating project security with OpenSSF Scorecard ## Quick Reference ### Supply Chain Attack Types | Attack Type | Description | Prevention | |-------------|-------------|------------| | **Dependency Confusion** | Attacker publishes malicious package with internal package name | Namespace scoping, private registries | | **Typosquatting** | Malicious packages with similar names (`lodash` vs `1odash`) | Lockfiles, careful review, tools | | **Compromised Maintainer** | Legitimate package hijacked | Pin versions, verify signatures | | **Build System Attack** | CI/CD pipeline compromised | SLSA compliance, hermetic builds | | **Malicious Dependency** | New dependency contains malware | SCA scanning, SBOM review | ### SLSA Levels Quick Reference | Level | Requirements | Protection | |-------|--------------|------------| | **SLSA 1** | Documentation of build process | Basic transparency | | **SLSA 2** | Authenticated provenance, hosted build | Tampering after build | | **SLSA 3** | Hardened build platform, non-falsifiable provenance | Tampering during build | | **SLSA 4** | Two-person review, hermetic builds | Insider threats | ### Essential Tools by Ecosystem | Ecosystem | Vulnerability Scanning | Lock File | SBOM Generation | |-----------|----------------------|-----------|-----------------| | **npm/Node.js** | `npm audit`, Snyk | `package-lock.json` | `@cyclonedx/cyclonedx-npm` | | **Python** | `pip-audit`, Safety | `requirements.txt` + hashes, `poetry.lock` | `cyclonedx-python` | | **Go** | `govulncheck`, Snyk | `go.sum` | `cyclonedx-gomod` | | **.NET** | `dotnet list package --vulnerable` | `packages.lock.json` | `CycloneDX` NuGet | | **Java/Maven** | OWASP Dependency-Check | `pom.xml` with versions | `cyclonedx-maven-plugin` | | **Rust** | `cargo audit` | `Cargo.lock` | `cargo-cyclonedx` | ## SBOM (Software Bill of Materials) ### SBOM Formats | Format | Standard | Best For | |--------|----------|----------| | **CycloneDX** | OASIS | Security-focused, VEX support | | **SPDX** | Linux Foundation | License compliance, legal | | **SWID** | ISO/IEC 19770-2 | Software asset management | ### CycloneDX SBOM Generation **Node.js:** ```bash # Install CycloneDX CLI npm install -g @cyclonedx/cyclonedx-npm # Generate SBOM cyclonedx-npm --output-file sbom.json cyclonedx-npm --output-file sbom.xml --output-format xml ``` **Python:** ```bash # Install CycloneDX pip install cyclonedx-bom # Generate from requirements.txt cyclonedx-py requirements -i requirements.txt -o sbom.json --format json # Generate from Poetry cyclonedx-py poetry -o sbom.json --format json # Generate from pip environment cyclonedx-py environment -o sbom.json ``` **.NET:** ```bash # Install CycloneDX tool dotnet tool install --global CycloneDX # Generate SBOM dotnet CycloneDX myproject.csproj -o sbom.json -j ``` **Go:** ```bash # Install cyclonedx-gomod go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest # Generate SBOM cyclonedx-gomod mod -json -output sbom.json ``` ### SBOM in CI/CD ```yaml # GitHub Actions - Generate and upload SBOM name: Generate SBOM on: release: types: [published] jobs: sbom: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Generate SBOM uses: CycloneDX/gh-node-module-generatebom@v1 with: output: sbom.json - name: Upload SBOM to release uses: actions/upload-release-asset@v1 with: upload_url: ${{ github.event.release.upload_url }} asset_path: sbom.json asset_name: sbom.json asset_content_type: application/json - name: Submit to Dependency Track run: | curl -X POST \ -H "X-Api-Key: ${{ secrets.DTRACK_API_KEY }}" \ -H "Content-Type: multipart/form-data" \ -F "project=${{ github.repository }}" \ -F "bom=@sbom.json" \ "${{ secrets.DTRACK_URL }}/api/v1/bom" ``` ## Vulnerability Scanning ### npm/Node.js ```bash # Built-in audit npm audit npm audit --json > audit-results.json npm audit fix # Auto-fix where possible # Check for outdated packages npm outdated # Use better-npm-audit for CI npx better-npm-audit audit --level moderate ``` ### Python ```bash # pip-audit (recommended) pip install pip-audit pip-audit pip-audit --fix # Auto-fix pip-audit -r requirements.txt pip-audit --format json > audit.json # Safety (alternative) pip install safety safety check safety check -r requirements.txt ``` ### .NET ```bash # Built-in vulnerability check dotnet list package --vulnerable dotnet list package --vulnerable --include-transitive # Output as JSON for CI dotnet list package --vulnerable --format json > vulnerabilities.json ``` ### Go ```bash # govulncheck (official Go tool) go install golang.org/x/vuln/cmd/govulncheck@latest govulncheck ./... govulncheck -json ./... > vuln.json ``` ### Rust ```bash # cargo-audit cargo install cargo-audit cargo audit cargo audit --json > audit.json cargo audit fix # Auto-fix (with cargo-audit-fix) ``` ## Lock Files and Integrity ### Lock File Best Practices ```csharp using System.Security.Cryptography; using System.Text.Json; using System.Text.Json.Serialization; /// /// Lock file verification utilities for supply chain security. /// public static class LockFileVerification { /// /// Verify npm package-lock.json integrity hashes. /// public static Dictionary VerifyNpmIntegrity(string packageLockPath) { var json = File.ReadAllText(packageLockPath); var lockData = JsonSerializer.Deserialize(json)!; var results = new Dictionary(); foreach (var (name, info) in lockData.Packages ?? new()) { if (string.IsNullOrEmpty(name)) continue; // Root package if (!string.IsNullOrEmpty(info.Integrity)) { var parts = info.Integrity.Split('-', 2); results[name] = new PackageIntegrityResult( HasIntegrity: true, Algorithm: parts[0]); } else { results[name] = new PackageIntegrityResult(HasIntegrity: false, Algorithm: null); } } return results; } /// /// Verify NuGet packages.lock.json integrity. /// public static Dictionary VerifyNuGetLockFile(string lockFilePath) { var json = File.ReadAllText(lockFilePath); var lockData = JsonSerializer.Deserialize(json)!; var results = new Dictionary(); foreach (var (framework, dependencies) in lockData.Dependencies ?? new()) { foreach (var (packageName, info) in dependencies) { var key = $"{packageName}@{info.Resolved}"; results[key] = new PackageIntegrityResult( HasIntegrity: !string.IsNullOrEmpty(info.ContentHash), Algorithm: !string.IsNullOrEmpty(info.ContentHash) ? "SHA512" : null); } } return results; } } public sealed record PackageIntegrityResult(bool HasIntegrity, string? Algorithm); public sealed record NpmPackageLock( [property: JsonPropertyName("packages")] Dictionary? Packages); public sealed record NpmPackageInfo( [property: JsonPropertyName("integrity")] string? Integrity); public sealed record NuGetPackagesLock( [property: JsonPropertyName("dependencies")] Dictionary>? Dependencies); public sealed record NuGetDependencyInfo( [property: JsonPropertyName("resolved")] string? Resolved, [property: JsonPropertyName("contentHash")] string? ContentHash); ``` ### pip with Hash Verification ```text # requirements.txt with hashes (most secure) requests==2.31.0 \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 certifi==2024.2.2 \ --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 \ --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8 ``` ### Generate Hashes Automatically ```bash # pip-tools for hash generation pip install pip-tools # Generate requirements with hashes pip-compile --generate-hashes requirements.in -o requirements.txt # Poetry with hash export poetry export --format requirements.txt --with-hashes > requirements.txt ``` ## Dependency Confusion Prevention ### Private Registry Configuration **npm (.npmrc):** ```ini # Scope packages to private registry @mycompany:registry=https://npm.mycompany.com/ //npm.mycompany.com/:_authToken=${NPM_TOKEN} # Always use exact versions save-exact=true ``` **Python (pip.conf):** ```ini [global] index-url = https://pypi.mycompany.com/simple/ extra-index-url = https://pypi.org/simple/ trusted-host = pypi.mycompany.com [install] # Prefer private packages prefer-binary = true ``` **Preventive Measures:** ```csharp using System.Net.Http.Json; using System.Text.Json.Serialization; /// /// Dependency confusion detection and prevention utilities. /// public sealed class DependencyConfusionChecker(HttpClient httpClient) { /// /// Check if internal NuGet package names exist on nuget.org. /// public async Task> CheckNuGetConfusionAsync( IEnumerable internalPackages, CancellationToken cancellationToken = default) { var results = new Dictionary(); foreach (var package in internalPackages) { try { var response = await httpClient.GetAsync( $"https://api.nuget.org/v3/registration5-semver1/{package.ToLowerInvariant()}/index.json", cancellationToken); if (response.IsSuccessStatusCode) { var registration = await response.Content.ReadFromJsonAsync( cancellationToken: cancellationToken); var latestVersion = registration?.Items?.LastOrDefault()?.Upper; results[package] = new ConfusionCheckResult( ExistsPublicly: true, PublicVersion: latestVersion, Risk: ConfusionRisk.High, Recommendation: "Register placeholder on nuget.org or use package prefix reservation"); } else if (response.StatusCode == System.Net.HttpStatusCode.NotFound) { results[package] = new ConfusionCheckResult( ExistsPublicly: false, PublicVersion: null, Risk: ConfusionRisk.Low, Recommendation: "Consider registering placeholder package"); } } catch (Exception ex) { results[package] = new ConfusionCheckResult( ExistsPublicly: false, PublicVersion: null, Risk: ConfusionRisk.Unknown, Recommendation: $"Check failed: {ex.Message}"); } } return results; } /// /// Generate placeholder .csproj for NuGet package reservation. /// public static string GeneratePlaceholderProject( string packageId, string description = "Internal package - not for public use") { return $""" netstandard2.0 {packageId} 0.0.1 {description} Security Team placeholder;internal;reserved false false """; } } public sealed record ConfusionCheckResult( bool ExistsPublicly, string? PublicVersion, ConfusionRisk Risk, string Recommendation); public enum ConfusionRisk { Low, Medium, High, Unknown } public sealed record NuGetRegistration( [property: JsonPropertyName("items")] List? Items); public sealed record NuGetCatalogPage( [property: JsonPropertyName("upper")] string? Upper); ``` ## Code Signing with Sigstore ### Sigstore Overview Sigstore provides keyless signing using OIDC identity: ```bash # Install cosign # macOS brew install cosign # Linux curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" chmod +x cosign-linux-amd64 sudo mv cosign-linux-amd64 /usr/local/bin/cosign ``` ### Sign Container Images ```bash # Sign with keyless (OIDC) cosign sign ghcr.io/myorg/myimage:v1.0.0 # Sign with key cosign generate-key-pair cosign sign --key cosign.key ghcr.io/myorg/myimage:v1.0.0 # Verify signature cosign verify ghcr.io/myorg/myimage:v1.0.0 \ --certificate-identity=ci@myorg.com \ --certificate-oidc-issuer=https://github.com/login/oauth ``` ### Sign Python Packages ```bash # Install sigstore pip install sigstore # Sign a package python -m sigstore sign dist/mypackage-1.0.0.tar.gz # Verify signature python -m sigstore verify identity \ --cert-identity ci@myorg.com \ --cert-oidc-issuer https://github.com/login/oauth \ dist/mypackage-1.0.0.tar.gz ``` ### Sign npm Packages ```bash # npm provenance (built-in since npm 9.5.0) npm publish --provenance # Verify provenance npm audit signatures ``` ## OpenSSF Scorecard ### Running Scorecard ```bash # Install scorecard # macOS brew install scorecard # Run on GitHub repo scorecard --repo=github.com/myorg/myproject # Run with specific checks scorecard --repo=github.com/myorg/myproject \ --checks=Vulnerabilities,Dependency-Update-Tool,Pinned-Dependencies # Output as JSON scorecard --repo=github.com/myorg/myproject --format=json > scorecard.json ``` ### GitHub Action for Scorecard ```yaml name: Scorecard Analysis on: push: branches: [main] schedule: - cron: '0 6 * * 1' # Weekly on Monday permissions: security-events: write id-token: write contents: read actions: read jobs: analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - name: Run Scorecard uses: ossf/scorecard-action@v2 with: results_file: results.sarif results_format: sarif publish_results: true - name: Upload to Security tab uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif ``` ### Scorecard Checks Explained | Check | What It Measures | How to Improve | |-------|------------------|----------------| | **Vulnerabilities** | Known vulnerabilities in dependencies | Enable Dependabot, fix vulns | | **Dependency-Update-Tool** | Automated dependency updates | Enable Dependabot/Renovate | | **Pinned-Dependencies** | CI uses pinned dependencies | Pin action versions, use hashes | | **Token-Permissions** | Minimal CI token permissions | Use least-privilege tokens | | **Branch-Protection** | Main branch protection | Require reviews, status checks | | **Code-Review** | PRs require review | Enable required reviews | | **Signed-Releases** | Releases are signed | Use Sigstore/GPG signing | | **Binary-Artifacts** | Repo contains binaries | Remove binaries, use releases | ## Security Checklist ### Pre-Release Checklist - [ ] Generate SBOM for release - [ ] Run vulnerability scan (npm audit, pip-audit, etc.) - [ ] Verify all dependencies have lock file entries - [ ] Check for dependency confusion risks - [ ] Sign release artifacts with Sigstore - [ ] Run OpenSSF Scorecard - [ ] Verify provenance generation is enabled ### Repository Security - [ ] Enable Dependabot or Renovate - [ ] Configure branch protection rules - [ ] Pin CI/CD action versions with hashes - [ ] Use minimal token permissions - [ ] Enable secret scanning - [ ] Configure code owners for security files ### Dependency Management - [ ] Use lock files in all projects - [ ] Enable integrity hash verification - [ ] Configure private registry for internal packages - [ ] Register placeholder packages on public registries - [ ] Review new dependencies before adding - [ ] Monitor for typosquatting attempts ## References - **SBOM Generation**: See `references/sbom-generation.md` for advanced SBOM workflows - **SLSA Framework**: See `references/slsa-levels.md` for implementation guidance - **Attack Prevention**: See `references/dependency-attacks.md` for detailed attack patterns ## Related Skills - `secure-coding` - Secure development practices - `devsecops-practices` - CI/CD security integration - `container-security` - Container image signing and scanning --- **Last Updated:** 2025-12-26