关于 signet-eval
signet-eval 面向需要稳定控制 AI Agent 工具调用的用户,是一个本地策略引擎。它按顺序确定性地评估规则,不在授权路径中使用大模型,并将建议性提示与授权分开。
主要功能
- 01确定性 ALLOW、DENY、ASK、GATE 和 ENSURE 规则
- 02按顺序执行并采用首条匹配规则
- 03不可更改的自我保护规则
- 04支持 Claude Code、Codex、Cursor 等的钩子适配器
- 05可选的建议上下文注入
适用场景
- 01在执行前拦截破坏性 Shell 命令
- 02要求对指定工具输入进行确认
- 03保护策略文件和 signet-eval 二进制文件
- 04在规则中加入支出或凭据条件
MCP 相关
本地 stdio MCP 服务允许客户端管理策略。锁定规则不能通过该管理服务更改。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
signet-eval
为 AI Agent 工具调用提供确定性策略执行。集成的工具调用会先经过用户定义的规则,再执行。授权流程中不使用大模型。建议性提示与授权相互独立。
安装
# crates.io
cargo install signet-eval
# from source
git clone https://github.com/wandercom/signet-eval
cd signet-eval
cargo install --path .
signet-eval 没有 npm 或 PyPI 软件包。公开分发渠道为
crates.io 和从 GitHub 安装源码。MCP Registry 条目指向仓库元数据;运行时是本地的 signet-eval serve stdio
服务。
快速开始
以下命令钩子适配器仍受支持,且彼此独立。有关 Claude Code 可选的早期体验功能钩子,请参阅 function adapter。该功能默认禁用; 安装或构建此软件包不会启用它,也不会更改用户设置。
1. 接入 Claude Code — 添加到 ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "",
"hooks": [{"type": "command", "command": "signet-eval", "timeout": 2000}]
}]
}
}
对于 Codex,在 ~/.codex/config.toml 或 <repo>/.codex/config.toml 中启用钩子:
[features]
codex_hooks = true
然后添加 ~/.codex/hooks.json 或 <repo>/.codex/hooks.json:
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "signet-eval --adapter codex",
"timeout": 30000,
"statusMessage": "Checking Signet policy"
}]
}],
"PermissionRequest": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "signet-eval --adapter codex-permission",
"timeout": 30000,
"statusMessage": "Checking Signet approval policy"
}]
}]
}
}
对于 Antigravity,将此区块添加到 ~/.gemini/config/hooks.json(与现有钩子组进行合并):
{
"signet": {
"enabled": true,
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "/bin/bash -lc 'source ~/.profile >/dev/null 2>&1 || true; exec signet-eval --adapter antigravity'",
"timeout": 30,
"statusMessage": "Checking Signet policy"
}]
}]
}
}
相同的配置也可在 hooks/antigravity-hooks.json 中找到。
对于 Cursor,添加到 ~/.cursor/hooks.json(用户级)或 <repo>/.cursor/hooks.json
(项目级),并与任何现有事件合并:
{
"version": 1,
"hooks": {
"preToolUse": [{
"command": "/bin/bash -lc 'source ~/.profile >/dev/null 2>&1 || true; exec signet-eval --adapter cursor'",
"matcher": "^(?!Shell$|MCP:)",
"timeout": 30,
"failClosed": true
}],
"beforeShellExecution": [{
"command": "/bin/bash -lc 'source ~/.profile >/dev/null 2>&1 || true; exec signet-eval --adapter cursor'",
"timeout": 30,
"failClosed": true
}],
"beforeMCPExecution": [{
"command": "/bin/bash -lc 'source ~/.profile >/dev/null 2>&1 || true; exec signet-eval --adapter cursor'",
"timeout": 30,
"failClosed": true
}]
}
}
相同的配置也可在 hooks/cursor-hooks.json 中找到。
preToolUse 匹配器会跳过 shell 和 MCP 调用,因为有两个专用事件会评估它们;如果没有该匹配器,每个此类调用都会被评估和记录两次。
对于 Muse Code,将顶层 hooks 成员添加到 ~/.config/muse/settings.json
(用户范围;保留 schema_version 和其他设置),或者将文件保存为
<repo>/.muse/hooks.json(项目范围,Muse 仅对受信任的工作区启用该配置):
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{"type": "command", "command": "signet-eval --adapter muse"}]
}]
}
}
相同的配置也可在 hooks/muse-hooks.json 中找到。
2. 完成。 现在,每次工具调用都会经过策略评估。默认策略会阻止破坏性操作、保护自身配置,并允许其他所有操作。
3.(可选)自定义 — 通过 MCP 服务与 Claude 对话:
claude mcp add --scope user --transport stdio signet -- signet-eval serve
然后说:“为 amazon 订单添加 50 美元限额” 或 “阻止所有 rm 命令”。
默认策略
自我保护规则为锁定状态 — AI Agent 无法删除、编辑或重新排序这些规则,即使通过 MCP 管理服务也不行。这样可以防止 Agent 禁用自身的防护措施。
| 操作 | 决策 | 锁定 |
|---|---|---|
修改 .signet/ 的 Write/Edit/Bash | 拒绝 | 是 |
修改 signet-eval 二进制文件的 Write/Edit/Bash | 拒绝 | 是 |
Write/Edit settings.json / settings.local.json | 询问 | 是 |
Bash kill/pkill/killall + signet | 拒绝 | 是 |
| 近期没有 Kindex 标记/搜索/上下文时使用直接编辑工具 | 拒绝 | 是 |
Claude TaskCreate、TaskUpdate、TaskList、TaskGet、TodoWrite | 拒绝 | 是 |
rm、rmdir | 拒绝 | |
Cursor 原生 Delete 工具 | 拒绝 | |
git push --force | 询问 | |
目标所有者身份不匹配的 Git 远程和 gh 操作 | 拒绝 | |
mkfs、format、dd if= | 拒绝 | |
curl | sh、wget | sh | 拒绝 | |
| 含有字面 已遮蔽密钥 值的任何工具输入 | 询问 | |
| 其他所有操作 | 允许 |
自定义策略
signet-eval init # write default policy to ~/.signet/policy.yaml
signet-eval validate # check policy for errors
signet-eval rules # show current rules
编辑 ~/.signet/policy.yaml:
version: 1
default_action: ALLOW
rules:
- name: block_rm
tool_pattern: ".*"
conditions: ["contains(parameters, 'rm ')"]
action: DENY
reason: "File deletion blocked"
- name: books_limit
tool_pattern: ".*purchase.*"
conditions:
- "param_eq(category, 'books')"
- "spend_plus_amount_gt('books', amount, 200)"
action: DENY
reason: "Books spending limit ($200) exceeded"
- name: protect_my_config
tool_pattern: ".*"
conditions: ["contains(parameters, '/etc/')"]
action: ASK
locked: true
reason: "System config changes require confirmation"
规则按顺序评估 — 以第一个匹配项为准。同一规则中的多个条件采用 AND 逻辑。带有 locked: true 的规则无法通过 MCP 管理服务修改。
ENSURE 检查上下文
用户管理的 ENSURE 脚本会通过 stdin 接收原始的规范化工具名称和输入。SIGNET_TOOL_COMMAND 会将规范化后的 command 和 cmd 字段中第一个非空字符串作为字面环境变量值传递。如果这两个字段都没有值,则该变量为空。
转发机制不会将该值解释为 shell 代码。
SIGNET_TOOL_CWD 依次使用规范化工具的 cwd/workdir、主机信封中的 cwd,然后是钩子进程的工作目录。空值会被跳过。
SIGNET_TOOL_CWD_SOURCE 会指出使用的是哪一个值:tool_input、host 或 process。
SIGNET_HOST_CWD 单独携带主机信封中的 cwd;如果主机没有发送该值,则此变量为空。tool_input 目录是主机将运行命令的位置,但该目录由模型编写。进程回退目录并非经过独立验证的执行目录。这些值都不是可信的位置证明;如果检查要基于位置进行授权,应先查看来源,再依赖该值。
Signet 不会解释命令,因此命令中的 cd 不会改变 SIGNET_TOOL_CWD。所有这些环境变量都会覆盖继承的同名变量,stdin 保持不变。
如果检查脚本缺失或无法解析,调用将被拒绝,并说明对应检查的名称;用户规则和锁定规则均如此。Signet 不会安装检查脚本,因此请连同引用它的任何 rules.yaml 一起复制 ~/.signet/checks/。
旧版本曾附带 github_identity_guard 系统规则。若 policy.yaml 中仍留有该规则的副本,它会被忽略,而 signet-eval validate 会将其报告为警告。若要保留身份检查,请将该规则放入 rules.yaml,并使用自行维护的脚本。
在仓库根目录执行 cargo build --release 后,运行以下独立的本地验收检查:
SIGNET_EVAL_BINARY="$(pwd)/target/release/signet-eval" python3 tests/test_ensure_behavior.py
这些 Python 验收检查可在本地运行,也会由托管 CI 在 Linux 和 macOS 上针对发布二进制运行,同时还会执行 Rust 和打包检查。
建议注入
INJECT 规则会以概率性方式在触发规则的工具调用附近添加建议上下文。这些内容是提示,而非授权:常规的 ALLOW/DENY/ASK/GATE/ENSURE 检查仍会先执行,并遵循首个匹配规则生效的原则,且结果是确定的。注入在之后运行,并且仅当匹配的注入规则触发时才会输出上下文。
rules:
- name: maybe_remind_kindex_on_git
tool_pattern: "^Bash$"
conditions: ["contains(parameters, 'git ')"]
action: INJECT
inject:
trigger:
mode: exponential
peak: 0.35
cooldown_seconds: 300
peak_after_seconds: 1800
max_per_session: 3
payload:
text: "Before committing, check whether project `.kin` files should be included."
触发模式:
| 模式 | 行为 |
|---|---|
constant / step | 冷却后使用固定概率 |
linear | 在 peak_after_seconds 时间内从 0 逐渐升至 peak |
exponential | 通过指数衰减逐渐接近 peak |
载荷来源:
| 来源 | 说明 |
|---|---|
text | 内联字面文本 |
text_file | ~/.signet/injections/ 下的纯文件名 |
from_command | 来自 ~/.signet/inject_commands.yaml 的 HMAC 签名允许列表条目;直接执行,不使用 shell |
模板替换默认启用:{tool_name}、{cwd}、{date} 和 {matched_param.X}。请参阅 examples/inject_examples.yaml。
条件函数
| 函数 | 描述 | 示例 |
|---|---|---|
contains(parameters, 'X') | 工具输入包含字符串 | contains(parameters, 'rm ') |
any_of(parameters, 'X', 'Y') | 存在任意一个字符串 | any_of(parameters, 'mkfs', 'format') |
param_eq(field, 'value') | 字段等于指定值 | param_eq(category, 'books') |
param_ne(field, 'value') | 字段不等于指定值 | param_ne(role, 'admin') |
param_gt(field, N) | 字段 > 数字 | param_gt(amount, 100) |
param_lt(field, N) | 字段 < 数字 | param_lt(amount, 5) |
param_contains(field, 'X') | 字段包含子字符串 | param_contains(command, 'sudo') |
matches(field, 'regex') | 字段匹配正则表达式 | matches(file_path, '\\.env$') |
has_credential('name') | 凭据存在于保险库中 | has_credential('cc_visa') |
spend_gt('cat', N) | 会话支出 > 限额 | spend_gt('books', 200) |
spend_plus_amount_gt('cat', field, N) | 支出 + 此金额 > 限额 | spend_plus_amount_gt('books', amount, 200) |
not(condition) | 对条件取反 | not(param_eq(format, 'json')) |
or(A || B) | 满足任一条件 | or(contains(parameters, '-f') || contains(parameters, '--force')) |
has_recent_action('search', N) | 最近允许的操作在工具名称或详情中匹配;用竖线分隔表示 OR | has_recent_action('EnterPlanMode|TaskCreate', 500) |
has_current_session() | Hook 宿主提供了独立的聊天/会话标识符 | has_current_session() |
contains_masked_value(parameters) | 工具输入包含机密屏蔽参考文件中的字面值;也接受字段名称。未配置时永不匹配 | contains_masked_value(command) |
true / false | 字面值 | true |
按模型限定的规则
Hook 模式调用可以携带 agent_model 字段,用于标识发起工具调用的模型。只有宿主可以提供该字段:Signet 会读取 Hook 信封中的 model(字符串或带有 id 的对象)或 Antigravity 的 modelName,除此之外不会读取任何内容。
- 每次评估时,工具输入中的任何
agent_model都会被丢弃(包括所有 Hook 适配器、MCP 代理和 Kindex 任务准入),因此 Agent 无法自行声明模型。宿主仍会运行原始工具输入。 - Signet 从不从会话记录中读取模型。Agent 可以写入该文件,因此其中记录的模型只是声明,并非事实。
- Claude Code 的经典
PreToolUse输入没有模型字段,因此旧版 Claude Hook 永远不会看到agent_model。可选的函数适配器(adapters/claude-function)会在宿主提供时传递宿主的$.session.model()返回值。这是会话的模型;它是否指明子 Agent 自身的模型则未作限定。 - MCP 代理和 Kindex 准入调用没有宿主证明的模型,因此其中始终不存在该字段。
- 只有规则或当前生效的预检指定了
agent_model时,才会附加该字段。其他策略看到的工具输入与之前完全相同。
模型未知时,该字段不存在,并按空字符串进行比较。请将条件写成取反的允许列表,以便在关闭失败时拒绝未知模型:
- name: cloud_tools_require_opus
tool_pattern: "^Bash$"
conditions:
- "matches(command, '(^|[;&|(\\s])(gcloud|cloud-sql-proxy)(\\s|$)')"
- "not(matches(agent_model, '^claude-(opus|fable)-'))"
action: DENY
locked: true
reason: "gcloud and cloud-sql-proxy are limited to Opus and Fable"
在不报告模型的宿主上,此规则会拒绝所有匹配的调用。 命令匹配仍基于子字符串,因此,若 Agent 执意通过解释器或包装脚本调用同一二进制文件,仍可绕过此规则。
密钥遮蔽
可选。创建 ~/.signet/secret_mask.yaml(或 $SIGNET_DIR/secret_mask.yaml),
指定存放密钥的 shell 环境文件:
files: [~/.profile] # required; nothing is masked without files
keys: [OPENAI, "AWS_*_KEY"] # optional; default fragments below
min_length: 8 # optional; shorter values are ignored
没有该文件时,此功能处于关闭状态。~ 会展开为 $HOME;相对路径相对于 signet 目录。该文件位于 ~/.signet/ 下,因此自我保护规则会阻止 Agent 编辑它。
每个引用文件都按行读取:可选的 export ,然后是 NAME=VALUE,其中 NAME 必须匹配 [A-Za-z_][A-Za-z0-9_]*。匹配的首尾引号会被移除,未加引号的值末尾的 #comment 会被删除。包含 $ 或反引号的值、引号未闭合或引号拼接的值,以及短于 min_length 的值都会被跳过。
当键名包含任意一个 keys 条目(不区分大小写)时,该键会被选中,其中 * 可匹配任意长度的字符序列。因此,完整名称也会选中包含它的更长名称。如果未指定 keys,默认片段为 KEY、TOKEN、SECRET、PASSWORD、PASSWD、PASS、PW、CREDENTIAL、AUTH、PRIVATE、SID、DSN、WEBHOOK、COOKIE、CLIENT_ID、ACCOUNT_ID 和 API(src/secret_mask.rs 中的 DEFAULT_KEY_PATTERNS)。
选中值的每次出现,以及其 JSON 转义形式,都会替换为 $NAME。较长的值优先替换;长度相同时按文件中的顺序处理;多个名称共用同一个值时,会遮蔽第一个名称对应的值;同一名称定义两次时,两个值都会被遮蔽。文件会缓存,并在其 mtime 或大小发生变化时重新读取。文件缺失或不可读时,会在 stderr 上输出一条包含路径的警告,且不会遮蔽该文件中的任何内容;配置无效时,会输出一条警告并关闭遮蔽功能。
遮蔽适用于 signet-eval 持久化或显示的内容:操作账本、预检记录和违规项、Kindex 结果传递、Claude 函数适配器使用的 integration redact 清理器、MCP 管理回复(最近操作、状态、违规项、规则列表、测试结果)、MCP 代理原因、钩子拒绝/询问原因和建议上下文,以及 rules、test 和 log CLI 输出。它不会更改策略评估的输入,也不会重写主机运行的命令。包含遮蔽值的预检定义会被拒绝,与其他密钥一样。Vault 凭据会按原样存储。
命令可能确实需要字面值,因此默认规则 block_masked_secret_literal 在工具输入包含该值时会请求确认,而非拒绝。原因会指出键名($NAME),绝不会暴露值,并建议改用 $NAME。可像任何未锁定的默认规则一样,在 rules.yaml 中覆盖此规则。规则原因和替代方案可以使用 {masked_keys} 占位符,该占位符会展开为匹配到的键名。signet-eval status 会报告被遮蔽的键名和文件数量。
遮蔽可以避免 signet-eval 显示这些值,但无法阻止主机将工具输出发送给模型。因此,锁定规则 protect_secret_reference_files 会拒绝命令或路径中提及引用文件的任何工具调用:cat ~/.profile、grep KEY $HOME/.profile、对该文件使用 Read 工具、编辑或修补该文件,或命令在其目录中运行(或 cd 到该目录)时仅使用文件名。source FILE 和 . FILE 仍然允许,因为它们不会打印内容;请按名称使用已加载的变量。不会检查写入的内容、提示词和描述,因此文档可以提及该文件。匹配基于词法分析:不会拦截通配符、符号链接、Agent 外部创建的副本,或对父目录进行的递归搜索。未配置时绝不会匹配。
加密保险库
采用基于口令派生密钥层级的三层加密存储(Argon2id + AES-256-GCM):
| 层级 | 加密方式 | 内容 |
|---|---|---|
| 1 | 无 | 操作日志、支出账本 |
| 2 | 会话密钥 | 会话状态 |
| 3 | 分区密钥 | 信用卡号、API 令牌、机密 |
signet-eval setup # create vault with passphrase
signet-eval store cc_visa 4111... # store Tier 3 credential
signet-eval status # vault status and spending
signet-eval log # recent action log
signet-eval unlock # refresh session after timeout
凭据通过 request_capability 支持范围受限的访问:域名限制、用途约束、单次使用金额上限,以及使用一次后自动失效的一次性令牌。
支出限额使用保险库账本——每次会产生费用的工具调用都会被记录,spend_plus_amount_gt() 会在允许下一次购买前检查累计金额。
自我保护
signet-eval 内置锁定规则,防止 AI Agent 禁用自身的策略强制执行机制:
-
protect_signet_dir — 拒绝任何涉及
.signet/(策略文件、保险库、HMAC)的 Write、Edit 或 Bash 命令 -
protect_signet_binary — 拒绝篡改
signet-eval二进制文件 -
protect_hook_config — 修改
settings.json(配置钩子的位置)前必须获得用户确认 -
protect_signet_process — 拒绝针对 signet 进程的 kill/pkill/killall 命令
-
protect_preflight_storage — 拒绝 Agent 侧修改 preflight 记录
-
require_kindex_engagement_before_edits — 在记录持久化会话上下文之前,拒绝直接编辑工具
-
prefer_persistent_task_store — 拒绝使用上述五个 Claude 任务状态工具,并引导 Agent 使用持久化 Kindex 任务。
Agent、旧版子 AgentTask、TaskOutput和TaskStop并非任务状态工具,因此此规则不会拦截它们。 -
protect_checks_dir — 拒绝 Agent 侧替换受信任的 ENSURE 脚本
-
protect_vault_passphrase — 保险库的设置和解锁操作仅限人类执行
-
protect_signet_symlink — 拒绝针对受保护强制执行界面的符号链接绕过操作
-
protect_secret_reference_files — 拒绝读取
secret_mask.yaml中列出的环境文件;source仍可使用(参见 Secret masking)
这些规则具有以下特性:
- 锁定 — MCP 工具拒绝移除、编辑或重新排序这些规则
- 位置受保护 — 未锁定规则不能被重新排序到锁定规则之前(先匹配者优先)
- 默认值中硬编码 — 如果策略文件损坏或缺失,二进制文件会回退到包含自我保护规则的硬编码默认值
- 按版本协调 — 当前编译的默认值会按规则名称覆盖过时的系统策略快照,因此升级后无需重新运行
init - 保留名称协调 — 内置规则名称由二进制文件所有;主机专属系统规则必须使用不同名称,而用户规则仍是覆盖未锁定默认规则的受支持方式
- 由 HMAC 保护 — 直接编辑文件会破坏策略签名,从而触发安全默认值回退
MCP 管理服务
通过 Claude 以对话方式管理策略:
claude mcp add --scope user --transport stdio signet -- signet-eval serve
| 工具 | 用途 |
|---|---|
signet_list_rules | 显示所有规则及其锁定状态 |
signet_add_rule | 添加新规则(追加在锁定规则之后) |
signet_remove_rule | 移除规则(拒绝移除锁定规则) |
signet_edit_rule | 修改规则属性(拒绝修改锁定规则) |
signet_reorder_rule | 移动规则(拒绝移动锁定规则,并阻止将规则移到锁定规则之前) |
signet_set_limit | 为某个类别设置支出限额 |
signet_test | 根据当前策略测试工具调用 |
signet_validate | 检查策略是否存在错误 |
signet_condition_help | 显示可用的条件函数 |
signet_status | 保险库状态、支出总额和凭据数量 |
signet_recent_actions | 显示近期操作日志 |
signet_store_credential | 存储 Tier 3 凭据 |
signet_use_credential | 通过能力约束请求凭据 |
signet_list_credentials | 列出凭据名称 |
signet_delete_credential | 删除凭据 |
signet_sign_policy | 使用 HMAC 为策略文件签名 |
signet_reset_session | 清除支出计数器 |
保险库可用时,所有变更操作都会自动为策略签名。
MCP 代理
通过策略强制执行来包装上游 MCP 服务。Agent 连接到代理,而非直接连接到服务。每次调用时都会热重载策略。
# Configure upstream servers
cat > ~/.signet/proxy.yaml << 'YAML'
servers:
linear:
command: npx
args: ["-y", "mcp-linear"]
env:
LINEAR_API_KEY: "your-key"
YAML
# Register proxy with Claude Code
claude mcp add --scope user --transport stdio signet-proxy -- signet-eval proxy
所有命令
| 命令 | 用途 |
|---|---|
signet-eval | 钩子评估(默认,25ms) |
signet-eval --adapter codex | Codex PreToolUse 钩子评估 |
signet-eval --adapter codex-permission | Codex PermissionRequest 钩子评估 |
signet-eval --adapter antigravity | Antigravity PreToolUse 钩子评估 |
signet-eval --adapter cursor | Cursor preToolUse、beforeShellExecution、beforeMCPExecution 和 beforeReadFile 钩子评估 |
signet-eval --adapter muse | Muse Code PreToolUse(以及可选的 PermissionRequest)钩子评估 |
signet-eval integration describe | 从 JSON 标准输入中只读检查策略/能力 |
signet-eval integration adjudicate | 具有持久化意图回执的、受范围和修订版本约束的 Kindex 任务准入 |
signet-eval integration record-result | 幂等交付 Kindex 任务回执;不能证明任务已执行 |
signet-eval integration redact | 用于受控输出投影的结构化净化器 |
signet-eval integration install-modern | 显式安装二进制文件内嵌的 Claude 适配器并创建备份;保留禁用的强制执行状态 |
signet-eval init | 写入包含锁定自我保护规则的默认策略 |
signet-eval rules | 显示当前策略规则(标记锁定规则) |
signet-eval validate | 检查策略是否存在错误 |
signet-eval test '<json>' | 根据策略测试工具调用 |
signet-eval setup | 创建加密保险库 |
signet-eval unlock | 刷新保险库会话 |
signet-eval status | 保险库状态和支出 |
signet-eval store <name> <value> | 存储 Tier 3 凭据 |
signet-eval delete <name> | 删除凭据 |
signet-eval log | 近期操作日志 |
signet-eval reset-session | 清除支出计数器 |
signet-eval sign | 使用 HMAC 为策略文件签名 |
signet-eval injections | 显示近期触发的注入规则 |
signet-eval inject-test <rule> | 强制触发一条注入规则以进行测试 |
signet-eval serve | MCP 管理服务(17 个工具) |
signet-eval proxy | 用于上游服务的 MCP 代理 |
性能
| 指标 | 数值 |
|---|---|
| 钩子评估(端到端) | 25ms — 进程启动、标准输入、JSON 解析、策略加载、评估、响应 |
| 进程内策略评估 | 14–63μs — 拒绝 14μs、询问 21μs、支出检查 63μs |
| CLI 校验 / 规则 | 8ms |
| 二进制文件大小 | 6.2MB(已剥离符号,LTO) |
架构
signet-eval 是 Signet 个人主权 Agent 技术栈的强制执行层。核心原则:**授权层不能使用大模型。**它只处理结构化数据——正则表达式、比较和保险库查询。不涉及自然语言、上下文窗口或劝说空间。规则要么匹配,要么不匹配。
Agent proposes action -> signet-eval evaluates policy -> allow / deny / ask
(deterministic, 25ms, no NLP)
安全模型
signet-eval 是安全带,而不是牢笼。了解它能防范和不能防范的情况至关重要。
它能防范什么
遵循协议的 Agent 犯错。 Claude Code、Codex、Antigravity、Cursor 和 Muse Code 会遵循钩子协议——它们会在使用工具前后调用 signet-eval,并遵循受支持的响应。在这些协议范围内,signet-eval 能可靠地执行策略:
- 阻止破坏性命令(
rm、mkfs、管道传递的远程执行) - 在多次工具调用中执行开支限额
- 根据域名、用途和金额限制凭据访问
- 对敏感操作要求用户确认(
git push --force) - 通过锁定规则和 MCP 防护机制,防止随意篡改策略
这是常规运行模式。Agent 并非试图逃逸——它只是想提供帮助,而策略会确保它不越界。
Codex 支持有一项重要的语义差异:目前,PreToolUse 只能通过拒绝来执行策略,因此 Signet 的 ALLOW 不会输出任何内容,而 Signet 的 ASK 在此阶段会转换为拒绝。PermissionRequest 适配器会明确允许或拒绝 Codex 原本就要提交审批的请求;Signet 的 ASK 则交由 Codex 的常规审批提示处理。
Antigravity 会发送嵌套的 toolCall 负载,并期望收到顶层的 decision 响应。该适配器会将原生命令和文件工具规范化为 Signet 的标准策略名称,将 call_mcp_tool 解包为 mcp__<server>__<tool>,并在原生封装格式有误时默认拒绝。
Cursor 语义(已针对 cursor-agent 2026.10.01 验证):该适配器会读取
hook_event_name,并以 Cursor 的扁平 permission 对象作答。只有
beforeShellExecution 可以询问用户;在该事件中,Signet 的 ASK 会转为
Cursor 的审批提示。preToolUse 只能执行拒绝;尽管 Cursor 的文档列出了
ask,cursor-agent 会忽略 beforeMCPExecution 上的 ask;而
beforeReadFile 只接受 allow 或 deny,因此在这三个事件中,Signet 的 ASK 都会转为拒绝。
Cursor 的 Shell 工具映射为 Bash;beforeMCPExecution 映射为
mcp__<server>__<tool>,参数则从 Cursor 的 JSON 字符串中解码;preToolUse
看到的 MCP 调用只有 MCP:<tool>,不包含服务名,这就是示例将 MCP 路由到专用事件的原因。
beforeReadFile 会评估路径,而非 Cursor 附带的文件内容。
Signet 会明确输出 allow,因为 Cursor 会将空输出视为钩子失败。除非条目设置了
failClosed: true(如示例所示),否则钩子崩溃、超时或以非零状态退出时,Cursor 都会默认放行;
无论如何,无效的 JSON 都会阻止操作。Cursor 也会运行 Claude Code 中的钩子:
~/.claude/settings.json 和项目级 .claude/settings*.json。这些钩子收到的是
Cursor 负载,因此在其中直接运行 signet-eval Claude 钩子,会在 Cursor 中再次进行一次未经规范化的评估;
它不会与 Cursor 适配器去重,因为二者的命令不同。
Muse Code 语义(已针对 Muse 1.4.6 验证):Muse 会发送 Claude 格式的
PreToolUse 负载,但使用自己的工具名称。该适配器会将 shell(以及
bash、bash_input、exec_command、execute_command)映射为 Bash,
将 read_file/write_file/edit_file 映射为 Read/Write/Edit(并将原生
path 复制到 file_path),将 search 映射为 Grep,并将
web_fetch/web_search 映射为 WebFetch/WebSearch;MCP 工具传入时已经是
mcp__<server>__<tool>。Muse 的 PreToolUse 只能执行拒绝:它会忽略 ask,并拒绝单独的
allow,因此 Signet 的 ALLOW 不会输出任何内容(注入提示时除外,此时只输出
additionalContext),而 Signet 的 ASK 会转为拒绝。Muse 没有故障关闭钩子选项:钩子崩溃、超时或无法解析时,工具调用仍会继续。
在工作区受信任之前,.muse/hooks.json 中的项目钩子会保持禁用。Muse 不会运行 Claude Code 或 Codex 钩子。该适配器也接受
Codex 响应格式的 PermissionRequest(ASK 会交由 Muse 的提示处理),但示例中省略了它:在默认的
ALLOW 策略下,它会批准 Muse 原本要请求审批的每一次调用。
无法防护的情形
拥有 shell 访问权限的恶意 Agent。 signet-eval 与其约束的 Agent 处于相同的权限级别。任何具有相同 UID 的进程都可以:
- 使用 shell 间接调用绕过字符串匹配(
alias、eval、base64 -d | sh、python3 -c "...") - 以看似无害的名称,在受保护路径下创建符号链接
- 读取会话密钥文件并伪造 HMAC 签名
- 替换 signet-eval 二进制文件本身
- 修改
settings.json中的钩子配置 - 使用
LD_PRELOAD、ptrace或进程注入
再多的模式匹配也无法解决这个问题。为 ln 添加规则,就会出现 alias。屏蔽 alias,就会出现 eval。屏蔽 eval,就会出现 python3 -c。这是一个无限倒退的问题——根本原因在于权限级别,而不是模式覆盖范围。
真正的隔离措施
要防护真正具有恶意的 Agent,需要使用高于 Agent 权限级别运行的操作系统级控制措施:
- 独立用户账户——以受限用户身份运行 Agent,使其无法写入策略文件
- 容器/沙盒——使用 Docker、Flatpak 或
bubblewrap,并将~/.signet/以只读方式挂载 - 精简的
$PATH——从 Agent 的环境中移除ln、alias和其他间接调用工具 - seccomp / AppArmor / SELinux——Agent 无法覆盖的强制访问控制
- 虚拟环境——在受限环境中运行 Claude Code,并限制其文件系统访问权限
在这样的配置中,signet-eval 是策略层。它通过清晰、可审计的规则,处理“应允许此 Agent 执行哪些操作”这一问题。操作系统负责处理“此 Agent 能否绕过策略”这一问题。二者互不可替代。
纵深防御
各层协同工作:
| 层级 | 防护对象 | 机制 |
|---|---|---|
| 字符串匹配 | 明显的错误、清晰的用户体验 | 正则表达式、子字符串、单词边界条件 |
| 锁定规则 | 随意篡改基于 MCP 的策略 | 不可变规则、位置保护 |
| HMAC 签名 | 带外文件修改 | 加密完整性验证 |
| 操作系统控制 | 权限提升、shell 间接调用 | 沙盒、RBAC、独立用户 |
没有操作系统控制措施时,signet-eval 只是一个减速带,而不是一道墙。有了这些措施,它就是安全边界内的策略引擎。
许可证
MIT
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。