Vigils v0.2.0-beta.1
Pre-releaseFirst public beta. Vigil grows from "MCP gateway only" into a local data-flow control
plane:vigil-hub hookextends secret protection to an agent CLI's native tool calls
(Bash / Edit / …), covering Claude Code + Codex + Gemini + Cursor — not just MCP servers.
We're shipping it as a beta to gather real-world feedback: runvigil-hub setup, try the
postures, and tell us anything surprising. Bug reports welcome.
⚠️ Behavior change (BREAKING for defaults)
- Default install surface is now the hook.
vigil-hub setup(no flags) registers the
agent-CLI hook by default (Claude as the primary surface, plus any detected Codex / Gemini /
Cursor) instead of MCP wrapping. MCP wrap is demoted to the explicitsetup --mcp(its
code and behavior are fully preserved — use it when you only want to protect an MCP tool
flow).setup --allstill does both in one step. - Default posture is Low. A
secret://placeholder reaching a native tool is allowed
at Low (α1 used to always deny). Three tiers: Low (deny only the highest risk — bare
hard-fingerprint secrets — plus a reserved ledger-tamper tier whose detection isn't wired
yet) / Medium (+ placeholder ask) / High
(= the old enforce, deny everything). A bare real credential is denied in every tier (a
non-negotiable floor). Switch withvigil-hub posture set|show. - A hook
askis now co-approval. At Medium, a placeholder's ask enters Vigil's approval
queue with a bounded wait; both Vigil (desktop / CLI) and the tool chain's own UI can
approve — first approver wins (atomic state-machine arbitration), and it falls back to the
tool-chain prompt on timeout. The MCP-wrap approval-queue behavior is unchanged.
Added
- Multi-agent hook adapter (
hook.rs): a normalization layer that maps event and field
names across Claude / Codex / Gemini / Cursor, then routes the response per CLI (Claude
deny= exit 2 + stderr; Codex / Gemini / Cursor = exit 0 + each one's JSON contract). A
bare secret is denied on any tool (includingmcp__*) — the single defense-in-depth line. - Multi-agent hook registration (
setup_hooks.rs): Codex ($CODEX_HOME/hooks.json),
Gemini (~/.gemini/settings.json), and Cursor surfaces, each idempotent, with--uninstall
removing only Vigil's own entries. If Codexconfig.tomlhas[features] hooks = false,
setup warns and never rewrites it. The Claude surface is completed (PreToolUse +
PostToolUse + timeout). vigil-hub posture show|set <low|medium|high>: a turnkey entry to the three tiers
(atomic config write + an audit event for every change).- Execution-boundary injection (α2): on PreToolUse, a
secret://<alias>placeholder inside
a boundary tool (Bash / shell) is resolved to its real value via a lease and rewritten
inline intoupdatedInputfor the host to execute — the model transcript only ever
sees the placeholder. Claude only (the CLI proven to honorupdatedInput). Real values
never reach audit / stderr / notes (sha256 fingerprints only). - PostToolUse result re-redaction: before a boundary tool's result returns to the LLM, the
real values of declared secrets are reverse-substituted back tosecret://<alias>(plus a
hard-fingerprint scrub as defense-in-depth), via Claude'supdatedToolOutput. A declared
secret that can't be resolved, or any residue found on self-check, triggers a fail-closed
truncation.
Security invariants
- Fail-closed by construction: the hook never returns an error or panics; a parse failure,
an injection failure, a re-redaction failure, or a missing ledger all converge to
deny-or-truncate (denyis exit 2 — exit 1 is fail-open and is never used to block). - Zero plaintext: a real value is exposed at a single point and flows straight to its
injection target / re-redaction substitution; audit, reasons, notes, and stderr only ever
carry the alias name + a sha256. Byte-level E2E confirms real values never hit disk.
Known scope limitations (this beta)
- Re-redaction covers only a boundary tool's direct result; it does not track a secret's
second-order propagation (a boundary command writes to disk → a non-boundary tool reads
it back). Full coverage needs egress-side (model-API proxy) interception. - inject / re-redact use the OS keyring as the value backend, but keyring population has no
turnkey CLI entry yet (the next increment); injection currently requires registering the
hook command with--inject --secretsby hand. - A full real-machine dual-CLI (Claude Code + Codex live) inject / re-redact round-trip is
still pending a controlled environment; the binary layer and unit tests already cover every
decision and protocol shape.
Also in this release — bug fixes
- DEF-004: the firewall's project boundary now actually binds —
--project-rootflag,
defaulting to the gateway's working directory. Found in real-machine testing.- The bug: every production entrypoint (
serve/wrap/ demo / desktop embed) started
the firewall with an empty set of project roots, and the policy engine'sOutside
condition is vacuously true on an empty set — so the built-indeny-outside-projectrule
(priority 150) treated the entire filesystem as "outside the project", while its
counterpartapprove-repo-write(priority 80) could never match. The Inside/Outside
boundary semantics were inverted wholesale: any call recognized as a filesystem write was
hard-denied in every posture (monitor only downgrades the default-deny floor, not
explicit Deny rules), with an audit reason that falsely claimed "writes OUTSIDE project".
It went unnoticed for so long because most wrapped third-party tool names aren't in the
effect-extraction vocabulary — no FsWrite extracted, rule never fired, calls fell to the
floor and were observe-allowed under monitor. - Fail-safe guard in the policy engine: with empty roots,
Outsideno longer asserts
"outside the project" (it doesn't match), so writes fall to the default-deny floor —
still fail-closed, and the audit reason is now the honest "no rule matched" instead of a
fabricated boundary violation. The risk scorer follows the same semantics (no more +30
"outside-project write" score on empty roots), and its root matching is now
case-insensitive on Windows, aligned with the policy engine. serve/wrapaccept a repeatable--project-root <DIR>; omitted, the boundary
defaults to the process working directory (agents launch the gateway inside the project,
matching git/cargo directory semantics). Roots are normalized to the same POSIX form the
path extractor emits (canonicalized,\→/,\\?\prefix stripped) — without this,
prefix comparison on Windows silently never matches and the boundary is inert.- Visible change under enforce: writes inside the boundary now route to the
approve-repo-writeapproval queue (previously hard-denied); writes outside are still
blocked bydeny-outside-project, with the reason pointing at a real boundary violation. - The startup banner prints the bound boundary (
project boundary -> <roots>/NONE),
so a gateway spawned from the wrong directory is visible at a glance. - SDK
FirewallBuilder::project_rootsnormalizes roots inbuild()the same way, so
native-form paths (C:\proj) from consumers compare correctly. - demo / desktop embed intentionally keep empty roots (self-contained simulation / no
meaningful CWD for a GUI); the engine guard covers them. Adversarially reviewed.
- The bug: every production entrypoint (
中文
首个公开测试版。 Vigil 从"仅 MCP 网关"成长为本地数据流控制平面:
vigil-hub hook
把 secret 防护扩到 agent CLI 的原生工具调用(Bash / Edit / …),覆盖
Claude Code + Codex + Gemini + Cursor —— 不再局限于 MCP server。我们以 beta 形式发布以收集
真实反馈:跑vigil-hub setup、试试三档姿态,把任何意外告诉我们。欢迎提 bug。
⚠️ 行为变更(影响默认行为)
- 默认安装面现在是 hook。
vigil-hub setup(无 flag)默认注册 agent CLI hook(Claude 为
主面,外加检测到的 Codex / Gemini / Cursor),不再默认 MCP wrap。MCP wrap 降级为显式
setup --mcp(代码与行为完全保留 —— 只想保护 MCP 工具流时用它)。setup --all仍一步两者全做。 - 默认姿态为 Low。 到达原生工具的
secret://占位符在 Low 档放行(α1 时是恒 deny)。
三档:Low(仅拦最高风险 —— 裸硬指纹 secret;账本篡改档位已在决策表预留但检测尚未接线)/
Medium(+ 占位符 ask)/
High(= 旧 enforce,全量 deny)。裸真凭据在任何档位恒 deny(不可降级的硬底线)。
用vigil-hub posture set|show切换。 - hook 的
ask现在是共同批准。 Medium 档下,占位符的 ask 进入 Vigil 审批队列有界等待;
Vigil(desktop / CLI)与工具链自身 UI 两边都能批准 —— 先批者生效(审批状态机原子仲裁),
超时回退工具链提示。MCP wrap 的审批队列行为不变。
新增
- 多 agent hook adapter(
hook.rs):归一层,把事件名与字段名跨 Claude / Codex / Gemini /
Cursor 归一,再按 CLI 分流响应(Claudedeny= exit 2 + stderr;Codex / Gemini / Cursor =
exit 0 + 各自 JSON 契约)。裸 secret 在任何工具(含mcp__*)恒 deny —— 唯一的纵深防御线。 - 多 agent hook 注册(
setup_hooks.rs):Codex($CODEX_HOME/hooks.json)、Gemini
(~/.gemini/settings.json)、Cursor 各面,均幂等,--uninstall仅删 Vigil 自有 entry。若
Codexconfig.toml含[features] hooks = false,setup 仅警告、绝不改写。Claude 面完整化
(PreToolUse + PostToolUse + timeout)。 vigil-hub posture show|set <low|medium|high>:三档姿态的 turnkey 入口(原子写配置 +
每次变更一条审计事件)。- 执行边界注入(α2):PreToolUse 时,边界工具(Bash / shell)内的
secret://<alias>占位符
经 lease 授权解析为真值,内联重写进updatedInput交宿主执行 —— 模型 transcript 始终只见
占位符。仅 Claude(实证支持updatedInput)。真值绝不进审计 / stderr / note(仅 sha256 指纹)。 - PostToolUse 结果再脱敏:边界工具结果回 LLM 前,声明 secret 的真值经逆向替换回
secret://<alias>(+ 硬指纹 scrub 作纵深防御),经 ClaudeupdatedToolOutput改写。声明的
secret 无法解析、或自检发现残留 → fail-closed 裁剪。
安全不变量
- fail-closed by construction:hook 永不返错或 panic;解析失败、注入失败、再脱敏失败、缺
ledger 一律收敛为 deny 或裁剪(deny走 exit 2 —— exit 1 是 fail-open,绝不用作拦截)。 - 零明文:真值仅在单点暴露,直达注入目的地 / 再脱敏替换;审计、reason、note、stderr 全程
只含 alias 名 + sha256。字节级 E2E 验证真值不落盘。
已知范围边界(本测试版)
- 再脱敏仅覆盖边界工具的直接结果;不追踪 secret 的二次传播(边界命令落盘 → 非边界
工具读出)。完整覆盖需 egress 侧(模型 API 代理)拦截。 - inject / re-redact 走 OS keyring 真值后端,但 keyring 填充尚无 turnkey CLI 入口(下一增量);
注入当前需手动用--inject --secrets注册 hook 命令。 - 完整真机双 CLI(Claude Code + Codex 实跑)inject / re-redact 往返 E2E 待受控环境验证;
二进制层与单测已覆盖全部决策与协议形状。
本版同时包含 —— bug 修复
- DEF-004:firewall 项目边界从未真正生效 —— 新增
--project-rootflag,缺省为网关工作目录。
真机测试中发现。- bug 本体:所有生产入口(
serve/wrap/ demo / 桌面 embed)启动 firewall 时项目根
集合为空,而 policy 引擎的Outside条件对空集合恒判 true —— 内置规则
deny-outside-project(priority 150)把整个文件系统判成"项目外",对称的
approve-repo-write(priority 80)永不匹配,Inside/Outside 边界语义整体反转:凡被识别为
文件写的调用在所有姿态被硬 deny(monitor 只降级 default-deny floor,不降级显式
Deny 规则),且审计 reason 谎报"writes OUTSIDE project"。长期未暴露是因为多数被 wrap 的
第三方工具名不在 effect 提取词表内 —— 提取不出 FsWrite,规则根本不触发,调用落 floor
被 monitor 观察放行。 - policy 引擎 fail-safe 守门:空 roots 时
Outside不再断言"项目外"(返不匹配),写操作
落 default-deny floor —— 仍 fail-closed,且 reason 诚实为 "no rule matched" 而非伪造的
越界。风险评分器同语义(空 roots 不再 +30 "越界写"评分),其根匹配在 Windows 下补齐
大小写不敏感,与 policy 引擎对齐。 serve/wrap新增可重复的--project-root <DIR>;省略时缺省 = 进程工作目录
(agent 在项目目录里启动网关,与 git/cargo 的目录语义一致)。根按路径提取器同款 POSIX
形式归一(canonicalize、\→/、剥\\?\前缀)—— 否则 Windows 下前缀比较静默不
匹配,边界形同虚设。- enforce 姿态下的可见变更:边界内的写操作现在走
approve-repo-write审批通道
(此前被硬 deny);边界外的写仍被deny-outside-project拦截,reason 如实指向真实
越界路径。 - 启动 banner 打印绑定的边界根(
project boundary -> <roots>/NONE),从错误目录
spawn 的网关一眼可见。 - SDK
FirewallBuilder::project_roots在build()时同样归一,消费者传C:\proj原生
形式也能正确前缀比较。 - demo / 桌面 embed 有意保持空 roots(自包含模拟 / GUI 无工作目录语义),由引擎守门
兜底。已通过对抗式审查。
- bug 本体:所有生产入口(
Downloads — which file do I want?
- Desktop app (most users): the installer for your OS — Windows
Vigils_*_x64-setup.exe(or.msi), macOSVigils_*.dmg, Linux.AppImage/.deb/.rpm. Gives you the GUI: Activity Feed, Approval Queue, Server Registry. - CLI gateway (put Vigils in front of an AI agent — Claude Code / Codex / Cursor / Zed):
vigils-cli-<platform>(containsvigil-hub+vigil-native-host). This is the MCP proxy your agent connects to. - Browser extension (guard pasting/typing secrets into AI web apps, Chrome MV3):
vigils-chrome-extension.zip— unzip, then load unpacked atchrome://extensions. - The
.sigandVigils.app.tar.gzfiles are desktop auto-updater artifacts — you do not need to download them.
New here? Full setup & agent-integration guide: https://duncatzat.github.io/vigils
Early releases are unsigned; your OS may show a Gatekeeper / SmartScreen prompt on first run.
Apache-2.0 · https://vigils.ai · Full changelog