theMiddleBlue/vibegate

GitHub: theMiddleBlue/vibegate

VibeGate 是一个 AI 编码助手的写入前安全钩子,通过基于 Semgrep 的确定性静态分析,在文件落盘前拦截和警告代码中的注入等安全漏洞。

Stars: 6 | Forks: 0

VibeGate logo

CI License: MIT

AI 编码工具的安全检查点。它会检查 AI 助手写入的每一个文件,并在危险文件落盘前拦截它们。

## 这能解决什么问题? AI 编码助手(Claude Code、Codex 等)写代码很快——包括处理密码、 电子邮件、API 密钥或原始用户输入的代码。助手很容易将这些数据 直接连接到数据库查询、shell 命令或 HTTP 响应中, 而不考虑安全性。 VibeGate 位于助手和你的文件系统之间。每次助手尝试写入或编辑文件时, VibeGate 都会先扫描新代码: - **查找**代码中用户可控的输入(使用 [Semgrep](https://semgrep.dev)) - **分析**它是什么类型的数据(电子邮件?密码?API 密钥?) 以及它要去哪里(数据库查询?shell 命令?HTTP 响应?) - **警告或拦截**,取决于该组合的风险程度 分析过程不涉及任何 LLM——这是快速、确定性的静态分析, 因此它绝不会胡编乱造,也不会消耗你的 token。 以下是 VibeGate 目前检查的所有内容: | 检查项 | 捕获内容 | 结果 | |---|---|---| | 命令注入 | 未经过滤的输入到达 shell 命令 | 拦截 | | SQL 注入 | 未经过滤的输入到达数据库查询 | 拦截 | | NoSQL 注入 | 请求体被直接用作数据库过滤器 | 拦截 | | 模板注入 (SSTI) | 模板源本身(不仅是其数据)来自用户输入 | 拦截 | | 不安全的反序列化 | 不受信任的数据到达不安全的反序列化器(pickle、不安全的 YAML 等) | 拦截 | | 路径穿越 | 未经过滤的输入到达文件读取、写入或删除操作 | 拦截 | | XXE | 不受信任的 XML 在启用外部实体的情况下被解析 | 拦截 | | XSS | 未经过滤的输入被渲染为原始 HTML | 拦截 | | 无限制文件上传 | 上传文件自身的名称被用于构建保存路径 | 拦截 | | SSRF | 服务器获取了非硬编码的 URL | 警告 | | 开放重定向 | 非硬编码的重定向目标 | 警告 | | 批量赋值 | 整个请求体被传递给模型构造函数或更新操作 | 警告 | | 请求体中的敏感数据 | 从请求体中读取的电子邮件、密码、token 等 | 警告 | | URL/查询中的敏感数据 | 从查询字符串中读取的电子邮件、密码、token 等 | 警告 | | Headers 中的敏感数据 | 从请求 headers 中读取的电子邮件、密码、token 等 | 警告 | | 来自用户输入的文件路径 | 使用变量(而非硬编码字符串)作为文件路径 | 警告 | | CLI 参数 | 数据来自命令行参数 | 警告 | | 标准输入 | 数据来自 stdin | 警告 | | 环境变量 | 数据来自环境变量 | 警告 | | 未锁定的 GitHub Action | 工作流使用可变标签(`@v4`)而非 commit SHA | 警告 | | 不安全的 `pull_request_target` | 工作流使用了 `pull_request_target` 触发器 | 警告 | | 凭证日志记录 | 密码、API 密钥或 token 被传递给 `print`/`console.log`/日志记录器 | 警告 | | 硬编码的密钥 | 类似密钥名称的变量被赋予了看起来真实的字面值 | 警告 | 完整的当前列表存在于 `guidance.TECHNICAL_RISKS` 和 `formatter.BLOCKING_CATEGORIES` 中,以防此表发生偏差。 ## 开启后会怎样 ``` ┌───────────────────────────────┐ │ You ask Claude Code to │ │ write or edit a file │ └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ Claude Code tries to save │ │ the file (Write/Edit tool) │ └───────────────┬───────────────┘ │ ▼ ┌───────────────────────────────┐ │ VibeGate hook │ │ (runs automatically, │ │ before the file is saved) │ └───────────────┬───────────────┘ │ scans the new code with Semgrep │ ┌─────────────────────┼─────────────────────┐ │ │ │ ▼ ▼ ▼ ┌────────────────────┐ ┌────────────────────┐ ┌──────────────────────┐ │ No risky input │ │ Risky input, │ │ Risky input reaches │ │ found │ │ but lower risk │ │ a critical sink │ │ │ │ (e.g. shown in │ │ (SQL/command/RCE, │ │ │ │ an HTTP reply) │ │ template injection) │ └─────────┬──────────┘ └─────────┬──────────┘ └───────────┬──────────┘ │ │ │ ▼ ▼ ▼ File is saved, File is saved, File is NOT saved. nothing shown. plus a warning in Claude Code sees the terminal with the block reason risk + how to fix it. and is told what to fix. ``` 简而言之:安全的代码会原封不动地通过,有风险但可存活的代码在保存时会附带警告, 而距离 SQL 注入、命令注入或远程代码执行仅一步之遥的代码,将在落盘前被停止。 如果 VibeGate 本身遇到意外错误,它总是允许写入——hook 中的 bug 绝不应成为你工作被阻塞的原因。 每个警告和拦截还带有明确的指令,告诉 Claude Code 在回复中向你提及该发现, 而不是仅仅默默修复它。这就是让 VibeGate 的活动在对话中可见的原因,而不仅仅是你需要去翻找的终端日志。 | VibeGate 看到什么 | 会发生什么 | |---|---| | 没有用户输入,或者是它尚不支持的语言 | 文件正常保存,不显示任何内容 | | 发现用户输入,但风险较低(例如开放重定向、批量赋值) | 文件保存,终端显示警告 + 指导 | | 用户输入未经过滤直接流向关键的 sink(SQL/NoSQL 查询、shell 命令、模板引擎、反序列化器、XML 解析器、文件路径、上传文件名或原始 HTML 输出) | 文件**未保存**——Claude Code 会被告知原因 | 有关拦截与仅警告的完整逐项检查明细,请参阅上方的 [“这能解决什么问题?”](#what-problem-does-this-solve)中的表格。 今天 VibeGate 支持 **Python**、**JavaScript/TypeScript**、**Go**、 **Java**、**PHP** 和 **Ruby**,并接入了 **Claude Code** 和 **Codex**。无需改动核心逻辑即可添加更多的语言和工具。 它还会检查 GitHub Actions 工作流文件是否存在两种常见的 CI/CD 供应链错误:action 被锁定到可变标签(`@v4`)而非 commit SHA,以及 不安全的 `pull_request_target` 触发器。两者都会发出警告而不是拦截, 因为它们属于安全加固检查,而非证明存在主动漏洞利用。 ## 实际应用 这是一段真实的录像:Claude Code 在开启了 VibeGate 的情况下, 从零开始构建一个 RSS 订阅阅读器应用。请注意 Claude Code 停下来并明确说明 VibeGate 标记了什么以及为什么标记,然后才继续操作的时刻——包括它在获取订阅源代码中修复的一个真实的 SSRF 风险。

这是第二个示例的静态图片:Claude Code 正在构建一个 允许人们上传照片并查看其详细信息的应用。VibeGate 注意到 文件名和其他文件细节稍后将会显示在屏幕上,并 警告这可能会被用于向页面注入恶意代码(这被称为 XSS)。Claude Code 调整了代码,以便安全地显示该信息。

VibeGate warning Claude Code about an XSS risk from uploaded file details, and Claude Code fixing it

在这两个案例中,没有任何事情被无故拦截,也无需任何人逐行阅读 代码来捕捉问题。VibeGate 在文件写入的那一刻就捕获了它, 而 AI 立即对其进行了修复。 ## 为什么一个 gate 比加载 skill 消耗的 token 更少 有两种方法可以让 AI 助手编写更安全的代码。一种方法是 在对话开始前,将一大套关于安全编码的指令加载到对话中, 例如涵盖 SQL 注入、XSS、密码处理、文件上传等的检查清单。 另一种方法就是 VibeGate 的做法:在写入文件时自动检查代码, 只有在出现问题时才发声。 第一种方法无论是否需要,都会在每条消息上消耗 token。 一份涵盖多种风险类别的典型安全编码检查清单很容易增加 数千个 token。如果 AI 助手在一个会话中编写了 50 个文件, 并且每次都重新加载或将该检查清单保留在上下文中, 你可能要为超过十万个提供建议的 token 买单,而在大多数情况下, 这些建议并不适用于当前正在写入的文件。登录页面和简单的 颜色常量文件不需要相同的警告,但加载的检查清单无法提前区分它们。 VibeGate 扭转了这一点。对于每一个没有风险模式的文件,它保持沉默, 不产生任何额外成本。只有当它发现问题时, 比如用户输入流向数据库查询,它才会针对该问题添加一条简短、 具体的提示,其大小通常只是完整检查清单的一小部分。 因此,你无需为每个文件支付固定的 token 成本,只需在确实需要关注的文件上支付少量成本, 而且该成本精确地指向所发现的问题,而不是关于安全的泛泛说教。 这也使得指导更加可靠。被要求在编写百行代码时“牢记安全性”的 AI 助手, 可能只是忽略了众多代码行中的一行风险代码。 而 gate 不会疲劳或分心:它每次都会使用相同的固定规则检查每一次写入。 ## 入门指南 安装一次——这也会拉取 VibeGate 依赖的 Semgrep: ``` pipx install git+https://github.com/theMiddleBlue/vibegate ``` 然后在你想保护的任何项目中将其开启: ``` cd your-project vibegate on # turn on here (reload Claude Code afterwards) vibegate status # check whether it's on for this project vibegate off # turn off here ``` `vibegate on` 会在该项目的 `.claude/settings.local.json` 中为 `Write|Edit|MultiEdit` 添加一个 `PreToolUse` hook。它是按项目划分的, 因此在一个 repo 中开启它不会影响任何其他项目。 Claude Code 将该 hook 作为 `vibegate run --host claude_code` 运行——不涉及绝对路径,因此即使你重新安装或移动文件,它也能继续工作。 `vibegate status` 还会显示 VibeGate 在该项目中实际捕获内容的运行日志——每个警告和拦截,包括文件、行和类别——这样你就可以随着时间推移观察它的活动,而不仅仅是知道它是否已开启: ``` $ vibegate status █ █ █████ ████ █████ ████ ███ █████ █████ ... ● VibeGate is ENABLED in .claude/settings.local.json Recent activity (last 2 of 2 recorded, most recent first): 2026-07-02T17:35:48+00:00 ⛔ BLOCKED server.py:3 EXEC_INPUT (FREE_TEXT) 2026-07-02T17:35:46+00:00 ⚠ WARNED app.py:2 HTTP_BODY (EMAIL) ``` 此日志位于项目根目录的 `.vibegate/activity.jsonl` 中——请将其添加到 `.gitignore`,它是本地开发者状态,不应该提交。 VibeGate 按照以下顺序判断它在与哪个宿主通信:显式的 `--host ` 标志,然后是 `VIBEGATE_HOST` 环境变量,接着是从传入的 payload 中自动检测,最后回退到 `claude_code`。 ## 抑制发现 如果 VibeGate 标记了你已明确认定是安全的操作,请在同一行添加 `vibegate-ignore` 注释——它适用于任何注释语法(`#`、`//` 等),因为 VibeGate 只是查找该文本: ``` query = f"SELECT * FROM users WHERE id = {user_id}" # vibegate-ignore ``` 如果只想抑制特定类别而不是该行上的所有内容, 请在冒号后列出它们(匹配技术类别或语义类型,以逗号分隔,不区分大小写): ``` query = f"SELECT * FROM users WHERE id = {user_id}" # vibegate-ignore: DB_QUERY ``` ## 代码组织结构 ``` src/vibegate/ ├── hook.py # entry point ├── cli.py # on/off/status commands + the ASCII banner ├── activity_log.py # persists warnings/blocks to .vibegate/activity.jsonl ├── colors.py # shared ANSI color codes (report + CLI banner) ├── core.py # the host-agnostic pipeline ├── models.py # InputEvent / ClassifiedFinding / AnalysisResult ├── semgrep_runner.py # runs Semgrep as a subprocess (fail-safe) ├── classifier.py # maps Semgrep rule → category, variable name → data type ├── guidance.py # the static risk/remediation write-ups ├── formatter.py # turns results into a terminal report + host context ├── adapters/ # base, claude_code, codex + a small registry └── rules/ # Semgrep rules — one file per language (Python, JS/TS, # Go, Java, PHP, Ruby) plus a generic placeholder ``` 流水线本身(`core.py`)从不直接与特定的宿主通信——所有特定于宿主的输入/输出都存在于 `adapters/` 中,因此添加新的宿主无需修改分析逻辑。 ## 运行测试 ``` semgrep --validate --config src/vibegate/rules/ # check the rules are valid pytest tests/ # unit + integration tests ``` 要在不使用 Claude Code 的情况下查看其端到端运行效果: ``` python3 -c 'import json; print(json.dumps({"tool_name":"Write","tool_input":{"file_path":"/tmp/t.py","new_content":"email = request.json.get(\"email\")"}}))' \ | python3 src/vibegate/hook.py --host claude_code ``` ## 扩展 VibeGate - **添加一种语言**——添加 `rules/-user-input.yaml`,在 `classifier.RULE_TO_TECHNICAL` 中注册新的规则 ID,并在 `core.EXT_TO_LANGUAGE` 中映射文件扩展名。 - **添加要识别的新数据类型**——在 `classifier.VARNAME_TO_SEMANTIC` 中添加关键字,并在 `guidance.SEMANTIC_GUIDANCE` 中撰写说明。 - **添加要检测的新 sink**(例如数据被滥用的新方式)——添加一条 Semgrep 规则、一条 `RULE_TO_TECHNICAL` 中的条目和一张 `guidance.TECHNICAL_RISKS` 中的卡片。 - **添加新的宿主工具**——在 `adapters/` 下添加一个适配器,并在 `adapters/__init__.py` 中注册它。 ## 需要了解的信息 - `codex` 适配器是一个早期的、尽力而为的映射。在依赖它来拦截任何内容之前,请仔细检查其事件契约是否与你的 Codex 版本相匹配。 - Semgrep 免费版返回 `"requires login"` 而不是实际匹配的行,因此分类器会自行根据行号从文件内容中重建代码片段。 - 对于 `Edit`/`MultiEdit`,`claude_code` 适配器会从磁盘重建编辑后的完整文件,以便由*独立的*编辑引入的受污染源和 sink 仍然能被关联起来——但仅会报告该编辑实际触及的行上的发现。如果 sink 已经存在,而后来的编辑仅添加了到达该 sink 的受污染源,则不会被捕获(sink 所在的行不属于新编辑的一部分)。这种重建是 Claude Code 特有的;`codex` 适配器尚未实现此功能。
标签:AI编程助手, Blue Team, IPv6支持, Semgrep, WordPress安全扫描, 代码安全, 命令注入, 安全网关, 漏洞枚举, 逆向工具, 错误基检测, 防注入, 静态代码分析