供应链安全
了解 Depsilo v0.11.0 的最小发布年龄、恶意包封锁、篡改检测、冻结快照与审计完整性。
Depsilo 把供应链策略放在包管理器与上游之间。v0.11.0 提供这些相互独立的控制:
| 控制 | 新安装默认值 | 结果 | 主要边界 |
|---|---|---|---|
| 最低发布年龄 | 关闭 | 年轻版本返回 451 QUARANTINED |
时间戳来源按生态绑定;Go 与 APT 没有可用来源 |
| 已知恶意包封锁 | 开启,每 6 小时同步 | 匹配版本返回 451 MALICIOUS_BLOCKED |
覆盖 8 个生态的 OSV MAL-* 记录 |
| 篡改检测 | 开启 | 保留首见哈希并发出严重告警;不返回 451 | 被动检测,首见信任 |
| 冻结 / 黄金快照 | 关闭 | 快照模式下未批准的版本返回 451 SNAPSHOT_BLOCKED |
只覆盖缓存中已经存在的制品 |
| 审计完整性与 SIEM 转发 | 审计链开启;转发需配置 | 审计行哈希链接、可离线校验,并可转发到收集器 | 整库重写只有配合外部锚点才能发现 |
这些控制彼此独立,也不会改变缓存、上游选择或代理行为。
最低发布年龄
链接到“最低发布年龄”小节最小发布年龄在请求路径上拒绝“太新”的版本。Depsilo 为每个生态绑定“制品身份 + 时间戳 + 将要提供该制品的上游”,只有能证明时间戳来源的生态才接受正阈值:
| 生态 | 时间戳来源 | 启用条件 |
|---|---|---|
| npm | 包文档的 time[version],随认证过的 tarball 令牌传递 |
直接可用 |
| PyPI | 同一上游 PEP 691 JSON simple index 的 upload-time |
上游需提供 JSON;仅 HTML 的上游会 fail closed |
| Composer | p2 元数据自身的 time 字段 |
直接可用;客户端可能在 451 之后回退原始 dist URL |
| NuGet | v3 registration index 的 published |
直接可用 |
| Cargo | sparse index 的 pubtime 与 cksum |
直接可用 |
| RubyGems | compact index 的 created_at 与校验和 |
直接可用 |
| Conda、CRAN、Maven、Alpine、Helm | 制品响应头的 Last-Modified(近似) |
需在 supply_chain.approximate_sources 中确认 |
| Docker | 本实例第一次看到该 digest 的时间(首次观测,不是发布时间) | 需在 supply_chain.observation_sources 中确认 |
| Go、APT | 没有可用的发布时间来源 | 阈值保持 0 |
[supply_chain]min_release_age_enabled = truemode = "block" # 或 "warn":只记录并放行,用于上线前的观察期fail_closed = trueapproximate_sources = ["conda", "cran", "maven", "alpine", "helm"]observation_sources = ["docker"]# allow = ["npm:internal-*"] # 按生态前缀绕过隔离,绕过同样写审计
[supply_chain.min_release_age]default = "0"pypi = "3d"npm = "7d"cargo = "3d"行为与边界:
- 决定发生在缓存查找之前:已经缓存过的版本也不能绕过新策略。
- 时间戳缺失时 fail closed;给未绑定或未确认的生态配置正阈值会被启动拒绝,而不是 静默降级成“不检查”。
- 管理员可以在 Admin 中批准单个
(生态, 包, 版本);批准、撤销与 allow 绕过都会写审计。 - Docker 的年龄从本实例第一次拉取该 digest 起算,发布者无法伪造;未见过的 digest
在阈值内保持隔离,registry 不返回
Docker-Content-Digest时拒绝提供。 - 近似生态信任服务上游返回的
Last-Modified;Composer 客户端可能绕过 451 回退到 原始 dist URL,因此这一条只能尽力而为。
已知恶意包封锁
链接到“已知恶意包封锁”小节恶意包封锁默认开启:
[supply_chain.blocklist]enabled = truesync_interval = "6h"# mirror_url = "https://osv-vulnerabilities.storage.googleapis.com"# proxy = "http://127.0.0.1:7890"v0.11.0 从 OSV malicious-packages 数据集中同步 npm、PyPI、Cargo、RubyGems、Composer、
NuGet、Go 和 Maven 的 MAL-* 记录。它导入明确版本列表和“所有版本”通告;没有明确
版本列表的有界范围会被跳过,因为 Depsilo 不对这种范围作推导。
当数据集覆盖某个生态时,身份无法证明的制品会被拒绝而不是放行:PyPI 的旧式归档
(.zip、.egg、.tar.bz2 等)通过声明它的 simple index 解析身份,RubyGems 通过
compact index 解析精确的 gem 身份,平台 gem 继承基础版本的记录。
匹配发生在发布年龄和 allow 规则之前。已在缓存中的匹配制品也不会通过该门继续提供, 但字节会留在存储中,直到 LRU 或管理员清理。确认误报时,管理员可创建最长 24 小时的 审计覆盖;覆盖不能原地延期,继续使用必须重新作出有记录的决定。
同步失败采用降级策略:
- 本地已有数据时,继续使用最后一次成功的数据集。
- 本地没有任何数据时,不会阻断任何版本。
- 同步错误本身不会让整个代理停止提供包。
制品篡改检测
链接到“制品篡改检测”小节[supply_chain.tamper_detection]enabled = true对于被视为不可变的制品,Depsilo 在第一次流式获取时记录 SHA-256。后台自然刷新若在
同一版本下收到不同字节,它会拒绝把替换内容写入缓存,继续保留首见缓存副本,并记录
严重 tamper_detected 事件;配置了 webhook 时也可发送通知。如果首见副本已经被 LRU
淘汰,之后的 cache miss 会先存储并返回重新获取的字节,再与保留的哈希基线比较并告警;
此时旧副本已经无法恢复。
必须同时理解以下限制:
- 检测是被动的,只在正常后台刷新或淘汰后的再次获取中比较,不主动探测上游。
- 它是告警机制,不会返回 451,也不是通用的阻断策略。
- “首见”是本地基线,不是签名或透明日志证明;第一次获取已经被污染时,哈希也会记录 被污染的字节。
- 如果 LRU 已删除原始缓存副本,不一致的新字节在验证前已经被存储并可能提供给客户端; Depsilo 仍会使用旧哈希告警,但无法恢复旧字节。
冻结 / 黄金快照
链接到“冻结 / 黄金快照”小节把当前缓存中的制品集合提升为一个命名、哈希固定的快照,之后可以让实例只提供该快照 内的版本:
- 在 Admin 的缓存页或 Admin API 中把缓存提升为命名快照,并导出 / 导入
depsilo/snapshot/v1清单。 - 打开快照模式后,恶意包门之后任何不在快照内的制品都会以 451
SNAPSHOT_BLOCKED拒绝,成员查询 fail closed。 - 快照固定篡改检测记录的 SHA-256;导入清单会为缺失的基线补种,所以与清单不一致的 首次获取仍然会产生篡改告警。
审计完整性与 SIEM 转发
链接到“审计完整性与 SIEM 转发”小节审计日志自 schema v8 起按行哈希链接(prev_hash / hash,UTC 归一化时间戳):
- 审计页可以随时重新校验;
GET /api/v1/admin/audit/integrity返回完整报告;depsilo audit verify [--json]可以离线对 SQLite 文件重算整条链。 - 早期版本写入的行会作为“链前前缀”单独报告,不会被当作已被覆盖。
- 编辑、删除或重排链上的行会报告第一个断裂的 id。
链本身不能发现“整库重写”:攻击者可以重算整条链。把链头检查点写到数据库之外才能
补上这一点——[audit] checkpoint_file 以 NDJSON 追加写入并 fsync,校验时逐条比对;
[audit] checkpoint_url 还会把同一行 POST 到 WORM 网关或日志平台,失败后 60 秒重试,
远程投递状态(remote_head_id、remote_last_success_at、remote_last_error)在
完整性 API 与审计页可见。
SIEM 转发把同一条审计流以 NDJSON 或 Splunk HEC 信封投递到外部收集器:每个 exporter
维护自己的游标,投递至少一次;收集器故障时游标停住而不是丢记录,重启后从最后一个
成功批次继续,失败按 10 秒到 5 分钟退避。可以按动作和结果(blocked、miss 等)
过滤事件;新 exporter 从当前审计头开始。Admin API 与审计页覆盖增删、启停、延迟和
最近错误状态,以及一次走真实路径的测试投递。
合规导出(Pro)
链接到“合规导出(Pro)”小节多项目工作区里的运行时 SBOM 导出提供 CRA 模式与技术文件预设:携带 purl、篡改检测
记录的 SHA-256、supplier / license 字段和产品到组件的依赖关系。supplier 与 license
来自操作者声明的 [compliance.components] 注解,未声明的组件会明确导出为 unknown;
覆盖缺口会写进清单,而不是编造数值。配置 Ed25519 密钥后,sign=true 会返回对响应体
的分离签名。
强制边界
链接到“强制边界”小节常见的绕过路径包括:
- pip 的额外直连 index;不要让
--extra-index-url指向不受控上游。 - Go 的
GOPROXY使用|direct等会在错误后直连的 fallback。 - Composer 在镜像 dist 被拒绝时可能回退到原始 dist URL。要硬性阻断,必须限制客户端 对原始下载地址的直接访问。
Depsilo 可转发生态自身的校验和或签名,但不会把未签名制品变成有来源证明的制品,也不 替代上游签名、构建来源证明、锁文件和 SBOM。最小发布年龄按生态绑定时间戳来源;恶意 列表阻断已知项目;篡改检测识别同版本字节变化;快照限制可提供的版本范围。这些控制 都不是对包安全性的完整证明。
审计与告警
链接到“审计与告警”小节隔离阻断、allow 绕过、批准与撤销、恶意包阻断与临时覆盖、篡改事件、快照阻断都会写入 数据库中的审计事件。可在 Admin 中按生态、动作和包查看,并按上面的方式校验完整性并 转发到 SIEM。配置 webhook 后,隔离和恶意包阻断可立即通知,篡改不匹配使用严重级别。
上线前至少验证一次:
- 为一个来源已绑定的生态(例如 npm)配置正阈值,确认年轻版本返回 451
QUARANTINED并留下事件。 - 需要近似或首次观测生态时,确认启动被接受,且能力摘要显示
approximate/observed或对应的安全暂停状态,而不是全绿。 - 用已审核的 Package Rule 或恶意数据集验证 451 与审计记录。
- 打开快照模式后确认快照外的版本返回 451
SNAPSHOT_BLOCKED。 - 运行
depsilo audit verify,并把 checkpoint 写到数据库所在的故障域之外。 - 客户端没有可绕过代理的备用 registry 或直连出口,审计记录和 webhook 到达预期的 值班通道。