alpha-omega-security/scrutineer

GitHub: alpha-omega-security/scrutineer

一款基于 Claude Code 技能 pipeline 的开源代码安全漏洞扫描与披露管理平台,支持从发现到披露的全流程追踪。

Stars: 120 | Forks: 24

# scrutineer 一个用于扫描开源代码仓库以发现安全漏洞并管理披露流程的本地工具。你可以通过 URL 添加代码仓库,scrutineer 会针对其运行由 [claude-code 技能](https://agentskills.io) 组成的 pipeline,并在 Web UI 中呈现结果。你可以在此对发现的问题进行分类、确认维护者并追踪披露情况。 ## 快速开始 你需要安装 [Go 1.26+](https://go.dev/dl/) 并运行 [Docker](https://docs.docker.com/get-docker/)。 ``` git clone https://github.com/alpha-omega-security/scrutineer cd scrutineer ``` 通过以下两种方式之一对 Claude 进行身份验证: **方式 A:Claude Code 订阅** (Max、Pro、Team 或 Enterprise) —— 使用 [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) 生成长期有效的 OAuth token: ``` claude setup-token export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... ``` **方式 B:Anthropic API key**,获取自 [console.anthropic.com](https://console.anthropic.com): ``` export ANTHROPIC_API_KEY=sk-ant-api03-... ``` 然后启动 scrutineer: ``` export ANTHROPIC_BASE_URL=https://... # optional: custom API endpoint go run ./cmd/scrutineer -skills ./skills ``` 然后打开 http://127.0.0.1:8080。 大批量任务在遇到 Claude 速率限制墙时会自动暂停和恢复,无需额外配置。当 claude-code(在使用 Claude 订阅 token 运行时)报告账户级别的速率/使用限制时,scrutineer 会暂停队列中剩余的扫描,从 claude-code 自身输出中发出的 `rate_limit_event` 读取重置时间,并在重置后将暂停的批次重新加入队列。如果没有报告重置时间(例如 API key 账户,它们不会发出这些事件),则批次保持暂停状态,等待手动恢复。最近的每个时间窗口的状态会显示在 `/usage` 页面上。 scrutineer 会自动检测 Docker 并开始使用它:每次扫描都在一个带有只读源码挂载和出口白名单代理的临时 container 中运行。runner 镜像 (`ghcr.io/alpha-omega-security/scrutineer-runner`) 会在首次使用时拉取,因此第一次扫描由于需要下载会相对较慢。如果 Docker 不可用,扫描将直接在宿主机上运行,没有任何隔离;在进行此操作之前,请参阅“安全”章节。 点击侧边栏中的 **添加代码仓库**,粘贴一个 git HTTPS URL,scrutineer 就会将 `triage` 技能加入队列。如果要扫描非默认的维护分支,请填写 **分支** 字段(它会在你输入时自动建议远程仓库的分支,并且也接受 tag 或 commit),或者在 URL 后面附加 `/tree/` 后缀;在批量导入时该后缀也支持每行一个。分类之后,剩余的 pipeline 任务会并行入队。元数据和包查询会在几秒钟内完成;而安全深度分析则需要几分钟,具体取决于仓库大小。打开仓库页面并切换到“扫描”选项卡以查看进度,或者等待“发现的问题”选项卡自动填充结果。 要一次性接入整个 GitHub 组织,请打开 **批量添加** → **导入整个组织** 并输入组织(或用户)名。scrutineer 会获取每个仓库,并使用默认的扫描集将它们逐一加入队列,除非你主动选择,否则会跳过 fork 和已归档的仓库。数据库中已存在的重复项将被跳过。在导入大型组织时,请设置 `GITHUB_TOKEN` 以提高 GitHub 未认证速率限制的上限。 你也可以扫描磁盘上的目录,这在推送代码之前,或者对于非托管在 git 代码托管平台上的代码非常有用。在同样的 **添加代码仓库** 字段中粘贴绝对路径 (`/path/to/project`)。scrutineer 会将该目录复制到每次扫描的独立 workspace 中,并运行默认的技能集;需要代码托管平台 URL 或 ecosyste.ms 数据丰富的技能(`advisories`、`exposure`、`fork`、`maintainers`、`metadata`、`packages`、`public-issue`、`report-upstream`)会被自动跳过。在复制过程中,符号链接会原样重新创建而不是被解引用;在 container 模式下,它们的目标会在 container 内部解析,因此只能通过此类链接访问的宿主机文件对技能是不可见的。在 `--no-container` 下,内核会正常对其进行解引用,因此请仅让 scrutineer 指向你信任的目录树。 可选的分析工具(semgrep、zizmor、git-pkgs、brief)已打包在 runner 镜像中,因此当使用 container 运行器时,你无需在本地安装它们。 ## Git 身份验证 scrutineer 会直接调用 `git clone` 而不显式传递 token,因此它会使用宿主机上已配置好的任何凭据:SSH 密钥、凭据助手、`gh auth login`、`.netrc` 文件或 macOS 钥匙串。 要扫描私有仓库,请在将 URL 添加到 scrutineer 之前,确保 `git clone https://github.com/org/repo` 可以在你的终端中正常运行。如果可以,scrutineer 也能克隆它。 常见配置: ``` # GitHub CLI (最简单) gh auth login # Git credential helper git config --global credential.helper store # or osxkeychain / manager-core # 不支持基于 SSH 的 clone URLs —— scrutineer 仅接受 https:// URLs。 # 请改用 credential helper 来认证 HTTPS clones。 ``` 当在 Docker (`docker run ...`) 内部运行时,container 无法访问宿主机凭据。挂载一个凭据存储或设置 `GIT_ASKPASS`,以便从 container 内部访问私有仓库。 当 container 化运行器处于活动状态时(当 container runtime 可用时的默认情况),每次扫描都在单独的 container 中运行,但克隆操作发生在宿主机上,之后源码才会被挂载进去。克隆使用的是宿主机凭据;container 永远不会看到它们。 ## 功能 ### 扫描与分析 - **基于技能的扫描 pipeline** —— 每次扫描都是磁盘上的一个 claude-code 技能(SKILL.md + schema + 可选脚本)。对于新仓库,默认的 pipeline 本身也是一个技能 (`triage`),由它将其他技能加入队列;修改它的 SKILL.md 即可更改运行内容 - **结构化的问题发现** —— 漏洞报告被解析到数据库中,包含严重程度、CWE、位置(链接到源码)、受影响版本以及六步分析追踪 - **威胁模型视图** —— 项目的安全契约(组件、入口点信任表、提供和声明拒绝的属性、已知的非发现问题)从 `threat-model` 扫描中渲染而来,对于较旧的仓库,则回退使用深度分析的边界和汇聚点清单 - **依赖关系探索** —— 依赖和被依赖关系表,支持一键导入以扫描任何包的源码仓库 - **包注册表数据** —— 每个已发布包的下载量、被依赖量、版本和注册表链接 - **已知安全公告** —— 自动拉取现有的 CVE 和安全公告 - **维护者识别** —— 由模型驱动的技能,结合提交历史、Issue/PR 活跃度和注册表所有权,识别出在进行漏洞披露时应联系的人员 - **CWE 目录** —— 内置 MITRE CWE 数据,在发现列表中以工具提示展示,在发现页面上展示完整描述 - **可达性分析** —— 将依赖中发现的汇聚点通过应用程序代码进行追踪,以查看哪些实际上是可达的 - **重新扫描去重** —— 发现的问题带有内容指纹,因此重新扫描会更新现有记录而不是创建重复项;一次扫描中相同指纹的命中记录会折叠为一个带有可展开 `+N` 位置列表的发现项,而不再出现的发现项会被标记为“未见到”并记录缺失次数 ### 分类与披露工作流 - **问题发现工作流** —— 指导性的分类流程,从新建到验证、披露和发布,每个步骤都设有人工审核关卡 - **低成本分类器预筛选** —— `revalidate` 技能会自动对高危/严重级别的深度分析结果以及所有导入的发现项进行入队,发出 `true_positive` / `false_positive` / `already_fixed` / `uncertain` 判定,以及可选的严重程度调整;对于高危/严重发现项的 `true_positive` 判定会自动触发并链接至 `verify` - **审计队列** —— 在 `/audit` 处对最近低危和误报判定进行随机抽样,以便操作员对分类器进行抽查;每次审查都会在发现项上记录同意或推翻的判定 - **在野利用标志** —— 仅限分析师填写的 `yes`/`no` 字段(带自由文本证据),展示在发现页面、OSV `database_specific` 块、CSAF 审计记录以及 Markdown 报告导出中 - **破坏性变更分类器** —— `breaking-change` 技能会针对建议的修复 diff 以及最相关的被依赖项进行扫描,记录 `breaking` / `non_breaking` / `unknown`,并附带理由和受影响的被依赖项列表 - **缓解指导** —— `mitigate` 技能会为每个发现项起草短期变通方案和可选的 semgrep 规则,独立于代码修复之外 - **CVSS v3.1 和 v4.0** —— 两个评分向量并排存储并得出衍生分数;分析师表单、OSV/CSAF 导出和 `disclose` 技能都同时携带两者,且 v4 指标集与 v3 保持区分 - **发布监视** —— `release-watch` 技能弥补了修复提交与修复发布之间的空档:一旦发现项状态达到 `fixed`,该技能会轮询上游发布,并在出现修复时记录发布标签、URL 和时间戳 - **CNA 匹配** —— 识别范围覆盖该仓库的 CVE 编号机构 (CNA),以便将披露发送给正确的联系人 - **向上游报告** —— 通过 GitHub 的私密漏洞报告功能,附上建议的补丁,在上游代码仓库提交漏洞发现,并在 GitHub 授予访问权限时将修复推送到临时的私有 fork 中。PVR 报告很难撤回;在将其指向外部仓库之前,请先在你拥有并启用了 PVR 的仓库上进行一次端到端的运行,以确认报告正文和补丁附件的效果符合预期。当上游没有可用的 PVR 时,请遵循 [docs/disclosure-fallback.md](docs/disclosure-fallback.md) 中的操作手册进行 ### 导入与导出 - **SBOM 导入** —— 上传 CycloneDX 或 SPDX 文档,将每个组件解析到对应的源码仓库,并自动排队进行扫描 - **发现项导入** —— 将来自外部扫描器和渗透测试报告的 SARIF、CSV、Markdown 或最简 JSON 格式的发现项 POST 到与原生扫描相同的工作流中,并通过指纹对重复导入进行去重 - **自由格式导入** —— 当 `/api/v1/import` 中的格式探嗅器无法处理某 payload 时,`ingest` 技能会在其进入发现列表之前,针对源码检出(解析位置)对其进行规范化 - **CSAF 导出** —— 将任何发现项下载为通过 schema 验证的 CSAF 2.0 公告文档 - **OSV 导出** —— 将任何发现项下载为通过 schema 验证的 OSV 记录,与 OSS-SIRT 公告模板对齐(致谢、CWE ID、撤回、SEMVER 范围、CVSS v3 + v4 严重程度条目) - **JSONL 导出** —— 将所有发现项或扫描流式传输为换行符分隔的 JSON,以便输入到其他系统 - **Markdown 报告导出** —— 为每个仓库或组织下载一个整合的 `report.md` - **披露捆绑包** —— 为每个发现项下载 `bundle.tar.gz`:包含 OSV、CSAF、Markdown 报告、patch.diff 以及列出内容清单的 manifest;方便移交给协调员或在 GitHub PVR 之外通过私密邮件发送时作为附件 ### 运维特性 - **容器化运行器** —— 可选的每次扫描 container 隔离,带有只读源码挂载、降权 capabilities 以及经过身份验证的出口白名单代理 - **技能 HTTP API** —— 正在运行的技能可以回调 scrutineer 以列出之前的扫描并将更多技能加入队列;接口文档见 `openapi.yaml` - **实时更新** —— 通过 SSE 流式传输扫描日志和状态变更,无需轮询 - **组织汇总** —— 按所属组织对仓库、发现项和维护者进行分组,并支持按组织导出 Markdown - **用量追踪** —— 每次扫描的 token 和成本数据,以及按技能汇总花费的 `/usage` 页面;如果使用的是 Claude 订阅 token,速率限制墙会自动暂停批次,并在报告的重置时间后恢复,同时在 `/usage` 显示每个时间窗口的状态。可选地 (`downgrade_on_overage`),一旦账户出现超额,新扫描的模型层级就会从 max/high 回退到中间层级,直到超额解除(通常在时间窗口重置时)——此变更会在日志、任务页面和 `/usage` 上进行公告 - **主题** —— 六种颜色主题,加上浅色/深色/系统切换,可在设置页面进行配置 ## 默认 pipeline 当添加一个仓库时,`triage` 技能会被加入队列。它的 SKILL.md 列出了要触发的技能。内置的技能位于 `skills/`中: | 技能 | 功能说明 | |-------|--------------| | `triage` | 通过 scrutineer API 编排默认的扫描集 | | `metadata` | 从 repos.ecosyste.ms 获取仓库元数据 | | `packages` | 从 packages.ecosyste.ms 查找已发布的包 | | `advisories` | 获取已知的安全公告 | | `dependencies` | 运行 `git-pkgs list` 索引所有 manifest | | `sbom` | 运行 `git-pkgs sbom` 生成 CycloneDX SBOM | | `maintainers` | 基于模型的分析,用于识别真实的维护者和联系方式 | | `repo-overview` | 运行 `brief --json` 生成结构化的项目摘要 | | `subprojects` | 枚举 monorepo 中的包/workspace,以便将深度分析范围限定在子路径下 | | `threat-model` | 推导项目的安全契约(组件、入口点信任表、声称提供和声明拒绝的属性),供深度分析加载 | | `semgrep` | 将静态分析映射到发现项的结构中 | | `vuln-scan` | 高召回率的、基于模型的静态候选扫描,改编自 Anthropic 的 defending-code 参考测试框架 | | `zizmor` | 将 GitHub Actions 工作流审计映射到发现项的结构中 | | `ingest` | 当 `/v1/import` 无法识别 payload 时,将任意格式的外部报告规范化为发现项 | | `security-deep-dive` | 生成结构化发现项的基于模型的审计 | | `finding-dedup` | 比较处于打开状态的发现项,并将重叠的报告标记为重复 | | `verify` | 针对当前 HEAD 重新检查单个发现项;记录 可复现 / 已修复 / 无法复现 | | `revalidate` | 低成本只读分类器(散文描述 + `git log`,不执行 PoC),发出 真阳性 / 假阳性 / 已修复 / 不确定 判定;对于来自 `security-deep-dive` 的高危/严重发现项以及所有导入的发现项自动入队。对于高危/严重发现项的 `true_positive` 会自动链接到 `verify` | | `breaking-change` | 对建议的修复 diff 进行静态破坏性变更检查;记录 `breaking`/`non_breaking`/`unknown` 并附带理由和受影响的被依赖项 | | `release-watch` | 当发现项状态达到 `fixed` 后,监视上游是否发布包含该修复提交的版本;在发现项上记录发布标签、URL 和时间戳 | | `disclose` | 为单个发现项起草 GHSA 格式的公告(标题、描述、CVSS、CWE、参考链接) | | `patch` | 提出修复单个发现项的统一 diff;通过适用性检查的 diff 将作为建议修复存储在发现项中 | | `report-upstream` | 通过 GitHub PVR 在上游代码仓库提交单个发现项,并附上建议的补丁;这是将发现项状态变为 `reported` 的操作 | | `public-issue` | 在分析师确认后,将低严重程度的发现项作为普通的公开 GitHub Issue 提交 | | `reachability` | 通过应用程序代码追踪依赖项的汇聚点,以确定哪些可以从信任边界访问到 | | `cna-match` | 将代码仓库与其 CVE 编号机构 (CNA) 匹配,以便将披露发送给正确的联系人 | | `posture` | 在仓库行中记录该仓库的安全态势(报告策略、响应历史、安全加固情况) | 修改 `skills/triage/SKILL.md` 以更改默认运行的内容。将新的技能目录放入 `skills/` 即可添加新的扫描类型;无需更改代码。有关 frontmatter 参考、`scrutineer.*` 元数据键、`context.json` 结构、输出类型、schema 验证以及面向技能的 HTTP API,请参阅 [docs/skills.md](docs/skills.md)。 每次扫描前,lockfile、压缩包和生成的目录树都会从 workspace 中剔除,以免技能在这些文件上浪费处理轮次。内置的跳过列表涵盖了 `node_modules`、`dist`、`generated`、`__generated__`、`*.min.js`/`*.min.css` 以及常见的 lockfile (`pnpm-lock.yaml`、`package-lock.json`、`yarn.lock`、`Cargo.lock`、`go.sum`、`Gemfile.lock`、`poetry.lock`、`composer.lock`)。技能可以通过 `scrutineer.paths` (允许列表) 覆盖此设置,并在此基础上叠加 `scrutineer.ignore_paths`;请参阅 [docs/skills.md](docs/skills.md#path-filtering)。 ## 从其他工具导入发现项 scrutineer 可以接收在其他地方生成的发现项,以便它们进入相同的分类和披露工作流: ``` curl --data-binary @report.sarif http://127.0.0.1:8080/api/v1/import ``` 支持 SARIF 2.1.0、CSV、Markdown 和最简 JSON 格式;具体格式将通过请求体进行嗅探识别。有关完整的请求和响应结构、各格式字段映射以及如何添加对新格式的支持,请参阅 [docs/import.md](docs/import.md)。 ## UI 导航 每个索引页面都有一个搜索框以及过滤和排序下拉菜单;具体选项因页面而异。侧边栏部分包括: - **代码仓库** —— 你扫描过的仓库,包含语言、最近扫描状态和发现项计数。点击其中一个,可通过选项卡查看摘要、发现的问题、威胁模型、包、依赖项、被依赖项、安全公告、维护者、数据和扫描记录,此外还有一个“导出报告”按钮用于生成 Markdown 汇总。 - **组织** —— 按所属组织分组的仓库、发现项和维护者,并支持按组织导出 Markdown。 - **发现的问题** —— 跨所有仓库的每一个漏洞。问题详情页显示六步分析(追踪、边界、验证、现有技术、可达性、评级)、评分字段、备注、沟通记录、参考链接、标签以及变更历史。 - **包** —— 跨所有仓库发现的注册表条目。 - **安全公告** —— 为任何已扫描包拉取的已知 CVE 和安全公告。 - **维护者** —— 被识别为维护者的人员,及其关联的仓库和发现项。 - **SBOM** —— 上传的 CycloneDX/SPDX 文档。每个组件都会被解析到对应的源码仓库,并支持导入以进行扫描。 - **审计** —— 对最近的低危和误报判定进行随机抽样,用于抽查低成本分类器的输出。每一行都记录了分析师同意或推翻的判定,并且一个小型仪表板会显示实时的推翻率。 - **扫描** —— 已经运行过的每一次扫描。队列中的扫描可以暂停/恢复,正在运行或队列中的扫描可以被取消,失败的任务可以重试。 - **技能** —— 从磁盘和 UI 安装的技能;可以查看、编辑或运行其中任何一个。 - **用量** —— 跨所有扫描的 token 和成本总计,按技能分类细目。 - **设置** —— 主题、颜色方案、模型层级、运行器并发数(应用时需重启运行器,这会取消正在进行的扫描)和默认轮次上限(应用于下一次扫描),以及系统统计信息(记录计数、DB 大小、路径)。 ## 问题处理工作流 来自 `security-deep-dive` 技能的每个发现项都从 **新建** 开始,并在引导式工作流中流转: 1. **new** —— 刚刚识别出。对于来自 `security-deep-dive` 的高危/严重发现项以及每个导入的发现项,会自动首先入队执行一次 `revalidate`,这会在发现项上记录 `true_positive` / `false_positive` / `already_fixed` / `uncertain`,并且(当高危/严重发现项判定为 true_positive 时)链接至 `verify`。在此路径之外:点击“验证”以触发独立确认,如果你信任审计结果则点击“跳至分类”,或者点击“拒绝” 2. **enriched** —— 已运行验证。审核并点击“分类” 3. **triaged** —— 确认为真实问题。点击“准备披露” 4. **ready** —— 草案已准备就绪。运行 `report-upstream` 技能以通过 GitHub PVR 提交(仅限 github.com,需要 `gh` 身份验证),对于经过审查且可以安全公开提交的低危安全加固类发现项运行 `public-issue`,或者在你自行发送后点击“标记为已报告”。当上游没有 PVR 时,请遵循 [docs/disclosure-fallback.md](docs/disclosure-fallback.md) 中的操作手册:如果 `cna-match` 指定了 CNA,则路由给该 CNA,否则联系 `maintainers` 返回的渠道 5. **reported** —— 已发送给维护者。当他们回复时点击“已确认” 6. **acknowledged** —— 维护者正在修复中。当修复发布时点击“标记为已修复” 7. **fixed** —— 补丁可用。点击“标记为已发布”以发布公告 8. **published** —— 完成 每个问题页面都有一个备注部分,用于记录分类理由和沟通历史。 如果 `patch` 运行生成的 diff 通过了适用性检查(diff 可解析、目标文件存在、触及了被标记的文件,并且能通过 `git apply --check`),它将作为 `suggested_fix` 及其基础 commit 存储在发现项中,可从发现页面下载为 `.patch` 文件,并包含在 Markdown 报告导出中。要修改修复方案,请将你的编辑推送到一个分支上,扫描该分支(通过“分支”字段,或使用 `/tree/` URL 后缀),然后针对新的扫描运行 `patch`:diff 将针对该 ref 的目录树提出建议,这样每一轮的编辑、推送和重新扫描都会在你的工作基础上生成一个新的提案。 ## 探索依赖关系 仓库的“依赖项”选项卡按名称对包进行分组,并显示其出现的所有 manifest 文件。默认情况下,它显示运行时依赖项,并带有用于切换查看 测试/构建/开发 行的开关。某个依赖项旁边的导入按钮(箭头图标)会通过 packages.ecosyste.ms 将其解析为仓库 URL,并为其排队完整的 pipeline。你已经导入的依赖项将显示为链接图标。 “被依赖项”选项卡也是如此——你可以一键导入任何被依赖项的仓库。 ## Docker ``` docker build -t scrutineer . docker run -p 127.0.0.1:8080:8080 -v scrutineer-data:/data \ -e ANTHROPIC_API_KEY=sk-ant-api03-... \ -e ANTHROPIC_BASE_URL=https://... \ scrutineer ``` 或者使用 Claude Code OAuth token 代替 API key: ``` docker run -p 127.0.0.1:8080:8080 -v scrutineer-data:/data \ -e CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... \ scrutineer ``` 始终绑定到 `127.0.0.1`。UI 没有身份验证功能;如果绑定到 `0.0.0.0`,会将你的漏洞数据库暴露给网络上的任何人。 如果宿主机上有可用的 container runtime (docker、rootless podman 或 Apple 的 `container`),scrutineer 会在临时的 container 中运行每次扫描以实现隔离。runner 镜像作为多架构 manifest (`linux/amd64` 和 `linux/arm64`) 发布到 GHCR,并在首次使用时自动拉取: ``` go run ./cmd/scrutineer -skills ./skills ``` 使用 `--runtime podman` 可在 podman 而非 docker 下运行扫描(请参阅下文的 [Podman (rootless)](#podman-rootless)),使用 `--runtime apple` 可在 macOS 上通过 Apple 的 `container` runtime 运行扫描(请参阅下文的 [Apple container (实验性)](#apple-container-experimental)),使用 `--no-container` 可完全禁用容器化执行,或使用 `--runner-image` 指定不同的镜像。要在本地构建 runner 而不是从 GHCR 拉取(如果你在这些 runtime 下运行扫描,请改用 `podman build` 或 `container build`): ``` docker build -t scrutineer-runner -f Dockerfile.runner . go run ./cmd/scrutineer -skills ./skills --runner-image scrutineer-runner ``` runner 镜像不会自动更新,因此分析工具链将保留在你拉取时的摘要版本,直到你再次拉取。为了让这种差异可见,scrutineer 会在启动时(在后台进行检查,如果无法连接到注册表则静默失败)检查一次注册表,并在 runner 镜像落后于已发布的 `:latest` 超过七天时对其进行标记——同时显示在启动日志中和设置页面上的横幅中。通过以下方式更新: ``` docker pull ghcr.io/alpha-omega-security/scrutineer-runner:latest ``` 如果你希望自动更新,可以针对 runner 镜像运行 [watchtower](https://github.com/containrrr/watchtower),或者向 runtime 传递 `--pull=always` 参数;scrutineer 故意不自行拉取,这样扫描的工具链只会在你选择更新时才会发生改变。 当 container 运行器处于活动状态时,scrutineer 会在宿主机上启动一个经过身份验证的出口代理,并将 container 内部的 `HTTPS_PROXY`/`HTTP_PROXY` 指向它。该代理仅允许连接到白名单内的主机:Anthropic API、`*.ecosyste.ms`、主要的代码托管平台(GitHub、GitLab、Codeberg、Bitbucket)、常见的包注册表(npm、PyPI、RubyGems、crates.io、Go module proxy、Packagist、Hex、NuGet)、公告来源(semgrep.dev、OSV、NVD、cwe.mitre.org),以及 runtime 的宿端点(对于 docker/podman 是 `host.docker.internal`,对于 Apple 的 `container` 是默认网关 IP),以便访问本地技能 API。对其他任何内容的请求都会收到 403 错误并被记录。可以通过配置文件中的 `egress_allow` 扩展此列表。当设置了 `-anthropic-base-url`(或回退到 `ANTHROPIC_BASE_URL` 环境变量)时,其主机名会自动添加到白名单中。该代理使用基于进程的随机 token,因此它不是开放中继;忽略代理环境的工具不会在网络层被阻止(请参阅 `threatmodel.md`)。 对于将技能 prompt 视为不受信任的部署环境,请传递 `--hardened`(或在配置中使用 `hardened: true`)。该标志会强制使用 container 运行器(拒绝 `--no-container`),将出口白名单缩减为 `*.anthropic.com` 加上宿主机技能 API(因此 `egress_allow` 会被忽略,如果你需要扩大范围则不要使用此标志),使用 `no-new-privileges` 将 container rootfs 只读挂载,将每次扫描附加到使用 `--internal` 创建的单独临时网络中(扫描结束时移除),使得忽略 `HTTPS_PROXY` 的进程无法找到出口路径且并发扫描之间无法互相访问,并且一旦克隆完成,将拒绝其 workspace 占用空间超过 2 GiB 的扫描。2 GiB 的检查发生在克隆之后:它限制了加固模式同意扫描的范围,而不是限制克隆期间可能存入磁盘的内容;如果你需要克隆期间的保证,请使用操作系统级别的磁盘配额。直接访问 ecosyste.ms 或包注册表的内置技能在加固模式下将会失败,除非它们通过宿主机技能 API 进行路由。每个生态系统的 runner 配置文件仍然适用,但需要 `/work` 和 `/tmp` 之外可写路径的配置文件镜像则不兼容。在 rootless podman 下,代理作为每次扫描的 sidecar container 在 `--internal` 网络上运行(宿主机代理在那里无法访问;请参阅 [docs/podman.md](docs/podman.md))。在 podman 下,每次加固扫描首先会验证其 `--internal` 网络确实阻止了外部出口,同时仍能到达出口代理,如果无法确认,则拒绝扫描,因此沙箱永远不会被悄无声息地削弱。 ## Runner 配置文件 当 container 运行器处于活动状态时,scrutineer 会为每次扫描自动检测特定于生态系统的 **配置文件**:它会对克隆的代码运行 `brief` 以读取包管理器,如果遇到 `brief` 无法识别的生态系统,则回退到通过文件标记进行判断。匹配的配置文件会选择一个 runner 镜像(根据需要从 `docker/profiles//Dockerfile` 构建,按 Dockerfile 的哈希值进行内容寻址缓存),并注入该配置文件的 `PROFILE.md` 作为 agent 的引导信息。最具体的配置文件优先级最高,因此带有原生扩展的仓库会在普通语言配置文件之前解析为其 `*-ext` 配置文件。可以在扫描 API 上使用 `?profile=` 强制指定一个(根据技能的 `requires_profile` 进行验证);`default` 会强制使用基础 runner 镜像。 | 配置文件 | 选中条件 | 增加内容 | |---------|---------------|------| | `php` / `php-ext` | Composer / 带有 `PHP_ARG_` 的 `config.m4` | PHP;`php-ext` 为 C 扩展构建 PHP debug + ASan/UBSan | | `python` / `python-ext` | pip/Poetry/uv/PDM / 声明了 C `Extension(` 的 `setup.py` | CPython;`python-ext` 构建 CPython debug + ASan/UBSan | | `ruby` | Bundler | Ruby 3.4 + Bundler;元编程 / 动态分发指导,外加一个用于标记未检测原生扩展的绊线 | | `ruby-ext` | 带有 `spec.extensions` 的 gemspec,或者 `ext/**/extconf.rb` / `ext/**/Cargo.toml` | `ruby` 的**超集**:增加了 ASan/UBSan Ruby(默认解释器)、stock 解释器上的 valgrind,以及用于 rb-sys gems 的 Rust nightly — 为 C/C++/Rust 原生 gems 提供内存安全覆盖 | | `ruby-rails` | `config/application.rb` | `ruby` 的超集,外加 **Brakeman**,特定于 Rails 的 SAST | | `node` | npm | Node.js | | `go` | Go Modules | Go 工具链 | | `java` | Maven/Gradle | JDK | | `dotnet` | NuGet | .NET SDK | | `beam` | Mix/rebar3 | Erlang/Elixir | | `rust` | Cargo | Rust stable + nightly, Miri, sanitizers | | `c-cpp` | CMake/Make/autotools/meson 构建文件且无语言生态系统 | C/C++ 构建工具链 | 这三个 Ruby 配置文件在选择时是互斥的,但 `ruby-ext` 和 `ruby-rails` 是有意设计为 `ruby` 的 *超集*(包含相同的 Ruby 级别审计,加上它们额外的覆盖范围),因此倾向于 `ruby-ext` 的检测只会增加构建时间,而不会损失覆盖范围。如果原生 gem 意外滑入普通的 `ruby` 配置文件,该配置文件的绊线会捕获它,并记录下内存安全扫描需要 `ruby-ext`。你可以通过在 `internal/worker/profile.go` 中注册并添加 `docker/profiles//{Dockerfile,PROFILE.md}` 来添加新的配置文件。 ## Podman (rootless) 传递 `--runtime podman`(或在配置中使用 `runtime: podman`)以在 podman 而非 docker 下运行扫描。推荐使用 rootless podman 的安全姿态:由于 runtime 不等同于 root 权限,因此如果恶意仓库逃逸了扫描 container,它将作为无特权附属用户落地,而不是在宿主机上接近 root 的身份(请参阅 [threatmodel.md](threatmodel.md),T12)。没有自动检测功能——仅安装了 podman 的宿主机必须显式设置 `--runtime podman`;默认值仍保持为 docker。 环境要求: - **podman ≥ 4.7** —— scrutineer 通过 `--add-host host.docker.internal:host-gateway` 访问宿主机出口代理,较旧版本的 podman 不支持此功能;如果没有它,扫描将因网络错误而失败。如果检测到的版本看起来太旧,或者无法解析主机网关地址,启动时会在日志中记录警告。 - **podman ≥ 5.0(对于 `--hardened` 推荐使用)** —— rootless 出口代理 sidecar 必须访问绑定在环回地址上的宿主机技能 API,这需要网络后端将主机网关转发到宿主机环回地址。podman ≥ 5.0 默认使用 pasta (`--map-host-loopback`);较旧版本的 podman 可以在启用了主机环回转发的 slirp4netns 后端下工作。在不可用的情况下,加固扫描将被拒绝(安全失效/失败即关闭)。如果使用 `--hardened` 且 podman < 5.0,启动时会发出警告。请参阅 [docs/egress-sidecar.md](docs/egress-sidecar.md)。 - **`/etc/subuid` + `/etc/subgid`** —— rootless podman 使用 `--userns=keep-id` 将 container 用户映射回你的宿主机用户,因此扫描输出和可恢复的会话存储将保持为宿主机用户所有。你的用户需要一个 sub-id 范围(通常 `useradd` 默认会提供一个;更改后请运行 `podman system migrate`)。scrutineer 在启动时会运行一次性的 keep-id 冒烟测试,如果配置不当,会快速失败并给出提示。 - **`skopeo`(可选)** —— 用于代替 `docker buildx` 以便在移动的 `:latest` runner 标签需要重建各生态系统的配置文件镜像时发出通知。如果没有它,配置文件仍会构建,但仅根据镜像引用来作为其缓存键。 - **SELinux** —— 在启用了 SELinux 的宿主机上(Fedora/RHEL/Rocky/Alma 默认启用),运行器会使用 `:z` 重新标记其绑定挂载,以便 container 可以读取克隆的代码并写入其输出;如果没有它,每次扫描都会因权限错误而失败。这是自动处理的:`--selinux auto`(默认)会检测宿主机,并且启动时的冒烟测试会验证真实重新标记的挂载是否有效。如果你自己预先标记了路径,请使用 `--selinux off`;或者使用 `--selinux on` 强制开启。有关 `:z` 与 `:Z` 的原理说明,请参阅 [docs/podman.md](docs/podman.md#selinux-and-bind-mount-file-passing)。 `--hardened` 会在每次扫描中被验证为安全失效/失败即关闭。在 **rootless podman** 下,扫描无法跨网络命名空间边界路由到宿主机代理,因此出口代理作为 **每次扫描的 sidecar container** 在 `--internal` 网络上运行——这使得在 rootless 下也能实施强制出口限制。它需要一个将主机网关转发到宿主机环回地址的网络后端(现代的 pasta,在 podman ≥ 5.0 中为默认,或启用了主机环回的 slirp4netns);在不可用的情况下,sidecar 无法到达宿主机技能 API,扫描将被拒绝(安全失效/失败即关闭)。对于非网络部分,请回退使用 `--hardened-runtime-only`(只读 rootfs + `no-new-privileges` + 克隆后 2 GiB 的 workspace 上限),或者在没有主机环回转发要求的宿主机上,使用 rootful podman/docker 运行 `--hardened` —— 无论在何种模式下,始终开启的 `--cap-drop ALL` / 非 root 用户 / `/tmp` tmpfs / SELinux `:z` 基线一律适用。有关完整的安全模型,请参阅 [docs/podman.md](docs/podman.md);有关操作员验证清单,请参阅 [docs/egress-sidecar.md](docs/egress-sidecar.md)。 本仓库中显示的用于 runner 镜像和 `docker/profiles/` 下各生态系统配置文件镜像的 `docker build` / `docker run` 命令与 podman 的 CLI 兼容;只需将 `docker` 替换为 `podman` 即可。 ## Apple container (实验性) 传递 `--runtime apple`(或在配置中使用 `runtime: apple`)以在 Apple 的 [`container`](https://github.com/apple/container) runtime 下而非 docker 下运行扫描。此路径需要显式开启;默认值仍保持为 docker,并且 scrutineer 不会在没有 docker 的 Mac 上自动检测。它被标记为实验性是因为它是新功能,且 Apple 的网络环境存在已知的一些不稳定问题,而不是因为存在能力差距:支持普通扫描和 `--hardened` 扫描。 环境要求与注意事项: - **搭载 Apple 芯片的 macOS 26 (Tahoe)**:Apple 仅在 macOS 26 上支持 `container`,并且不会处理无法在此版本上重现的问题,因此较旧版本的 macOS 不在支持范围内。 - **Apple `container` 且 `container system start` 正在运行**:启动时会检查 `container system status`,如果服务不可用,则拒绝使用该 runtime。 - **主机网关**:scrutineer 在宿主机上启动其出口代理,并将扫描 container 指向 runtime 的网关 IP(在 runner 镜像内从 `/proc/net/route` 中发现)。代理将本地技能 API 请求重写回 `127.0.0.1`。 - **加固模式**:支持 `--hardened`。每个 container 都在其独立的轻量级 VM 中运行,因此 VM 边界即为隔离手段;`container network create --internal` 是一个 vmnet 仅主机网络(阻止出口,可访问宿主机代理),且每次加固扫描都会在运行前证明其已安全失效/失败即关闭。Apple CLI 唯一无法设置的 `--hardened` 标志是 `--security-opt no-new-privileges`,该功能由每个 container 的 VM 边界代替实现(Apple 自身不受信任代码的沙箱以相同方式进行了加固)。`--hardened-runtime-only` 是 rootless podman 的概念,在此会被拒绝;请使用 `--hardened`。 当你使用此 runtime 时,本仓库中显示的用于 runner 镜像和配置文件的 `docker build` 命令可以作为 `container build` 运行。有关完整的兼容性矩阵、VM 隔离安全模型以及加固模式的工作原理,请参阅 [docs/apple.md](docs/apple.md)。 ## 标志参数 | 标志 | 默认值 | 描述 | |------|---------|-------------| | `-config` | `./scrutineer.yaml` (如果存在) | YAML 配置文件的路径 | | `-addr` | `127.0.0.1:808` | 监听地址 | | `-data` | `./data` | 用于存放数据库和 workspace 的数据目录 | | `-effort` | `high` | Claude 努力程度级别 | | `-skills` | - | 加载 SKILL.md 文件的本地目录(可重复使用) | | `-skills-repo` | - | `owner/repo[@ref]` 或 git HTTPS URL `https://host/path[@ref]`,用于在启动时克隆技能;`@ref` 固定分支、标签或 commit,且解析出的 SHA 会记录在每次扫描中 | | `--runtime` | `docker` | Container runtime: `docker`, `podman` (支持 rootless podman), 或 `apple` (Apple, 实验性) | | `--selinux` | `auto` | 绑定挂载的 SELinux 重新标记: `auto` (检测到 SELinux 时重新标记), `on`, 或 `off` | | `--no-container` | false | 禁用容器化运行器;直接在宿主机上运行 claude (无隔离)。已弃用的别名: `--no-docker` | | `--hardened` | false | 严格沙箱: 需要 container runtime, 出口限制为 `*.anthropic.com` + 宿主机技能 API, 只读 rootfs, 内部网络 | | `--hardened-runtime-only` | false | `--hardened` 的非网络部分 (只读 rootfs + `no-new-privileges` + 2 GiB workspace 上限) **但不包含**每次扫描的 `--internal` 网络; 这是针对无法运行 `--hardened` 出口 sidecar 的宿主机的 rootless 回退方案 (由 `--hardened` 隐式包含)。已弃用的别名: `--hardened-rootless-runtime` | | `--runner-image` | `ghcr.io/alpha-omega-security/scrutineer-runner:latest` | 用于每次扫描 container 的镜像 | | `-concurrency` | `4` | 并行运行的扫描数量 | | `-clone` | `shallow` | 克隆深度: `shallow` (`--depth 1`) 或 `full` | | `-scan-timeout` | `1h` | 每次扫描的墙上时间限制;超时的扫描将失败 | | `-max-turns` | `0` | 作为 `--max-turns` 传递给 claude-code (0 = 无限制) | | `-schema-strict` | `false` | 当扫描的 `report.json` 未能通过技能的 `schema.json` 验证时,使扫描失败 (默认值: 在扫描日志中发出警告并强行解析) | | `-anthropic-base-url` | - | 自定义 Anthropic API base URL (环境变量: `ANTHROPIC_BASE_URL`) | ## 配置文件 上面提到的每个标志都可以改为在 YAML 配置文件中设置。加载器默认检查 `./scrutineer.yaml`;使用 `-config path/to/file` 进行覆盖。命令行标志始终具有最高优先级。有关完整的文件结构,请参阅 [scrutineer.sample.yaml](scrutineer.sample.yaml)。 配置文件还可以替换模型选择列表,并固定高级别层级使用的回退默认模型: ``` default_model: claude-sonnet-4-6 models: - name: Sonnet 4.6 id: claude-sonnet-4-6 - name: Sonnet 5.0 id: claude-sonnet-5 - name: Opus id: claude-opus-4-7 ``` scrutineer 通过层级来解析技能模型。除非技能的 `SKILL.md` 元数据将 `scrutineer.model` 固定到另一个层级或确切的模型 ID,否则技能默认使用 `high` 层级。诸如 `metadata` 之类的轻量级内置技能使用 `mid`,而 `security-deep-dive` 使用 `max`。你可以通过设置页面将每个层级映射到任何已配置的模型。 ## 沙箱化的 Claude Code 配置 在 `--no-container` 模式下,`claude` 子进程会继承你的 `~/.claude/settings.json`,因此在该处限制网络或文件系统访问的 [沙箱设置](https://code.claude.com/docs/en/sandboxing) 将导致需要这些权限的技能失败。让 `claude` 指向一个专门用于 scrutineer 运行的独立配置目录: ``` CLAUDE_CONFIG_DIR=~/.claude-scrutineer go run ./cmd/scrutineer -skills ./skills ``` 将你的 `settings.json` 复制到该目录中,并删除沙箱相关的键;你正常的 Claude Code 配置将保持不变。Container 模式不受影响:无论宿主机配置如何,`claude` 都会在 container 内部以其自身的环境运行。 ## 安全性 有关报告策略,请参阅 [SECURITY.md](SECURITY.md);有关完整的威胁模型,请参阅 [threatmodel.md](threatmodel.md)。简而言之:扫描代码仓库等同于运行其中的代码。容器化运行器(如果可用)会隔离每次扫描,但默认的裸机模式会以你的用户身份运行所有内容。请仅扫描你愿意在本地克隆和构建的代码仓库。 ## 更多文档 - [docs/skills.md](docs/skills.md) —— 内置技能、编写自定义技能、frontmatter 与输出类型参考 - [docs/import.md](docs/import.md) —— 从其他工具导入发现项 (SARIF, CSV, Markdown, 最简 JSON) 以及添加新格式 - [openapi.yaml](openapi.yaml) —— 面向技能的 HTTP API - [docs/database.md](docs/database.md) —— 完整的数据库 schema 参考 - [docs/backup.md](docs/backup.md) —— 备份和还原数据库 (内置 `scrutineer backup`/`restore`, `sqlite3`, Litestream) - [docs/development.md](docs/development.md) —— 项目布局、重新生成嵌入的数据、运行测试 - [docs/encrypted-sharing.md](docs/encrypted-sharing.md) —— 贡献者之间加密共享发现项 (age + SSH 密钥,团队密钥环管理) - [docs/podman.md](docs/podman.md) —— podman / rootless runtime 的安全模型和已知差距 (沙箱隔离,加固模式验证) - [docs/egress-sidecar.md](docs/egress-sidecar.md) —— rootless `--hardened` 出口代理 sidecar 的操作员验证清单 ## 许可证 MIT。请参阅 [LICENSE](LICENSE)。版权所有 (c) 2026 Alpha-Omega。
标签:AI安全审计, C2, Claude Code, Docker, EVTX分析, Go, Ruby工具, 安全防御评估, 插件系统, 日志审计, 漏洞披露管理, 请求拦截