Tmauc/packguard
GitHub: Tmauc/packguard
一个离线的多生态包版本治理工具,通过本地策略引擎与情报比对实现供应链风险检测与合规管控。
Stars: 0 | Forks: 0
# PackGuard
PackGuard 扫描你的依赖清单,查询包注册表(npm、PyPI),
计算每个依赖与 `latest` 的偏离程度,将其与每仓库策略(`.packguard.yml`)进行比对,
从 OSV + GitHub Advisory + 可选扫描器获取建议数据,并将结果存储在本地 SQLite 缓存中,以便离线报告。
详见 [CONTEXT.md](./CONTEXT.md) 了解完整愿景、架构与路线图。
**阶段状态 — 已完成:**
- ✅ **阶段 0 / 1 / 1.5** — MVP CLI:npm + PyPI,SQLite 存储,策略引擎
(偏移 / 固定 / 稳定性 / 最小年龄天数),`init`、`scan`、`report`(表格 /
JSON / SARIF),带版本历史的严格解析器。
- ✅ **阶段 2** — 漏洞情报:OSV 转储 + GHSA Git 摄入,区分方言的匹配器,
`block.cve_severity`,OSV API 实时回退,`audit` 命令。
- ✅ **阶段 2.5** — 恶意软件与拼写劫持:OSV-MAL 采集,顶级 N 拼写劫持
启发式算法,可选 Socket.dev 扫描器,`block.malware`、`block.typosquat`,
`audit --focus`,`report` 中统一的 `Risk` 列。
- ✅ **阶段 4** — 本地仪表板:`packguard-server`(axum + ts-rs),
React 19 + Vite SPA,概览 / 软件包 / 软件包详情(visx 时间线 / 策略 YAML 编辑器与干运行预览),
通过单个 `packguard ui` 二进制文件提供服务,该文件在发布时内嵌 Vite 捆绑包。
- ✅ **阶段 5** — 依赖关系图与污染:传递边
从 `package-lock.json` / `pnpm-lock`(v6/v7 + v9 快照)
/ `poetry.lock` / `uv.lock` 采集,Cytoscape `/graph` 页面聚焦 CVE
模式,可追溯从工作区根到易受攻击软件包的链条,兼容性标签页及
`packguard graph` CLI(ASCII / dot / JSON)。
- ✅ **阶段 7** — 每个项目范围(单体仓库就绪):`GET
/api/workspaces` + `?project=<路径>` 查询参数应用于所有返回列表的端点,
严格后端验证(404 并附带已知工作区列表),` ` 头部下拉框将范围
写入 URL 并在 localStorage 中保留最后选择,每页范围徽章,每个工作区的 `.packguard.yml` 编辑器,以及“被 N 个工作空间使用 ·
兼容性”标签页中的下钻。CLI `report` / `audit` / `graph` 接受 `--project <路径>` 作为标志别名;
所有命令回退到最近一次扫描,并在空参数时输出明确的 stderr 提示。
- ✅ **阶段 8a** — 发布就绪打包:多阶段 Dockerfile
(约 46 MB distroless 镜像包含 UI 嵌入),GitHub Actions CI +
`release.yml` 工作流生成 5 个平台二进制文件 + SHA256SUMS +
可选的 cosign 签名 + 多架构 `ghcr.io` 推送 + Trivy 扫描,
POSIX `install.sh` 一键安装(零信任 SHA256 校验),
[`docs/integrations/`](docs/integrations/README.md)(GitLab CI、GitHub Actions、pre-commit、VSCode)下的四个复制粘贴 CI/IDE 配方,`packguard init
--with-ci ` 生成可直接粘贴的流水线片段,Homebrew 公式模板,以及完整的 [`PUBLISHING.md`](PUBLISHING.md)
运行手册,用于第 8b 阶段的凭据绑定步骤。
## 仪表板
```
# 开发:并排运行服务器 + Vite(UI 热重载)。
packguard ui # starts the API on :5174
pnpm --dir dashboard dev # starts Vite on :5173 (auto-proxies /api/*)
# 发布:单个二进制文件,无需 Node 运行时。
PACKGUARD_SKIP_UI_BUILD=1 cargo build --release -p packguard-cli --features ui-embed
./target/release/packguard ui
```
发布版二进制文件提供仪表板、REST API 并自动打开浏览器。
`--no-open` 禁止自动打开,`--port` / `--host` 覆盖绑定。
`PACKGUARD_SKIP_UI_BUILD=1` 在已预先构建仪表板时跳过 `pnpm build`(CI)。
`ui-embed` 特性为可选,以便调试构建保持快速且无需 PATH 中有 pnpm。
### 页面
| 页面 | URL | 亮点 |
|------|-----|------|
| 概览 | `/` | 健康评分 · 追踪软件包数量 · CVE/供应链甜甜圈 · 前 5 风险 |
| 软件包 | `/packages` | 可筛选、可排序的表格,URL 状态过滤器,分页 |
| 软件包详情 | `/packages/:eco/:name` | 6 标签页:版本 + visx 时间线、漏洞、恶意软件、策略评估、兼容性(被 N 个工作空间使用 · 逐工作空间下钻)、变更日志 |
| 图形 | `/graph` | Cytoscape(dagre + cose-bilkent),URL 驱动过滤器,聚焦 CVE 污染模式 |
| 策略 | `/policies` | 每个工作空间的 CodeMirror YAML 编辑器,对当前策略的干运行预览,原子保存 |
每个返回列表的页面从 `?project=<路径>` 读取活动工作区并将其传递到后端调用。
页眉的 `工作区` 下拉框在不卸载当前路由的情况下写入该参数——
可安全加入书签,可在会话中随时切换,可同时打开两个浏览器标签页查看不同工作区。
每个页面右上方的范围徽章会立即告诉你数字是聚合的还是范围限定的。







**阶段 7 — 工作区范围(两个浏览器标签页,两个工作区,同一个存储):**




### 图形 CLI
```
# 只读的最近扫描树视图(默认为 ASCII)。
packguard graph path/to/repo
# 仅显示特定软件包根目录的子树。
packguard graph path/to/repo --focus npm:react@18.3.1
# 针对某个 CVE 的所有污染链(与仪表盘相同的 BFS + 缓存)。
packguard graph path/to/repo --contaminated-by CVE-2026-4800
# 通过 Graphviz 管道输出。
packguard graph path/to/repo --format dot | dot -Tsvg -o deps.svg
# 机器可读格式(与仪表盘导出的 DTOs 一致)。
packguard graph path/to/repo --format json
```
## 安装
选择适合你机器的通道。每个选项都会安装相同的 `packguard` 二进制文件 —
扫描器、仪表板和 CLI 全部打包在一起。
```
# 选项 1 — curl | sh(验证 SHA256,若 /usr/local/bin 不可写则无需 sudo)
curl -fsSL https://raw.githubusercontent.com/Tmauc/packguard/main/install.sh | sh
# 选项 2 — Docker(约 46 MB,多架构)
docker run --rm -v "$PWD":/workspace ghcr.io/tmauc/packguard:latest scan /workspace
# 选项 3 — Homebrew
brew tap Tmauc/packguard
brew install packguard
# 选项 4 — 从源码构建(包含嵌入式仪表盘)
cargo install --path crates/packguard-cli --features ui-embed
```
该二进制文件名为 `packguard`。使用 `packguard --version` 验证。
## 在 CI 中集成 — 5 分钟入门
目标:在任意仓库中对关键 CVE 实现阻塞式流水线门控,
仅需三次提交。
```
# 1. 生成策略 + 为你的 VCS 预配置的流水线片段。
packguard init --with-ci github # or gitlab / jenkins
# ⇒ 写入 .packguard.yml(保守默认值)
# ⇒ 写入 .packguard/ci/github.yml(可直接粘贴的片段)
# ⇒ 完整配方:docs/integrations/github-actions.md
# 2. 将片段复制到预期的仓库流水线布局中。
mkdir -p .github/workflows && cp .packguard/ci/github.yml .github/workflows/packguard.yml
# 3. 提交并推送。
git add .packguard.yml .github/workflows/packguard.yml \
&& git commit -m "ci: add PackGuard supply-chain gate" \
&& git push
```
在下一个 PR 中,工作流将:
- 安装 PackGuard(快速 — `install.sh` 或 ghcr.io 镜像),
- 缓存 `~/.packguard/`,键为锁文件的哈希,
- 运行 `scan → sync → report --fail-on-violation`,
- 将 SARIF 上传至“安全”标签页。
引入关键 CVE 的 PR 会将检查置为红色并阻止合并
(如果分支保护要求该检查)。仅此而已。调整 `.packguard.yml` 的 YAML 形状以调节门槛。
完整的配方与额外功能(单体仓库矩阵、定时情报刷新、
每个工作空间范围、pre-commit 钩子、VSCode 任务)位于
[`docs/integrations/`](docs/integrations/README.md)。
## 快速启动(本地扫描,无需 CI)
```
# 1. 在仓库中写入保守的 .packguard.yml。
packguard init
# 2. 扫描 — 获取注册表数据、分类并写入存储。
packguard scan
# 3. 刷新供应链情报(OSV + GHSA 转储 + 拼写劫持列表)。
packguard sync
# 4. 审计 — 列出每个 CVE、恶意软件发现和拼写劫持嫌疑。
packguard audit
# 5. 报告 — 按生态系统/工作区分组,显示策略合规性。
packguard report
# 6. 仪表盘 — 上述内容的交互式视图。
packguard ui
```
默认存储位于 `~/.packguard/store.db`。使用全局 `--store <路径>` 覆盖
## 命令
### `packguard init [路径] [--force]`
检测 `路径` 下的受支持生态系统,并写入
`<路径>/.packguard.yml`,采用保守默认模板(`offset: -1`,
`stability: stable`,`min_age_days: 7`,阻断高/严重 CVE + 恶意软件 +
已弃用 + 被撤销,拼写劫持 = 警告)。除非使用 `--force`,否则拒绝覆盖。
### `packguard scan [路径] [--offline] [--force]`
遍历一级生态系统(npm、PyPI),解析清单与锁定文件,查询注册表获取完整版本历史,
并持久化至 SQLite。
清单与锁定文件的 SHA-256 指纹用于门控注册表往返;
与缓存指纹匹配的重新运行将短路。`--offline` 在缓存未填充时干净报错。
### `packguard sync [--skip-osv] [--skip-ghsa] [--ghsa-cache <路径>] [--all]`
刷新漏洞与供应链情报:
- **OSV 转储**(npm 与 PyPI)(`https://osv-vulnerabilities.storage.googleapis.com/{生态}/all.zip`),
条件 GET 通过 `If-None-Match` / `If-Modified-Since`。
- **GitHub Advisory Database** 通过 `git clone --depth 1` 然后 `git pull
--ff-only` 获取 `github/advisory-database`。仅解析 `advisories/github-reviewed/`。
- **拼写劫持顶级 N 参考列表**(hugovk 维护的 `top-pypi-packages` JSON),7 天 TTL,
缓存于 `~/.packguard/cache/reference/pypi-top-packages.json`。npm 基线内嵌在二进制中(约 200 个名称);
可通过在同一路径放置自定义列表来扩展。
- 加载列表后,对每个被监控软件包进行评分 —
Levenshtein ≤ 2、字符交换、前缀/后缀模式匹配的嫌疑项被持久化为
`malware_reports`,来源为 `typosquat-heuristic`。
- 默认仅持久化已存在于存储中的软件包的建议(保持数据库紧凑,约数百行);
`--all` 持久化转储中的每一条建议(CI 预热;会使数据库膨胀)。
### `packguard audit [路径] [--focus] [--fail-on] [--fail-on-malware] [--severity] [--no-live-fallback]`
读取存储(除非实时回退触发,否则无需网络)并打印每个已安装依赖的匹配风险,分为三节:
- **CVE** — 包含软件包、已安装版本、建议 ID(优先使用 CVE)、严重性、
受影响范围、修复版本的表格。符合 `--severity` 与 `--fail-on`。
- **恶意软件** — 包含软件包、已安装版本、来源(osv-mal、ghsa-malware、
socket.dev)、建议引用、证据摘要的表格。`--fail-on-malware` 退出 1。
- **拼写劫持嫌疑** — 软件包、与其相似的合法名称、编辑距离、得分(0.0–1.0)、
原因(交换 / 编辑 / 前缀 / 后缀)。
`--focus cve|malware|typosquat|all`(默认 `all`)限制输出到单一节;
`--format table|json|sarif`。SARIF 在 `packguard.cve` 与 `packguard.malware` 规则下输出两个发现 —
将文件放入 GitHub 代码扫描 UI 即可进行内联注释。
当设置 `PACKGUARD_SOCKET_TOKEN` 环境变量且未传递 `--no-live-fallback` 时,
每个已安装(生态,名称,版本)元组也会查询 [Socket.dev](https://socket.dev)。
标记为恶意软件的警报归为 `Malware` 类型;其他(`installScripts`、`obfuscatedFile`、…)
作为信息性 `ScannerSignal`。未设置令牌时静默跳过 Socket。
未启用 `--no-live-fallback` 时,对缓存中尚无建议的软件包也会触发
`POST /v1/query` 查询 `api.osv.dev`(每个软件包 24 小时 TTL)。
### `packguard report [路径] [--format table|json|sarif] [--fail-on-violation]`
仅读取 **SQLite 存储**(零网络)。加载 `.packguard.yml`(或内置保守默认值),
评估每个存储的依赖项,并打印按生态系统 → 工作区 → 软件包分组的合规表:
- `Policy` 列:`compliant` / `warning` / `violation` / `cve-violation` /
`malware` / `typosquat` / `insufficient`。
- `Risk` 列:组合徽章:`2🔴 · 1🟠 · 1🏴☠️ · ⚠`(CVE 计数 +
已确认恶意软件 + 拼写劫持嫌疑)。
- 页脚摘要:合规 / 警告 / 违规 / 不足,以及漏洞计数和
`Supply-chain: 🏴☠️ N 确认恶意软件 · ⚠ M 拼写劫持嫌疑`(非零时)。
`--fail-on-violation` 在至少存在一行处于 `violation`、`cve-violation` 或
`malware` 时退出 1。JSON / SARIF 输出对第二阶段结果做增量叠加。
### `packguard ui [路径] [--port N] [--host H] [--no-open]`
启动本地仪表板。在调试构建中,Rust 服务器仅提供 `/api/*` —
请同时运行 `pnpm --dir dashboard dev` 以便 Vite 代理 API 调用。
在发布构建中(通过 `--features ui-embed` 编译),该二进制文件也提供
已构建的 Vite 捆绑包,路径为 `/`,因此单个命令即可足够。Ctrl+C 触发优雅关闭。
### `packguard graph [路径] [--workspace …] [--focus pkg] [--contaminated-by CVE] [--format ascii|dot|json] [--max-depth N] [--kind runtime,dev,peer,optional]`
仅读取 SQLite 存储(先用 `scan` 填充)。输出传递性依赖关系图,格式如下:
- `ascii`(默认)— 带风险后缀的缩进树
(`(high CVE)`、`(malware)`、`(unresolved peer)`)。
- `dot` — Graphviz `digraph`,生态着色填充 + CVE 命中红色边框。
通过 `dot -Tsvg` 或类似工具转换为 SVG。
- `json` — 原始 `GraphResponse`(或启用 `--contaminated-by` 时的
`ContaminationResult`);与仪表板通过 ts-rs 消费的 DTO 相同。
`--focus ecosystem:name@version` 限制到前向可达子树。
`--contaminated-by <建议>` 从给定 CVE/GHSA/别名执行逆向污染 BFS,
并打印每个根 → 命中链条;复用与 `/graph` 页面相同的缓存。
### `packguard scans [--json]`
列出存储中已知的每个 `(路径, 生态)` —
当 `report` / `audit` / `graph` 因“未缓存扫描”而退出时非常有用。
`--json` 用于脚本编写。
## 策略格式(`.packguard.yml`)
完整参考见 CONTEXT.md §6。简要介绍:
```
defaults:
offset: -1 # stay one major behind latest
allow_patch: true
allow_security_patch: true
stability: stable # exclude prereleases
min_age_days: 7 # ignore releases younger than a week
block:
cve_severity: [high, critical] # any installed match → cve-violation
malware: true # MAL-* / GHSA malware on installed → malware
deprecated: true
yanked: true
typosquat: warn # warn | strict | off (default warn)
overrides:
- match: "react" # exact name
offset: 0
- match: "lodash"
pin: "4.17.21" # hard pin
- match: "@babel/*" # glob
offset: -2
groups:
- name: security-critical
match: ["jsonwebtoken", "bcrypt*", "@auth/*"]
offset: 0
min_age_days: 0
```
解析级联:`defaults` → 每个匹配的 `group` → 每个匹配的
`override`,后续层严格覆盖字段。
## 支持的生态系统(Tier 1)
| 生态系统 | 包管理器 | 用于 `installed` 的锁定文件 |
|----------|----------|------------------------------|
| npm | npm | `package-lock.json` v2 或 v3 |
| npm | pnpm | `pnpm-lock.yaml`(根导入器) |
| npm | yarn | 仅清单(yarn.lock 解析延迟) |
| PyPI | poetry | `poetry.lock` |
| PyPI | uv | `uv.lock` |
| PyPI | pip | **仅声明依赖**(见下文) |
Tier 2(Cargo、Go 模块)在 MVP 之后提供。所有明确不在范围内的内容
列在 CONTEXT.md §4 中。
### pip 仅声明模式
pip 没有原生锁定文件格式。PackGuard 解析 `requirements*.txt`
尽力遵循 PEP 508,仅将使用精确固定(`pkg==1.2.3`)的要求视为
**已安装**。宽松范围如 `flake8>=7.0` 保持 `installed = None` 并归类为
`Unknown` / `Warning`。结果是:
仅 `requirements.txt` 的仓库会产生完全分类的行(针对 `==` 固定项)和
警告(针对其他项)— 如果需要完全覆盖,请迁移到 `pyproject.toml` + `uv.lock`
或运行 `pip-compile --generate-hashes`。
## 供应链情报来源
| 来源 | 激活方式 | 补充内容 |
|------|----------|----------|
| OSV.dev 转储 | 始终(通过 `packguard sync`) | npm 与 PyPI 的 CVE + MAL 记录 |
| GitHub Advisory DB | 始终(通过 `sync` 克隆) | 与 OSV 通过别名在匹配时去重 |
| OSV `/v1/query` | 默认;可通过 `--no-live-fallback` 禁用 | 缓存未命中时针对(名称,版本)的回退(24 小时 TTL) |
| 拼写劫持启发式 | 始终(列表每 7 天刷新) | Levenshtein ≤ 2、交换、前缀/后缀 |
| Socket.dev | `PACKGUARD_SOCKET_TOKEN=…` | 每个版本扫描器提醒(恶意软件、安装脚本等) |
### Socket.dev 设置
1. 在 [socket.dev](https://socket.dev) 注册(免费层级适用于日常使用)。
2. 在仪表板生成 API 令牌。
3. `export PACKGUARD_SOCKET_TOKEN="sk_…"`。
4. 运行 `packguard audit`。CLI 打印一行确认令牌已被检测到;
结果进入 `malware_reports`,来源为 `socket.dev`。未设置令牌时静默跳过 Socket。
### Phylum
Phylum 的 API 以项目为导向而非每个软件包,因此无法适配相同的拉取模式。
**推迟**到引入项目级扫描器的未来阶段;现有 `socket.dev`
可选模式展示了该模式将采用的形式。
### 拼写劫持调优
默认启发式故意保守——它会标记嫌疑项,但
*已安装软件包中拼写劫持的基础比率约为 0%*。预期主要是误报。
经验法则:
- 将嫌疑项视为人工审核项,而非阻断项 — 默认
`block.typosquat: warn` 反映了这一点。
- 当重复出现 FP 时,向源代码白名单添加合法混淆项
(`crates/packguard-intel/src/typosquat/mod.rs::WHITELIST`)。
- 对于高信任环境,可通过 `.packguard.yml` 覆盖按组设置
`block.typosquat: strict`,而非全局设置。
## 项目结构
```
.
├── CONTEXT.md # source of truth for scope & decisions
├── Cargo.toml # workspace manifest
├── crates/
│ ├── packguard-core # Ecosystem trait, npm + pypi parsers, shared types
│ ├── packguard-policy # YAML parser, rule resolution, evaluator
│ ├── packguard-store # rusqlite + refinery persistence (V1..V5)
│ ├── packguard-intel # OSV/GHSA fetchers, matcher, malware harvest,
│ │ # typosquat heuristic, OSV/Socket clients
│ ├── packguard-server # axum REST API + job runner + ts-rs DTOs
│ │ # (+ rust-embed fallback under ui-embed feature)
│ └── packguard-cli # binary (init / scan / sync / audit / report / ui / graph)
├── dashboard/ # Vite + React 19 SPA consuming the REST API
├── docs/screenshots/ # embedded dashboard captures (real Nalo data)
├── fixtures/ # npm-basic, pypi-poetry, pypi-uv, pypi-pip
└── rust-toolchain.toml
```
## 开发
```
cargo test # full workspace test suite
cargo clippy --all-targets -- -D warnings
cargo fmt --all -- --check
cargo test -p packguard-server --features ui-embed --test embed # dashboard serving
# 仪表盘(Vite 工作区)。
pnpm --dir dashboard lint
pnpm --dir dashboard typecheck
pnpm --dir dashboard test
```
在 `packguard-server` 中 DTO 变更后重新生成 TypeScript 类型:
```
PACKGUARD_REGEN_TYPES=1 cargo test -p packguard-server --test types_drift
```
受 `PACKGUARD_LIVE_TESTS=1` 约束的实时测试会执行真实的 api.osv.dev 查询。
未设置该环境变量时它们会无操作并打印说明。
```
PACKGUARD_LIVE_TESTS=1 cargo test --test live_osv
```
## 非目标(v1)
- 无 SaaS/云后端。
- 无桌面应用(无 Tauri,无 Electron)。
- 无 IDE 扩展。
- 无操作系统包管理器(`apt`、`brew`、`pip install` 行为超出已声明依赖之外)、无 Docker / Helm、无 Nix。
- 不以供应链博客作为主要来源抓取 — 仅使用结构化 API。
参见 CONTEXT.md §4 获取完整排除列表,以及 §12–§14 获取分阶段计划与明确推迟内容。
## 许可证
根据以下之一授权:
- Apache License, Version 2.0([LICENSE-APACHE](LICENSE-APACHE) 或
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
- MIT 许可证([LICENSE-MIT](LICENSE-MIT) 或
[http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
任选其一。
### 贡献
除非你明确声明否则,任何故意提交至本工作的贡献,均按上述 Apache-2.0 许可证双重授权,不附加额外条款或条件。
标签:axum, CVE, Cytoscape, GitHub Advisory, GraphQL, monorepo, Mutation, npm, OSV, PyPI, React, Rust, SARIF, SEO: 供应链治理, SEO: 包安全, SEO: 本地漏洞扫描, Socket.dev, SQLite, Syscalls, ts-rs, Vercel, Vite, 仪表盘, 依赖图, 依赖管理, 偏移策略, 前后端分离, 包版本治理, 可视化, 可视化界面, 多仓库, 多生态, 审计, 山寨包检测, 提示词模板, 政策即代码, 数字签名, 无云, 最小年龄策略, 本地化, 污染传播, 版本历史, 版本漂移, 离线审计, 稳定性策略, 策略引擎, 网络安全挑战, 网络流量审计, 请求拦截, 通知系统, 锁定解析, 风险评分