shahzaneer/Pre-CI-Security-Audit-Checks
GitHub: shahzaneer/Pre-CI-Security-Audit-Checks
一套基于 GitHub Actions 的可复用 CI 前安全审计流水线模板,集成了 SAST、SCA、恶意软件检测和包成熟度验证,作为部署前的安全门控机制。
Stars: 1 | Forks: 0
# CI 安全检查
一个可复用、多语言的 **CI 前安全审计** pipeline,作为部署前的门控机制。它执行四个并行安全扫描 — 供应链恶意软件检测、静态分析 (SAST)、软件成分分析 (SCA) 以及包成熟度验证 — 然后将结果聚合到一个统一的通过/失败仪表板中。
通过特定语言的 GitHub Actions 可复用工作流,同时支持 **JavaScript / Node.js 前端** 和 **Python 后端** 项目。
## 架构
```
workflow_call / push trigger
│
┌─────────────────┬───────────┼───────────┬─────────────────┐
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌──────────┐
│ depx │ │ sast │ │ sca │ │ age │ │ (repo) │
│ malware │ │ semgrep │ │ trivy │ │package │ │ inventory│
│ scan │ │ scan │ │ scan │ │maturity │ │ (lock) │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬─────┘
│ │ │ │ │
└────────────────┼────────────┼────────────┘──────────────┘
│ │
▼ ▼
┌──────────────────────┐
│ gate job │
│ aggregate + decide │
└──────────┬───────────┘
│
outputs: has_failures (boolean)
failure_count (integer)
```
所有四个扫描任务均**并行**运行。**门控任务**会等待所有任务完成 (`needs: [depx, sast, sca, age]`),下载它们的 Markdown artifact 部分,将它们连接成 `$GITHUB_STEP_SUMMARY` 中的单个仪表板,评估通过/失败状态,并将结果作为 workflow 输出呈现,供下游任务进行条件分支。
## 目录结构
```
ci-checks/
├── .vscode/
│ └── settings.json # VS Code Snyk integration
├── javascript-frontend/
│ ├── security_audit.yaml # Reusable workflow for Node.js / npm projects
│ └── verify_package_age.cjs # Standalone npm package-age verifier (Node.js)
└── python-backend/
├── security_audit.yaml # Reusable workflow for Python / PyPI projects
└── verify_package_age.py # Standalone PyPI package-age + hash verifier (Python)
```
## 关于误报
没有任何安全扫描器能达到 100% 的准确率。此 pipeline 中的工具都是各自领域中**顶级的免费开源方案** (depx、Semgrep、Trivy) — 它们被广泛采用、积极维护,并在无需付费许可证的情况下提供最高的信噪比。
为了防止误报阻碍开发,只有置信度最高的信号才会对部署进行门控。较低严重性或较早的发现将作为仪表板警告显示 — 可见且可操作,但不会阻断流程。
## 四道门控 — 为什么每个步骤都至关重要
### 1. 供应链恶意软件检测 (`depx`)
**工具**: ProjectDiscovery 的 [depx](https://github.com/projectdiscovery/depx)
**功能**: 提取 `package.json` 和 `requirements.txt` 中声明的所有依赖项,通过管道传递给 `depx`,后者将每个包与已知恶意包数据库进行交叉比对。匹配结果将被分类为 **recent** (发布 ≤ 30 天) 或 **known** (超过 30 天)。
| | |
|---|---|
| **数据源** | ProjectDiscovery 精选数据库 (社区报告、安全研究、registry 分析) |
- Recent 恶意包将**阻断** pipeline。
- Known 恶意包将作为**信息性警告**显示 (折叠在 `
` 块中)。
**必要性**: 包 registry 是供应链攻击的头号载体。攻击者会发布拼写错误抢注或依赖混淆的包,用于窃取机密、挖掘加密货币或注入后门。`depx` 可以在它们于您的 CI 环境中执行之前捕获它们 — 在最早可能的节点阻断攻击。
**失败条件**: 任何被标记为 **recent** 恶意包的依赖项 (`depx_recent > 0`)。
### 2. 静态应用安全测试 — SAST (`sast`)
**工具**: [Semgrep](https://semgrep.dev) (OSS 引擎)
**功能**: 对整个源代码树运行 `semgrep scan --config auto` (排除 `.github/` 和 Kubernetes 清单)。它可以检测已知会导致漏洞的模式 — 硬编码的机密、SQL 注入、路径遍历、不安全的反序列化等。发现结果按严重程度进行分类:
| | |
|---|---|
| **数据源** | Semgrep 社区规则库 (OWASP Top 10, CWE Top 25, 社区贡献) |
| 严重程度 | 影响 |
|----------|--------|
| `ERROR` | **阻断** pipeline |
| `WARNING` | 信息性 (仅显示在仪表板) |
| `INFO` | 信息性 (仅显示在仪表板) |
**必要性**: SAST 通过在开发期间、*在* 代码进入生产环境 *之前* 捕捉漏洞来实现安全左移。与 linter 不同,Semgrep 能理解安全语义 — 它不仅会标记“不良代码风格”,还会标记“此代码模式会导致 RCE”。将其作为 CI 门控运行可以防止已知的高危模式被部署。
**失败条件**: 一个或多个 `ERROR` 严重级别的 Semgrep 发现 (`sast_errors > 0`)。
### 3. 软件成分分析 — SCA (`sca`)
**工具**: Aqua Security 的 [Trivy](https://github.com/aquasecurity/trivy) v0.71.2
**功能**: 根据漏洞数据库扫描代码仓库中的所有第三方依赖项 (lockfile、vendored 包、site-packages)。识别具有已知 CVE 的包,并按严重程度进行评分:
| | |
|---|---|
| **数据源** | NVD, GHSA, GitLab Advisories, Red Hat, Debian, npm, PyPA, RubySec |
| 严重程度 | 影响 |
|----------|--------|
| `CRITICAL` / `HIGH` | **阻断** pipeline |
| `MEDIUM` / `LOW` | 信息性 (仅显示在仪表板) |
**必要性**: 即使您自己的代码是安全的,您的依赖项也未必安全。Log4Shell (CVE-2021-44228)、`event-stream` 事件以及 `xz` 后门都证明了传递依赖中的漏洞可能是灾难性的。SCA 回答了这个问题:*“我们正在发布已知存在漏洞的代码吗?”*
**失败条件**: 任何具有 `CRITICAL` 或 `HIGH` CVE 的依赖项 (`sca_critical > 0`)。
### 4. 包成熟度验证 (`age`)
**工具**: 自定义脚本 — `verify_package_age.cjs` (Node.js) 和 `verify_package_age.py` (Python),以及工作流中的内联 Python 代码。
| | |
|---|---|
| **数据源** | npm registry (`registry.npmjs.org`) / PyPI JSON API (`pypi.org/pypi`) — 权威的实时 registry 元数据 |
**功能**: 为每个锁定的依赖项查询 npm registry 或 PyPI,并检查其**发布日期**。任何发布少于 30 天的包都将被标记。
#### JavaScript (`verify_package_age.cjs`)
- 解析 `package-lock.json` (支持 v1、v2 和 v3 lockfile 格式)
- 查询 `https://registry.npmjs.org/`,对 scoped 包 (`@types%2Fnode`) 进行正确的 URL 编码
- 使用自定义的 `asyncPool` 辅助函数将并发限制为 25 个同时进行的 HTTP 请求
- 处理速率限制 (HTTP 429) 采用指数退避策略:[1秒, 3秒, 5秒, 10秒, 15秒]
- 输出 `.pkg-age-report.json` (结构化 JSON) 和 `.pkg-age-errors.json` (用于 Slack/webhook 集成)
- 在工作流中,该脚本会在 `node .github/workflows/verify_package_age.cjs` 被调用 — 这意味着需要将此文件复制到目标代码仓库的 `.github/workflows/` 目录中
#### Python (YAML 内联 + 独立的 `verify_package_age.py`)
- **工作流 (内联)**: Python 的 `security_audit.yaml` 完全以内联方式执行年龄检查 — 它读取 `requirements.txt`,通过带有 `ThreadPoolExecutor(max_workers=16)` 的 `urllib` 查询 PyPI 的 JSON API,并直接生成 Markdown 部分。无需外部文件调用。
- **独立脚本** (`verify_package_age.py`): 一个更丰富的实现,额外支持:
- 从 `requirements.txt` 解析 `--hash` 行
- **根据 PyPI 发布的摘要验证哈希值**,以检测篡改或镜像被入侵的情况
- 支持换行符 (`\`)、环境标记、extras 符号 (`psycopg[binary]`)
- 输出 `package_age_report.json` 和带样式的 `package_age_report.html`
**必要性**: 全新的包受到的社区审查要少得多。攻击者利用这一点发布恶意包,希望在这些包被报告和移除之前入侵项目。30 天的最短期限强制执行了一个“冷却期” — 如果一个包是供应链攻击,它很有可能在 30 天内被发现并下架。哈希验证 (Python 独立版) 增加了第二层保护:验证包的哈希值是否与 registry 的官方摘要匹配,可以防范镜像被入侵和中间人替换。
**失败条件**: 任何发布少于 30 天的包 (`age_failures > 0`)。
## 门控如何聚合结果
`gate` 任务在所有四个并行任务完成后运行。它会:
1. 下载所有四个 `*-section` artifact (来自 depx、sast、sca、age 的 Markdown 片段)。
2. 将它们拼接到 `$GITHUB_STEP_SUMMARY` 中,生成一个统一的仪表板。
3. 读取每个任务的数值输出:`depx_recent`、`sast_errors`、`sca_critical`、`age_failures`。
4. 将它们汇总为总的 `failure_count`。
5. 如果 `force_deploy` 为 `true`,则从总数中减去 `age_failures`,但仍会发出警告。
6. 将 `has_failures` (布尔值) 和 `failure_count` (整数) 设置为 workflow 输出。
下游的部署任务随后可以根据以下条件进行门控:
```
if: needs.security.outputs.has_failures == 'false'
```
## 工作流差异:JavaScript vs Python
| 方面 | JavaScript | Python |
|--------|-----------|--------|
| Push 触发分支 | `stager` | `security-audit-depx` |
| `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24` | 在所有 5 个任务上设置 | 未设置 |
| Semgrep `setup-python` 缓存 | 仅 `~/.cache/pip` | `cache: 'pip'` + `~/.cache/pip` |
| 年龄检查实现 | 调用外部 `node .github/workflows/verify_package_age.cjs`,然后由内联 Python 渲染 Markdown | 完全使用内联 Python — 无外部脚本 |
| 年龄检查并发 | 25 个请求 (Node.js `asyncPool`) | 16 个 workers (Python `ThreadPoolExecutor`) |
| `download-artifact` action | `@v7` | `@v8` |
| 年龄脚本输出 | `.pkg-age-report.json`, `.pkg-age-errors.json` | 直接输出 `age_section.md` |
尽管存在这些差异,两个工作流都会产生相同的输出 (`has_failures`、`failure_count`),并遵循相同的四道门控架构。
## 如何使用
### 从其他工作流调用 (推荐)
```
# 在你的 downstream repo 的 .github/workflows/deploy.yaml
jobs:
security:
name: Pre-CI Security Audit
uses: your-org/ci-checks/.github/workflows/javascript-frontend/security_audit.yaml@main
with:
repo_type: javascript # 'python', 'javascript', or 'both'
force_deploy: false # set to 'true' to bypass age check only
deploy:
needs: security
if: needs.security.outputs.has_failures == 'false'
runs-on: ubuntu-latest
steps:
- run: echo "All security checks passed — deploying!"
```
## 工作流输出
| 输出 | 类型 | 描述 |
|--------|------|-------------|
| `has_failures` | `boolean` | 如果有任何安全门控失败,则为 `true` (不包括强制部署的年龄检查) |
| `failure_count` | `integer` | 所有四项检查中失败项目的总数 |
## 工具与依赖
| 层级 | 工具 | 版本 | 用途 |
|-------|------|---------|---------|
| CI 运行时 | GitHub Actions | — | 流程编排 |
| 供应链 | [depx](https://github.com/projectdiscovery/depx) | latest | 恶意包检测 |
| SAST | [Semgrep](https://semgrep.dev) | latest (pip) | 静态漏洞扫描 |
| SCA | [Trivy](https://github.com/aquasecurity/trivy) | 0.71.2 | 已知 CVE 检测 |
| 年龄检查 — JS | Node.js | ≥ 22 | npm registry 查询 |
| 年龄检查 — Python | Python | ≥ 3.12 | PyPI registry 查询 |
| 缓存 | `actions/cache` | v6 | depx vendor 数据库、pip 缓存、Trivy 漏洞数据库 |
| Artifacts | `actions/upload-artifact@v7` / `actions/download-artifact@v7,v8` | v7 / v8 | 跨任务 Markdown + JSON 数据共享 |
| IDE | VS Code + Snyk extension | — | 本地开发者侧扫描 |
## 优势
- **安全左移**: 在漏洞进入生产环境之前 — 而不是之后 — 捕捉它们。
- **深度防御**: 四个独立、互不重叠的安全视角。供应链恶意软件、SAST、SCA 和包成熟度各自能捕捉不同类别的风险。
- **顶级的免费工具**: pipeline 中的每个工具都是开源、免费且被广泛采用的 — 没有供应商锁定,无需付费许可证,而且每一个都代表了其所在领域的黄金标准 (用于恶意软件检测的 depx、用于 SAST 的 Semgrep、用于 SCA 的 Trivy)。
- **感知误报的设计**: 该 pipeline 的构建基于没有扫描器是完美的这一理解。只有最高置信度的信号 (recent 恶意软件、ERROR 级别的 SAST、CRITICAL/HIGH CVE、小于 30 天的包) 才会阻断部署。较低严重性的发现将作为信息性的仪表板警告显示 — 可见且可操作,同时不会中止发布。
- **并行执行**: 所有四个扫描均并发运行,因此总的门控延迟等于*最慢*扫描的持续时间,而不是所有四个扫描时间的总和。
- **设计上即可复用**: 单个工作流文件可以从任意数量的下游代码仓库中调用 — 避免了复制粘贴造成的偏差。
- **紧急覆盖**: `force_deploy 标志允许您发布依赖于一个确实全新的包的热修复,而无需禁用其他三个安全门控。
- **丰富的统一报告**: 每次运行都会在 GitHub Actions 总结界面中生成一个人类可读的 Markdown 仪表板,包含文件路径、行号、CVE ID 和包版本 — 开发者可以一目了然地对发现进行分类,而无需在日志中搜寻。
- **独立验证**: 年龄检查脚本可以在本地或任何 CI 系统中运行,而不仅仅是在 GitHub Actions 中。
- **语言对等性**: JavaScript 和 Python 项目通过一个共享架构获得同等的保护,仅存在极少的、有详细文档说明的特定语言差异。
标签:DevSecOps, GitHub Actions, MITM代理, 上游代理, 自动笔记, 逆向工具, 错误基检测, 静态代码分析