ParanoiHack/ScopeGuardian
GitHub: ParanoiHack/ScopeGuardian
一款将多种开源安全扫描器(KICS、OpenGrep、Grype)编排整合并与 DefectDojo 联动的 CLI 工具,支持在 CI/CD 流水线中根据可配置阈值自动执行安全门控。
Stars: 2 | Forks: 0
# ScopeGuardian
ScopeGuardian 是一款 CLI 工具,可在您的代码库上运行安全扫描器,并将结果同步至 [DefectDojo](https://github.com/DefectDojo/django-DefectDojo)。当发现的问题数量超过可配置的阈值时,它可以选择性地强制执行安全门控,从而阻止 CI/CD 流水线。
## 目录
1. [前置条件](#prerequisites)
2. [快速开始](#quick-start)
3. [CLI 用法](#cli-usage)
4. [配置文件 (`config.toml`)](#configuration-file-configtoml)
5. [环境变量](#environment-variables)
6. [如何管理 Engagement](#how-engagements-are-handled)
7. [同步功能的工作原理](#how-the-sync-feature-works)
8. [安全门控的工作原理](#how-the-security-gate-works)
9. [使用 Docker 运行](#running-with-docker)
10. [本地 DefectDojo 设置](#local-defectdojo-setup)
## 前置条件
- Go 1.25+(仅在从源码构建时需要)
- [KICS](https://github.com/Checkmarx/kics) 二进制文件位于 `/opt/kics/bin/kics`(已预装在 Docker 镜像中)
- [OpenGrep](https://github.com/opengrep/opengrep) 二进制文件位于 `/opt/opengrep/bin/opengrep`(已预装在 Docker 镜像中;在配置了 `[opengrep]` 时需要)
- [Syft](https://github.com/anchore/syft) 二进制文件位于 `/opt/syft/bin/syft`(已预装在 Docker 镜像中;在配置了 `[grype]` 时需要)
- [Grype](https://github.com/anchore/grype) 二进制文件位于 `/opt/grype/bin/grype`(已预装在 Docker 镜像中;在配置了 `[grype]` 时需要)
- 运行中的 DefectDojo 实例和 API 访问令牌(仅在使用 `--sync` 时需要)
## 快速开始
```
# 构建二进制文件
go build -o ScopeGuardian .
# 运行基本扫描(不同步,不设门控)
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
./config.toml
# 在静默模式下运行扫描(无日志)
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
-q \
./config.toml
# 运行扫描并将发现写入文件(默认为 JSON)
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
-o /tmp/findings.json \
./config.toml
# 运行扫描并将发现写入为 CSV
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
-o /tmp/findings.csv \
--format csv \
./config.toml
# 运行扫描并显示活动和重复的发现
SCAN_DIR=/path/to/repos ./ScopeGuardian \
--projectName my-service \
--branch main \
--filter ACTIVE,DUPLICATE \
./config.toml
# 运行扫描、将结果同步到 DefectDojo,并实施安全门控
SCAN_DIR=/path/to/repos \
DD_URL=http://localhost:8080 \
DD_ACCESS_TOKEN= \
./ScopeGuardian \
--projectName my-service \
--branch main \
--sync \
--threshold critical=1,high=5 \
./config.toml
```
## CLI 用法
```
ScopeGuardian [flags]
```
| 标志 | 类型 | 必需 | 描述 |
|------|------|----------|-------------|
| `--projectName` | string | 是 | 正在扫描的项目名称。使用 `--sync` 时,必须与 DefectDojo 中的产品名称匹配。 |
| `--branch` | string | 是 | 正在扫描的分支(例如 `main`、`feature/my-branch`)。 |
| `--sync` | bool | 否 | 将扫描结果上传到 DefectDojo。需要 `DD_URL` 和 `DD_ACCESS_TOKEN`。默认值:`false`。 |
| `--threshold` | string | 否 | 逗号分隔的严重性阈值,用于定义安全门控(参见[安全门控](#how-the-security-gate-works))。 |
| `--filter` | string | 否 | 逗号分隔的发现状态,用于包含在显示和文件输出中。可接受的值:`ACTIVE`、`INACTIVE`、`DUPLICATE`。默认值:`ACTIVE`。示例:`--filter ACTIVE,DUPLICATE`。 |
| `-q` | bool | 否 | 静默模式:抑制所有日志输出。默认值:`false`。 |
| `-o` | string | 否 | 将发现结果写入指定文件。不包含横幅和日志;仅写入扫描发现结果。 |
| `--format` | string | 否 | 设置 `-o` 时使用的输出格式。可接受的值:`json`(默认)、`csv`、`raw`(纯文本表格)。 |
| `` | path | 是 | TOML 配置文件的路径。 |
### 执行顺序
```
Parse flags → Load config.toml → Initialize scanners
→ Phase 1: Run prerequisite scanners concurrently (Syft SBOM generation)
→ Phase 2: Run dependent/independent scanners concurrently (Grype, KICS, OpenGrep)
Any scanner whose prerequisite failed is skipped automatically
→ Load findings → [Sync to DefectDojo] → Display findings (stdout)
→ [-o: Dump findings to file in --format (json/csv/raw)]
→ [Evaluate security gate → exit(-1) on failure]
```
当同时提供 `--sync` 和 `--threshold` 时,门控会针对已存储在 DefectDojo 中(经过去重后)的发现结果进行评估,而不是原始的本地扫描输出。
## 配置文件 (`config.toml`)
配置文件是一个 [TOML](https://toml.io) 文档,用于控制运行哪些扫描器以及如何管理 engagement。
```
title = "Scope-guardian configuration file" # Optional human-readable label
# 其 DefectDojo engagement 被赋予一年结束日期的分支。
# 所有其他分支获得一周的结束日期。
protected_branches = ["main", "master"]
# 要扫描的目录,相对于 SCAN_DIR 环境变量。
# 由 KICS 和 OpenGrep 共同用作其扫描目标的根目录。
path = "./my-service"
# KICS – 基础设施即代码扫描器
[kics]
# 基础设施平台类型。作为 --type 传递给 KICS。
# 示例:"Dockerfile"、"Terraform"、"CloudFormation"、"Kubernetes"、"Ansible"
platform = "Dockerfile"
# 要从扫描中排除的可选 KICS 查询 ID 列表。
# exclude_queries = ["a227ec01-f97a-4084-91a4-47b350c1db54"]
# Grype – 软件组合分析 (SCA) 漏洞扫描器。
# 启用此部分也会启用 Syft SBOM 生成作为前置条件。
[grype]
# 要忽略的以逗号分隔的漏洞状态。
# 常见值:"not-fixed"、"unknown"、"wont-fix"
ignore_states = "not-fixed,unknown,wont-fix"
# 为 true 时,Syft 会从 Maven Central 解析传递性 Java 依赖。
# 这提高了 Java 项目的扫描准确性,但会显著增加扫描时间。
transitive_libraries = false
# 要从 Grype 扫描中排除的可选路径模式列表。
# exclude = ["**/vendor/**", "**/testdata/**"]
# OpenGrep – 静态应用安全测试 (SAST) 扫描器。
[opengrep]
# 要从扫描中排除的可选路径模式列表。
# exclude = ["**/vendor/**", "**/testdata/**"]
# 要跳过的可选规则 ID 列表。
# exclude_rule = ["python.lang.security.audit.formatted-sql-query.formatted-sql-query"]
# Proxy – 转发给所有扫描器子进程的可选 HTTP/HTTPS 代理设置。
# 所有字段均为可选。省略整个部分或将字段留空以禁用。
# [proxy]
# http_proxy = "http://proxy.company.com:3128"
# https_proxy = "http://proxy.company.com:3128"
# no_proxy = "localhost,127.0.0.1"
# ssl_cert_file = "/path/to/ca.pem" # PEM 编码的 CA 证书(例如 Burp Suite CA)
```
### 字段参考
| 字段 | 类型 | 必需 | 描述 |
|-------|------|----------|-------------|
| `title` | string | 否 | 人类可读的标签;不用于编程处理。 |
| `protected_branches` | string array | 否 | 其对应的 engagement 将获得 1 年结束日期的分支。默认为空(所有分支均为 1 周)。 |
| `path` | string | 是* | 要扫描的目录路径。解析为 `$SCAN_DIR/`。供 KICS 和 OpenGrep 共同使用。 |
| `[kics].platform` | string | 否 | KICS 平台过滤器(例如 `Dockerfile`)。省略时,KICS 会扫描所有受支持的类型。 |
| `[kics].exclude_queries` | string array | 否 | 要跳过的 KICS 查询 ID(例如 `["a227ec01-f97a-4084-91a4-47b350c1db54"]`)。 |
| `[grype].ignore_states` | string | 否 | 要抑制的以逗号分隔的 Grype 漏洞状态(例如 `not-fixed,unknown,wont-fix`)。 |
| `[grype].transitive_libraries` | bool | 否 | 当为 `true` 时,Syft 会通过 Maven Central 解析传递性 Java 依赖项。默认值:`false`。 |
| `[grype].exclude` | string array | 否 | 要从 Grype 扫描中排除的路径 glob 模式(例如 `["**/vendor/**"]`)。 |
| `[opengrep].exclude` | string array | 否 | 要从 OpenGrep 扫描中排除的路径 glob 模式(例如 `["**/vendor/**"]`)。 |
| `[opengrep].exclude_rule` | string array | 否 | 要跳过的 OpenGrep 规则 ID(例如 `["python.lang.security.audit.formatted-sql-query.formatted-sql-query"]`)。 |
| `[proxy].http_proxy` | string | 否 | 作为 `HTTP_PROXY` / `http_proxy` 转发给所有扫描器子进程的 HTTP 代理 URL。 |
| `[proxy].https_proxy` | string | 否 | 作为 `HTTPS_PROXY` / `https_proxy` 转发给所有扫描器子进程的 HTTPS 代理 URL。 |
| `[proxy].no_proxy` | string | 否 | 绕过代理的主机逗号分隔列表,作为 `NO_PROXY` / `no_proxy` 转发。 |
| `[proxy].ssl_cert_file` | string | 否 | PEM 编码的 CA 证书包的路径,作为 `SSL_CERT_FILE`(Go 工具)和 `REQUESTS_CA_BUNDLE`(Python 工具,如 OpenGrep)转发给所有扫描器子进程。在使用拦截代理(例如 Burp Suite)时必需。 |
\* 仅在您希望运行 KICS 或 OpenGrep 扫描时才需要。如果在配置了 `[kics]` 或 `[opengrep]` 的情况下省略 `path`,将导致这些扫描器使用空路径。
省略整个 `[grype]` 部分将同时禁用 Grype 和 Syft SBOM 生成步骤。
省略整个 `[opengrep]` 部分将禁用 SAST 扫描器。
省略整个 `[proxy]` 部分(或将所有字段留空)将禁用代理转发——扫描器子进程不会从此配置中继承任何代理环境变量。
每个代理变量的大写(`HTTP_PROXY`)和小写(`http_proxy`)变体均会被设置,以实现跨工具的最大兼容性。`ssl_cert_file` 的值会同时作为 `SSL_CERT_FILE`(供基于 Go 的工具使用)和 `REQUESTS_CA_BUNDLE`(供基于 Python 的工具如 OpenGrep 使用)输出。
## 环境变量
| 变量 | 必需 | 描述 |
|----------|----------|-------------|
| `SCAN_DIR` | 是 | 扫描操作的基础目录。扫描路径和结果文件将相对于此值进行解析。 |
| `DD_URL` | 使用 `--sync` 时 | DefectDojo 实例的基础 URL(例如 `http://localhost:8080`)。 |
| `DD_ACCESS_TOKEN` | 使用 `--sync` 时 | DefectDojo API 令牌。可在 DefectDojo 的 **User → API v2 Key** 下生成。 |
复制 `.env.example` 到 `.env` 并填入用于本地开发的值:
```
cp .env.example .env
```
## 如何管理 Engagement
ScopeGuardian 为每个项目/分支组合使用单个 DefectDojo **engagement** 来存储该分支的所有发现结果。Engagement 会被自动管理——您无需手动创建或更新它们。
### Engagement 命名
每个 engagement 命名为 `-`,例如:
- `my-service-main`
- `my-service-feature-my-branch`
项目名称必须与 DefectDojo 中现有的 **Product** 完全匹配。
### Engagement 生命周期
当使用 `--sync` 时,每次调用都会运行以下逻辑:
1. 通过确切名称(`projectName`)在 DefectDojo 中**查找产品**。
2. **列出该产品的所有 engagement**(所有页面)。
3. **搜索名称**与 `-` **匹配的 engagement**。
- **找到,且结束日期仍有效** → 按原样重用它。
- **找到,但结束日期已过期** → 自动延长结束日期并重用该 engagement。
- **未找到** → 创建一个新的 engagement。
4. 将扫描结果**上传**至该 engagement 中。
### Engagement 持续时间
结束日期由该分支是否出现在 `protected_branches` 中来决定:
| 分支类型 | 结束日期 |
|------------|----------|
| 受保护的(例如 `main`、`master`) | 从今天起 1 年 |
| 未受保护的(如 feature 分支等) | 从今天起 1 周 |
### Engagement 详情
新创建的 engagement 具有以下属性:
| 属性 | 值 |
|-----------|-------|
| 类型 | CI/CD |
| 状态 | In Progress |
| 标签 | `SCOPE-GUARDIAN`、`` |
| Engagement 级别去重 | 已禁用 |
## 同步功能的工作原理
在命令行中传递 `--sync` 会在扫描完成后,将每个已注册扫描器的扫描结果上传到 DefectDojo。
### 详细步骤
1. 使用 `DD_URL` 和 `DD_ACCESS_TOKEN` 创建 DefectDojo 服务客户端。
2. 解析 engagement ID(参见 [Engagement](#how-engagements-are-handled))。
3. 对于每个已注册的扫描器,将使用 engagement ID 和分支调用其 `Sync` 方法。
### 导入与重新导入
在给定 engagement 的**首次运行**时,每个扫描器会调用 `/api/v2/import-scan/` 端点。DefectDojo 会创建一个新的测试并用发现结果填充它。
在**每次后续运行**时,每个扫描器会检查该 engagement 中是否已存在相同扫描类型的测试(通过 `GET /api/v2/tests/`)。如果找到,它会改为调用 `/api/v2/reimport-scan/`。然后 DefectDojo 会:
- **关闭**在上次扫描中存在但在新扫描中不存在的发现结果(`close_old_findings_product_scope=true`)。
- 将真正新的发现结果**创建**为 Active。
- **不会重新激活**人类先前已抑制(inactive、误报、已接受风险)的发现结果,即使它们仍出现在扫描中(`do_not_reactivate=true`)。
这意味着在 DefectDojo 中作出的有意分类决策将在流水线运行中得以保留。
### 发现结果状态
上传后,ScopeGuardian 会从 DefectDojo 获取该 engagement 的所有发现结果,并为每个结果分配一个本地状态:
| 状态 | 条件 | 含义 |
|--------|-----------|---------|
| `ACTIVE` | `active=true`、`duplicate=false` | 待处理、已确认的发现结果 |
| `DUPLICATE` | `duplicate=true` | DefectDojo 的去重引擎识别出早期出现过的记录 |
| `INACTIVE` | `active=false` | 被人工抑制(误报、已接受风险、手动关闭) |
`--filter` 标志控制 CLI 输出以及通过 `-o` 写入的文件中显示的状态。默认情况下,仅显示 `ACTIVE` 发现结果。您可以使用例如 `--filter ACTIVE,DUPLICATE` 来扩展此范围。
安全门控(`--threshold`)始终**仅评估 `ACTIVE` 发现结果**,无论 `--filter` 的值如何设置。
### KICS 同步行为
KICS 扫描器将其 JSON 输出文件作为 `multipart/form-data` 请求上传到 DefectDojo。使用的端点在首次运行时为 `/api/v2/import-scan/`,在后续运行中为 `/api/v2/reimport-scan/`。每次上传都会设置以下选项:
| 选项 | 值 | 效 |
|--------|-------|--------|
| 扫描类型 | `KICS Scan` | 告知 DefectDojo 使用哪个解析器 |
| 严重性阈值 | `Info` | 导入所有严重级别的发现结果 |
| 分组依据 | `finding_title` | 合并具有相同标题的发现结果 |
| 创建发现组 | `true` | 将相关发现结果分组在一起 |
| 为发现结果应用标签 | `true` | 为每个发现结果打上 `IACST` 标签 |
| 关闭旧发现结果 | `true` | 新扫描中不存在的发现结果会被自动关闭 |
| 不重新激活 | `true` | 先前被抑制的发现结果在重新导入时不会被重新激活 |
| 分支标签 | `` | 将结果与被扫描的分支关联 |
### Grype 同步行为
Grype 扫描器将其 JSON 输出文件作为 `multipart/form-data` 请求上传到 DefectDojo。使用的端点在首次运行时为 `/api/v2/import-scan/`,在后续运行中为 `/api/v2/reimport-scan/`。每次上传都会设置以下选项:
| 选项 | 值 | 效果 |
|--------|-------|--------|
| 扫描类型 | `Anchore Grype` | 告知 DefectDojo 使用哪个解析器 |
| 严重性阈值 | `Info` | 导入所有严重级别的发现结果 |
| 分组依据 | `finding_title` | 合并具有相同标题的发现结果 |
| 创建发现组 | `true` | 将相关发现结果分组在一起 |
| 为发现结果应用标签 | `true` | 为每个发现结果打上 `SCA` 标签 |
| 关闭旧发现结果 | `true` | 新扫描中不存在的发现结果会被自动关闭 |
| 不重新激活 | `true` | 先前被抑制的发现结果在重新导入时不会被重新激活 |
| 分支标签 | `` | 将结果与被扫描的分支关联 |
### OpenGrep 同步行为
OpenGrep 扫描器将其 JSON 输出文件作为 `multipart/form-data` 请求上传到 DefectDojo。在上传之前,文件会被充实,以便每个结果包含 DefectDojo 的 Semgrep JSON Report 解析器所需的 `extra.severity` 字段(该值从 `extra.metadata.impact` 复制)。使用的端点在首次运行时为 `/api/v2/import-scan/`,在后续运行中为 `/api/v2/reimport-scan/`。每次上传都会设置以下选项:
| 选项 | 值 | 效果 |
|--------|-------|--------|
| 扫描类型 | `Semgrep JSON Report` | 告知 DefectDojo 使用哪个解析器 |
| 严重性阈值 | `Info` | 导入所有严重级别的发现结果 |
| 分组依据 | `finding_title` | 合并具有相同标题的发现结果 |
| 创建发现组 | `true` | 将相关发现结果分组在一起 |
| 为发现结果应用标签 | `true` | 为每个发现结果打上 `SAST` 标签 |
| 关闭旧发现结果 | `true` | 新扫描中不存在的发现结果会被自动关闭 |
| 不重新激活 | `true` | 先前被抑制的发现结果在重新导入时不会被重新激活 |
| 分支标签 | `` | 将结果与被扫描的分支关联 |
### 结合同步的安全门控
当同时设置 `--sync` 和 `--threshold` 时,门控将针对 **DefectDojo 去重后的发现结果**进行评估,而不是原始的本地扫描输出。这意味着:
- `INACTIVE` 发现结果(已抑制、误报、已接受风险)会被排除在计数之外,无论它们是否出现在最新的扫描中。
- `DUPLICATE` 发现结果不会使计数膨胀。
- 只有在经历了 DefectDojo 的去重和抑制逻辑后仍然存在的 `ACTIVE` 发现结果才会被计入。
## 安全门控的工作原理
当达到或超过配置的严重性级别的发现结果数量达到或超过配置的限制时,安全门控将使流水线失败(退出代码 `-1`)。
### 阈值语法
```
--threshold =[,=...]
```
支持的严重性值(不区分大小写):`critical`、`high`、`medium`、`low`、`info`。
```
# 出现任何严重发现时失败
--threshold critical=1
# 出现 1 个及以上严重或 5 个及以上高危发现时失败
--threshold critical=1,high=5
# 出现 10 个及以上中危及以上发现时失败
--threshold medium=10
```
### 评估逻辑
对于每个阈值规则:
1. 计算严重性**等于或高于**阈值严重性的发现结果数量。
严重性排序(从高到低):`CRITICAL` > `HIGH` > `MEDIUM` > `LOW` > `INFO`
2. 如果计数**≥ 配置的值**,门控**失败**并且进程以代码 `-1` 退出。
3. 所有阈值规则必须全部通过,门控才算通过。
### 发现结果来源
| 使用的标志 | 门控评估的发现结果 | 显示/写入的发现结果 |
|-----------|---------------------------|------------------------------|
| 仅 `--threshold` | 本地扫描中的 `ACTIVE` 发现结果 | 由 `--filter` 控制(默认:`ACTIVE`) |
| `--threshold` + `--sync` | 从 DefectDojo 获取的 `ACTIVE` 发现结果 | 由 `--filter` 控制(默认:`ACTIVE`) |
## 使用 Docker 运行
提供的 `Dockerfile` 构建了一个多阶段镜像,将 ScopeGuardian 与 KICS、OpenGrep、Grype 和 Syft 打包在一起。
```
# 构建镜像
docker build -t ScopeGuardian .
# 运行扫描
docker run --rm \
-v /path/to/your/project:/tmp/data/project \
-v /path/to/config.toml:/config.toml \
-e SCAN_DIR=/tmp/data \
-e DD_URL=http://host.docker.internal:8080 \
-e DD_ACCESS_TOKEN= \
ScopeGuardian \
--projectName my-service \
--branch main \
--sync \
/config.toml
```
在容器内部,`SCAN_DIR` 默认为 `/tmp/data`。
## 本地 DefectDojo 设置
提供了一个 `docker-compose.yml` 文件,用于启动一个由 PostgreSQL 和 Redis 支持的本地 DefectDojo 实例。
```
# 1. 配置凭证
cp .env.example .env
# 编辑 .env – 更改密码并设置一个强 DD_SECRET_KEY
# 2. 启动 DefectDojo(首次运行需要一分钟)
docker compose up -d
# 3. 在浏览器中打开 DefectDojo
open http://localhost:8080
# 4. 使用您在 .env 中设置的管理员凭证登录
# 默认:admin / changeme
# 5. 生成 API token
# Profile → API v2 Key → 复制 token
# 6. 在您的环境中设置 token
export DD_URL=http://localhost:8080
export DD_ACCESS_TOKEN=
```
### 创建 DefectDojo 产品
在运行带有 `--sync` 的 ScopeGuardian 之前,请在 DefectDojo 中创建一个**产品**,其名称需与您将在命令行中传递的 `--projectName` 值相匹配。
1. 在 DefectDojo 中,转到 **Products → Add Product**。
2. 将 **Name** 设置为您将作为 `--projectName` 传递的确切值(例如 `my-service`)。
3. 保存。ScopeGuardian 将自动管理该产品下的 engagement。
标签:Angular, ASTO, CI/CD安全, DefectDojo, DevSecOps, Docker, EVTX分析, FTP漏洞扫描, Go语言, GPT, GraphQL安全矩阵, Grype, KICS, Linux安全, Llama, LLM防护, LNA, OpenGrep, Python安全, SAST, SBOM分析, Svelte, Syft, 上游代理, 代码安全, 代码库扫描, 安全合规, 安全扫描器, 安全编排, 安全门禁, 安全防御评估, 日志审计, 漏洞枚举, 漏洞管理, 盲注攻击, 程序破解, 网络代理, 请求拦截, 静态应用安全测试