软件供应链安全Skill supply-chain-security

该技能提供软件供应链安全的综合指导,包括生成软件物料清单(SBOM)、实施SLSA框架、进行依赖漏洞扫描、使用SCA工具以及防护依赖混淆和typosquatting等供应链攻击。关键词:软件供应链安全,SBOM,SLSA,依赖扫描,供应链攻击。

安全运维 0 次安装 0 次浏览 更新于 3/11/2026

名称: 软件供应链安全 描述: 软件供应链安全指南,涵盖SBOM生成、SLSA框架、依赖扫描、SCA工具,以及防护依赖混淆和typosquatting等供应链攻击。 允许工具: 读取, 全局查找, 搜索, 任务

软件供应链安全

软件供应链安全的全面指南,包括依赖管理、SBOM生成、漏洞扫描和供应链攻击防护。

何时使用此技能

  • 生成软件物料清单(SBOM)
  • 实施SLSA框架合规
  • 设置依赖漏洞扫描
  • 防护依赖混淆攻击
  • 配置锁文件和完整性验证
  • 使用Sigstore进行代码签名
  • 验证软件来源
  • 使用OpenSSF Scorecard评估项目安全性

快速参考

供应链攻击类型

攻击类型 描述 预防措施
依赖混淆 攻击者发布恶意包,使用内部包名 命名空间范围、私有注册表
Typosquatting 恶意包使用相似名称(如lodash vs 1odash 锁文件、仔细审查、工具
维护者被攻陷 合法包被劫持 固定版本、验证签名
构建系统攻击 CI/CD管道被攻陷 SLSA合规、密封构建
恶意依赖 新依赖包含恶意软件 SCA扫描、SBOM审查

SLSA级别快速参考

级别 要求 防护
SLSA 1 构建过程文档 基本透明度
SLSA 2 认证来源、托管构建 构建后篡改
SLSA 3 强化构建平台、不可伪造来源 构建期间篡改
SLSA 4 两人审查、密封构建 内部威胁

按生态系统的必备工具

生态系统 漏洞扫描 锁文件 SBOM生成
npm/Node.js npm audit, Snyk package-lock.json @cyclonedx/cyclonedx-npm
Python pip-audit, Safety requirements.txt + 哈希, 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 带版本 cyclonedx-maven-plugin
Rust cargo audit Cargo.lock cargo-cyclonedx

SBOM(软件物料清单)

SBOM格式

格式 标准 最佳用途
CycloneDX OASIS 安全重点,支持VEX
SPDX Linux Foundation 许可证合规,法律
SWID ISO/IEC 19770-2 软件资产管理

CycloneDX SBOM生成

Node.js:

# 安装CycloneDX CLI
npm install -g @cyclonedx/cyclonedx-npm

# 生成SBOM
cyclonedx-npm --output-file sbom.json
cyclonedx-npm --output-file sbom.xml --output-format xml

Python:

# 安装CycloneDX
pip install cyclonedx-bom

# 从requirements.txt生成
cyclonedx-py requirements -i requirements.txt -o sbom.json --format json

# 从Poetry生成
cyclonedx-py poetry -o sbom.json --format json

# 从pip环境生成
cyclonedx-py environment -o sbom.json

.NET:

# 安装CycloneDX工具
dotnet tool install --global CycloneDX

# 生成SBOM
dotnet CycloneDX myproject.csproj -o sbom.json -j

Go:

# 安装cyclonedx-gomod
go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest

# 生成SBOM
cyclonedx-gomod mod -json -output sbom.json

CI/CD中的SBOM

# GitHub Actions - 生成和上传SBOM
名称: 生成SBOM
触发:
  发布:
    类型: [已发布]

任务:
  sbom:
    运行环境: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v5

      - 名称: 生成SBOM
        使用: CycloneDX/gh-node-module-generatebom@v1
        参数:
          输出: sbom.json

      - 名称: 上传SBOM到发布
        使用: actions/upload-release-asset@v1
        参数:
          upload_url: ${{ github.event.release.upload_url }}
          asset_path: sbom.json
          asset_name: sbom.json
          asset_content_type: application/json

      - 名称: 提交到Dependency Track
        运行: |
          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"

漏洞扫描

npm/Node.js

# 内置审计
npm audit
npm audit --json > audit-results.json
npm audit fix  # 自动修复可能之处

# 检查过时包
npm outdated

# 在CI中使用better-npm-audit
npx better-npm-audit audit --level moderate

Python

# pip-audit(推荐)
pip install pip-audit
pip-audit
pip-audit --fix  # 自动修复
pip-audit -r requirements.txt
pip-audit --format json > audit.json

# Safety(替代)
pip install safety
safety check
safety check -r requirements.txt

.NET

# 内置漏洞检查
dotnet list package --vulnerable
dotnet list package --vulnerable --include-transitive

# 输出为JSON用于CI
dotnet list package --vulnerable --format json > vulnerabilities.json

Go

# govulncheck(官方Go工具)
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
govulncheck -json ./... > vuln.json

Rust

# cargo-audit
cargo install cargo-audit
cargo audit
cargo audit --json > audit.json
cargo audit fix  # 自动修复(使用cargo-audit-fix)

锁文件和完整性

锁文件最佳实践

using System.Security.Cryptography;
using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// 锁文件验证工具,用于供应链安全。
/// </summary>
public static class LockFileVerification
{
    /// <summary>
    /// 验证npm package-lock.json完整性哈希。
    /// </summary>
    public static Dictionary<string, PackageIntegrityResult> VerifyNpmIntegrity(string packageLockPath)
    {
        var json = File.ReadAllText(packageLockPath);
        var lockData = JsonSerializer.Deserialize<NpmPackageLock>(json)!;

        var results = new Dictionary<string, PackageIntegrityResult>();

        foreach (var (name, info) in lockData.Packages ?? new())
        {
            if (string.IsNullOrEmpty(name)) continue;  // 根包

            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;
    }

    /// <summary>
    /// 验证NuGet packages.lock.json完整性。
    /// </summary>
    public static Dictionary<string, PackageIntegrityResult> VerifyNuGetLockFile(string lockFilePath)
    {
        var json = File.ReadAllText(lockFilePath);
        var lockData = JsonSerializer.Deserialize<NuGetPackagesLock>(json)!;

        var results = new Dictionary<string, PackageIntegrityResult>();

        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<string, NpmPackageInfo>? Packages);

public sealed record NpmPackageInfo(
    [property: JsonPropertyName("integrity")] string? Integrity);

public sealed record NuGetPackagesLock(
    [property: JsonPropertyName("dependencies")] Dictionary<string, Dictionary<string, NuGetDependencyInfo>>? Dependencies);

public sealed record NuGetDependencyInfo(
    [property: JsonPropertyName("resolved")] string? Resolved,
    [property: JsonPropertyName("contentHash")] string? ContentHash);

pip哈希验证

# requirements.txt带哈希(最安全)
requests==2.31.0 \
    --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
    --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1

certifi==2024.2.2 \
    --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1 \
    --hash=sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8

自动生成哈希

# pip-tools用于哈希生成
pip install pip-tools

# 生成带哈希的需求文件
pip-compile --generate-hashes requirements.in -o requirements.txt

# Poetry带哈希导出
poetry export --format requirements.txt --with-hashes > requirements.txt

依赖混淆防护

私有注册表配置

npm (.npmrc):

# 将包范围限定到私有注册表
@mycompany:registry=https://npm.mycompany.com/
//npm.mycompany.com/:_authToken=${NPM_TOKEN}

# 始终使用精确版本
save-exact=true

Python (pip.conf):

[global]
index-url = https://pypi.mycompany.com/simple/
extra-index-url = https://pypi.org/simple/
trusted-host = pypi.mycompany.com

[install]
# 优先私有包
prefer-binary = true

预防措施:

using System.Net.Http.Json;
using System.Text.Json.Serialization;

/// <summary>
/// 依赖混淆检测和预防工具。
/// </summary>
public sealed class DependencyConfusionChecker(HttpClient httpClient)
{
    /// <summary>
    /// 检查内部NuGet包名是否存在于nuget.org上。
    /// </summary>
    public async Task<Dictionary<string, ConfusionCheckResult>> CheckNuGetConfusionAsync(
        IEnumerable<string> internalPackages,
        CancellationToken cancellationToken = default)
    {
        var results = new Dictionary<string, ConfusionCheckResult>();

        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<NuGetRegistration>(
                        cancellationToken: cancellationToken);

                    var latestVersion = registration?.Items?.LastOrDefault()?.Upper;

                    results[package] = new ConfusionCheckResult(
                        ExistsPublicly: true,
                        PublicVersion: latestVersion,
                        Risk: ConfusionRisk.High,
                        Recommendation: "在nuget.org上注册占位包或使用包前缀保留");
                }
                else if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    results[package] = new ConfusionCheckResult(
                        ExistsPublicly: false,
                        PublicVersion: null,
                        Risk: ConfusionRisk.Low,
                        Recommendation: "考虑注册占位包");
                }
            }
            catch (Exception ex)
            {
                results[package] = new ConfusionCheckResult(
                    ExistsPublicly: false,
                    PublicVersion: null,
                    Risk: ConfusionRisk.Unknown,
                    Recommendation: $"检查失败: {ex.Message}");
            }
        }

        return results;
    }

    /// <summary>
    /// 生成用于NuGet包保留的占位.csproj。
    /// </summary>
    public static string GeneratePlaceholderProject(
        string packageId,
        string description = "内部包 - 非公开使用")
    {
        return $"""
            <Project Sdk="Microsoft.NET.Sdk">
              <PropertyGroup>
                <TargetFramework>netstandard2.0</TargetFramework>
                <PackageId>{packageId}</PackageId>
                <Version>0.0.1</Version>
                <Description>{description}</Description>
                <Authors>安全团队</Authors>
                <PackageTags>占位;内部;保留</PackageTags>
                <IncludeSymbols>false</IncludeSymbols>
                <IncludeSource>false</IncludeSource>
              </PropertyGroup>
            </Project>
            """;
    }
}

public sealed record ConfusionCheckResult(
    bool ExistsPublicly,
    string? PublicVersion,
    ConfusionRisk Risk,
    string Recommendation);

public enum ConfusionRisk { 低, 中, 高, 未知 }

public sealed record NuGetRegistration(
    [property: JsonPropertyName("items")] List<NuGetCatalogPage>? Items);

public sealed record NuGetCatalogPage(
    [property: JsonPropertyName("upper")] string? Upper);

使用Sigstore进行代码签名

Sigstore概述

Sigstore使用OIDC身份提供无密钥签名:

# 安装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

签名容器镜像

# 使用无密钥签名(OIDC)
cosign sign ghcr.io/myorg/myimage:v1.0.0

# 使用密钥签名
cosign generate-key-pair
cosign sign --key cosign.key ghcr.io/myorg/myimage:v1.0.0

# 验证签名
cosign verify ghcr.io/myorg/myimage:v1.0.0 \
  --certificate-identity=ci@myorg.com \
  --certificate-oidc-issuer=https://github.com/login/oauth

签名Python包

# 安装sigstore
pip install sigstore

# 签名包
python -m sigstore sign dist/mypackage-1.0.0.tar.gz

# 验证签名
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

签名npm包

# npm来源(自npm 9.5.0内置)
npm publish --provenance

# 验证来源
npm audit signatures

OpenSSF Scorecard

运行Scorecard

# 安装scorecard
# macOS
brew install scorecard

# 运行在GitHub仓库
scorecard --repo=github.com/myorg/myproject

# 运行特定检查
scorecard --repo=github.com/myorg/myproject \
  --checks=漏洞,依赖更新工具,固定依赖

# 输出为JSON
scorecard --repo=github.com/myorg/myproject --format=json > scorecard.json

Scorecard的GitHub Action

名称: Scorecard分析
触发:
  推送:
    分支: [main]
  计划:
    - cron: '0 6 * * 1'  # 每周一

权限:
  security-events: 写
  id-token: 写
  contents: 读
  actions: 读

任务:
  analysis:
    运行环境: ubuntu-latest
    步骤:
      - 使用: actions/checkout@v5

      - 名称: 运行Scorecard
        使用: ossf/scorecard-action@v2
        参数:
          results_file: results.sarif
          results_format: sarif
          publish_results: true

      - 名称: 上传到安全选项卡
        使用: github/codeql-action/upload-sarif@v3
        参数:
          sarif_file: results.sarif

Scorecard检查解释

检查 测量内容 如何改进
漏洞 依赖中的已知漏洞 启用Dependabot,修复漏洞
依赖更新工具 自动化依赖更新 启用Dependabot/Renovate
固定依赖 CI使用固定依赖 固定操作版本,使用哈希
令牌权限 最小CI令牌权限 使用最低权限令牌
分支保护 主分支保护 要求审查,状态检查
代码审查 PR需要审查 启用必要审查
签名发布 发布被签名 使用Sigstore/GPG签名
二进制工件 仓库包含二进制文件 移除二进制,使用发布

安全清单

发布前清单

  • [ ] 为发布生成SBOM
  • [ ] 运行漏洞扫描(npm audit、pip-audit等)
  • [ ] 验证所有依赖有锁文件条目
  • [ ] 检查依赖混淆风险
  • [ ] 使用Sigstore签名发布工件
  • [ ] 运行OpenSSF Scorecard
  • [ ] 验证来源生成已启用

仓库安全

  • [ ] 启用Dependabot或Renovate
  • [ ] 配置分支保护规则
  • [ ] 固定CI/CD操作版本带哈希
  • [ ] 使用最小令牌权限
  • [ ] 启用秘密扫描
  • [ ] 为安全文件配置代码所有者

依赖管理

  • [ ] 在所有项目中使用锁文件
  • [ ] 启用完整性哈希验证
  • [ ] 为内部包配置私有注册表
  • [ ] 在公共注册表上注册占位包
  • [ ] 添加新依赖前审查
  • [ ] 监控typosquatting尝试

参考

  • SBOM生成: 见references/sbom-generation.md获取高级SBOM工作流
  • SLSA框架: 见references/slsa-levels.md获取实施指南
  • 攻击防护: 见references/dependency-attacks.md获取详细攻击模式

相关技能

  • 安全编码 - 安全开发实践
  • DevSecOps实践 - CI/CD安全集成
  • 容器安全 - 容器镜像签名和扫描

最后更新: 2025-12-26