LeightonSec/security-gate

GitHub: LeightonSec/security-gate

一款面向 Python 安全与 AI 项目的静态安全门工具,在代码交付前拦截数据泄露、供应链风险、加密缺陷及模型来源问题,并以强制阻断机制确保严重发现必须被解决。

Stars: 0 | Forks: 0

# security-gate 在编写第一行 [threat-classifier](https://github.com/LeightonSec/threat-classifier) 代码之前,一次手动审计发现了被原封不动转发给 Anthropic API 的攻击者提示词,一个会在仓库目录结构改变时静默加载错误位置的 `sys.path.insert('../ai-firewall')`,以及回退到 `"changeme"` 的生产环境凭证。跨越四个仓库的六处违规——没有一处被现有工具捕获。 security-gate 将该审计中可重复的部分自动化了。它执行的每一条规则都是最初由人工发现的。构建 AI 产品的团队在交付时,通常会带着他们尚未映射的信任边界、来源未知的训练数据,以及将攻击者控制的字符串传递给外部 API 的推理流水线。扫描器会在这些违规行为进入生产环境之前将其捕获。而安全门则会强制要求对它无法自动处理的问题做出决策。 ## 安全门理念 Linter 只会告诉你哪里出了问题。而安全门会强迫你在继续之前做出决定。 每一项发现都对应一个检查清单条目。扫描器会自动关闭它能够验证的部分。任何需要人工判断的事项——信任边界图、模型来源、离线推理确认——在人工签字确认前都会保持开启状态。任何项目都不能带着未解决的 CRITICAL(严重)或 HIGH(高)级别发现进入下一阶段。 这就是安全扫描器和安全门之间的区别:安全门是有约束力的。 ## 每个安全门防范的威胁 | 安全门 | 旨在防范的威胁 | |------|-----------------------------| | `outbound_calls` | 攻击者提示词被泄露给外部 API;数据在没有记录决策的情况下离开您的信任边界 | | `path_manipulation` | 隐式的仓库耦合,在目录结构改变时会静默加载错误的代码 | | `unpinned_deps` | 通过包注册表投毒引发的依赖项混淆和供应链攻击 | | `hardcoded_secrets` | 提交到版本历史中的凭证,或硬编码的回退凭证在生产环境中发生故障开放 | | `retention_policy` | 无限期累积威胁数据、网络抓包或攻击者载荷且没有清理路径 | | `missing_validation` | 攻击者控制的输入在没有 schema 合约的情况下到达业务逻辑 | | `ai_ml` | 未固定版本的模型下载、通过 `trust_remote_code` 执行任意代码、将训练数据发送给第三方的遥测 | | `web_app` | 生产环境中的 Debug 模式、SQL 注入、CORS 通配符、未经身份验证即改变状态的 endpoint | | `security_tool` | 测试夹具包含真实的攻击载荷或路径遍历字符串——仅限合成夹具 | | `crypto` | 加密路径中使用弱 CSPRNG、未经验证的 GCM 信封、未定义盐值的 HKDF、丢弃身份验证失败的静默 catch 块、时间不安全的密钥比较、日志中的敏感密钥材料 | ## 捕获了实时 H2 发现的加密扫描器 v1.2 增加了一个支持 TypeScript 的加密扫描器,它是基于对后量子消息协议的 Gate 0 手动审计构建的。在任何修复代码编写之前,该扫描器针对实时代码库运行了一次。它报告了横跨七个文件的六个生产环境发现: ``` crypto 6 findings HIGH src/crypto/hybrid.ts:30 CRYPTO-02 createCipheriv without setAAD — GCM envelope fields unauthenticated HIGH src/ruflo/context.ts:17 CRYPTO-01 Math.random() used for session ID — not a CSPRNG HIGH src/ruflo/taskqueue.ts:53 CRYPTO-01 Math.random() used for task ID — not a CSPRNG MEDIUM src/guardian/client.ts:108 CRYPTO-04 silent catch — decryption failure swallowed with no log MEDIUM src/ruflo/orchestrator.ts CRYPTO-04 silent catch — decrypt error swallowed with no log LOW src/crypto/hybrid.ts:70 CRYPTO-03 hkdf with undefined salt ``` H2 发现是关键所在。在没有调用 `setAAD` 的情况下使用 `createCipheriv('aes-256-gcm', ...)`,意味着外部信封字段——发送者身份、接收者身份、时间戳——完全没有经过身份验证。拦截密文的攻击者可以交换接收者的公钥或重放时间戳,从而绕过 30 秒的重放窗口。GCM 身份验证标签仅覆盖了密文主体,而没有覆盖其周围的传输格式。 修复方案:`setAAD` 现在在加密和解密时都会绑定 `version || type || sender_pk || recipient_pk || timestamp || mlkem_ciphertext`(1644 字节)。扫描器规则确认了该修复:重新扫描后 CRYPTO-02 已清除。 两处 `CRYPTO-04` 发现是在生产环境的 catch 块中找到的——一个在 orchestrator 的解密路径中,另一个在 guardian 的链查找中。它们都在静默返回 `null`。在手动调查解密失败之前,这两处问题都不会暴露出来。 扫描器中的每一条规则都是首先基于一个真实的发现编写的。 ## DAST:发现真实 Bug 的扫描器 v1.3 增加了一个运行时扫描器(`--url`),它会在静态分析的同时对实时服务发起测试。在对 `llm-honeypot`(同一作品集中构建的一个 Flask 蜜罐)进行的第一次实际运行中,它报告了: ``` dast 1 findings HIGH /chat detection_rate LLM threat detection rate 92.3% is below 95% threshold ``` 该发现是真实的。13 个探测载荷中有一个带着 `risk_level: LOW` 混过了分类器。根本原因:关键字模式 `r"ignore previous instructions"` 无法匹配 `"Ignore your previous instructions"`——单词 "your" 破坏了匹配。修复方案仅仅是正则表达式修改了一个字符。重新扫描返回了 13/13 并且显示 `dast clean`。 扫描器在与之相伴构建的系统中发现了一个真实的漏洞。这正是该工具的用途所在。 ## 不在范围内——以及原因 security-gate 是刻意收窄范围的。它涵盖了 SAST 和直接依赖项 SCA。它不尝试进行运行时沙盒隔离、传递依赖分析、容器扫描或模糊测试。这些每一项都是独立的学科,有更好的专用工具。 `# gate: ignore` 抑制模式的存在是因为扫描器并不总是正确的。当某个发现属于误报时,该抑制要求在代码行内提供记录在案的理由——不是一揽子排除,也不是配置文件,而是在调用点用一句话解释为什么这个特定的实例是安全的: ``` tokenizer = DistilBertTokenizerFast.from_pretrained(str(path)) # gate: ignore — local MODEL_PATH, not HuggingFace hub ``` TypeScript 文件使用 `//` 注释语法——两种形式都被接受: ``` const id = `${Date.now()}-${Math.random().toString(36).slice(2)}` // gate: ignore — test helper only, not a crypto path ``` 这个决定存在于代码中,也就是未来的读者会去寻找它的地方。 ## 用法 ``` # SAST scan security-gate scan /path/to/repo # SAST + DAST(需要运行 service) security-gate scan /path/to/repo --url http://localhost:5001 # 保存报告到磁盘 security-gate scan /path/to/repo --save # 用于 CI 的 JSON 输出 security-gate scan /path/to/repo --output json --save ``` ## 安全门逻辑 - **GATE BLOCKED(拦截)** ——任何 CRITICAL 或 HIGH 发现。不得继续。 - **GATE PASSED(通过)** ——零 CRITICAL/HIGH。MEDIUM 及以下级别需要审查,但不会拦截。 ## 扫描器 | 扫描器 | 检测内容 | 严重程度 | |---------|---------|----------| | `outbound_calls` | HTTP 调用、Anthropic/OpenAI SDK、boto3 | HIGH | | `path_manipulation` | `sys.path.insert/append` | HIGH | | `unpinned_deps` | 缺失版本锁定或哈希 | HIGH/MEDIUM | | `sca` | 锁定的 PyPI 依赖项中已知的 CVE——单次 OSV.dev 批量查询,通过 CVE 别名对 GHSA/PYSEC 进行去重,显示修复版本;涵盖 requirements.txt 和 pyproject.toml | CRITICAL/HIGH/MEDIUM/LOW | | `hardcoded_secrets` | 不安全的 `getenv()` 回退、内联密钥分配 | CRITICAL/HIGH | | `retention_policy` | 没有 TTL/清理逻辑的数据库写入和文件追加 | MEDIUM | | `missing_validation` | 攻击者控制的输入在没有 schema 合约的情况下到达逻辑(识别调用内部或周围的 Pydantic 模型/验证器;手动防护条件不会自动清除) | HIGH/CRITICAL | | `pickle_usage` | 对非字面量输入使用 `pickle.load`/`loads`,`pickle.Unpickler`——在不受信任的反序列化时发生 RCE | CRITICAL | | `missing_timeout` | 没有 `timeout=` 的 `requests`/`urllib` 出站调用(挂起/DoS 风险) | MEDIUM | | `ai_ml` | 没有使用 `revision=` 的 `from_pretrained()`、`trust_remote_code=True`、宽松的 HF 遥测 | CRITICAL/HIGH/MEDIUM | | `web_app` | Debug 模式、SQL 注入、CORS 通配符、未经身份验证的路由 | CRITICAL/HIGH/MEDIUM | | `security_tool` | 测试夹具中的路径遍历和注入载荷字符串 | MEDIUM | | `crypto` | 加密路径中的 Math.random、没有 setAAD 的 GCM、未定义盐值的 HKDF、加密上下文中的静默 catch、时间不安全的密钥比较、日志中的密钥材料 | HIGH/MEDIUM/LOW | | `dast` | 运行时:标头、Debug 模式、堆栈跟踪泄漏、LLM 检出率、模型产物泄漏 | CRITICAL/HIGH/MEDIUM/INFO | ## DAST 检查(需要 `--url`) | 检查 | 测试内容 | 失败时的发现 | |-------|--------------|-------------------| | DAST-1 | 存在安全标头(`X-Content-Type-Options`、`X-Frame-Options`、`CSP`) | MEDIUM | | DAST-2 | Debug 模式已关闭(探测 `/dast-debug-probe` 以寻找 Werkzeug 标记) | CRITICAL | | DAST-3 | API 响应中未暴露堆栈跟踪 | HIGH | | DAST-4 | LLM 检测流水线激活,且对威胁探测的检出率 ≥95% | HIGH(如果未启用 `DAST_MODE` 则为 INFO) | | DAST-5 | 响应中没有模型产物泄漏(`logits`、`token_ids`、`hidden_states`) | HIGH | 要启用 DAST-4 评估,请在启动目标服务时设置 `DAST_MODE=true`。该服务必须在其响应中返回 `risk_level` 和 `classification` 字段。 ## 需要人工签字确认 每个安全门报告都包含扫描器无法自动验证的项目: - [ ] 信任边界映射完成并已审查 - [ ] 已评估对抗性输入路径 - [ ] HF 模型来源已验证(锁定 SHA 校验和) - [ ] 离线推理已确认(网络抓包验证) - [ ] 数据保留策略已定义并实施 - [ ] 测试夹具已确认为合成的(没有真实的 IOC/IP/载荷) ## CI 集成 ``` - name: Run security-gate run: security-gate scan . --output json --save ``` 遇到 CRITICAL/HIGH 时返回退出代码 1——在问题解决之前拦截合并。 ### 它对自己也进行拦截 security-gate 在 CI 中以与其他人相同的约束力对自己的仓库运行: ``` run: security-gate scan . --exclude tests ``` 没有 `--no-exit-code` 这样的逃生舱——产品源码或依赖清单中的任何 CRITICAL 或 HIGH 都会拦截构建。`tests/` 被排除在外,因为 `tests/fixtures/` 故意包含了恶意模式以验证扫描器是否会触发;拦截它们将毫无意义。源码自身的匹配(扫描器自己的正则表达式字符串和文档字符串示例)通过内联 `# gate: ignore - ` 进行抑制,而剩下那几个被接受的发现在 `accepted-findings.toml` 中记录了其理由和审查者。该安全门对自己也坚持了同等的标准。 ## 框架对齐 与 OWASP SAMM v2.0、NIST SSDF SP 800-218 v1.1、NIST SSDF AI Profile SP 800-218A 以及 OWASP Top 10 2025 对齐。 它检测到的每一次违规在成为测试夹具之前都是真实的。 *LeightonSec —— 安全工程,逐门构建。*
标签:AI安全, Chat Copilot, CI/CD安全门禁, DNS 反向解析, Python, SAST, StruQ, 文档安全, 无后门, 盲注攻击, 逆向工具, 静态代码扫描