@aimarket/warden 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +113 -0
- package/README-es.md +27 -11
- package/README-fr.md +27 -11
- package/README-ru.md +27 -11
- package/README-zh.md +27 -11
- package/README.md +27 -11
- package/dist/annotation-review.d.ts +10 -0
- package/dist/annotation-review.d.ts.map +1 -0
- package/dist/annotation-review.js +36 -0
- package/dist/annotation-review.js.map +1 -0
- package/dist/atomic-screen.d.ts +61 -0
- package/dist/atomic-screen.d.ts.map +1 -0
- package/dist/atomic-screen.js +165 -0
- package/dist/atomic-screen.js.map +1 -0
- package/dist/flow-guard-answer.d.ts +29 -0
- package/dist/flow-guard-answer.d.ts.map +1 -0
- package/dist/flow-guard-answer.js +201 -0
- package/dist/flow-guard-answer.js.map +1 -0
- package/dist/flow-guard-classify.d.ts +14 -0
- package/dist/flow-guard-classify.d.ts.map +1 -0
- package/dist/flow-guard-classify.js +224 -0
- package/dist/flow-guard-classify.js.map +1 -0
- package/dist/flow-guard-decode.d.ts +17 -0
- package/dist/flow-guard-decode.d.ts.map +1 -0
- package/dist/flow-guard-decode.js +149 -0
- package/dist/flow-guard-decode.js.map +1 -0
- package/dist/flow-guard-destinations.d.ts +23 -0
- package/dist/flow-guard-destinations.d.ts.map +1 -0
- package/dist/flow-guard-destinations.js +158 -0
- package/dist/flow-guard-destinations.js.map +1 -0
- package/dist/flow-guard-fingerprints.d.ts +27 -0
- package/dist/flow-guard-fingerprints.d.ts.map +1 -0
- package/dist/flow-guard-fingerprints.js +164 -0
- package/dist/flow-guard-fingerprints.js.map +1 -0
- package/dist/flow-guard-fragments.d.ts +17 -0
- package/dist/flow-guard-fragments.d.ts.map +1 -0
- package/dist/flow-guard-fragments.js +122 -0
- package/dist/flow-guard-fragments.js.map +1 -0
- package/dist/flow-guard-instructions.d.ts +12 -0
- package/dist/flow-guard-instructions.d.ts.map +1 -0
- package/dist/flow-guard-instructions.js +36 -0
- package/dist/flow-guard-instructions.js.map +1 -0
- package/dist/flow-guard-labels.d.ts +10 -0
- package/dist/flow-guard-labels.d.ts.map +1 -0
- package/dist/flow-guard-labels.js +55 -0
- package/dist/flow-guard-labels.js.map +1 -0
- package/dist/flow-guard-leak.d.ts +29 -0
- package/dist/flow-guard-leak.d.ts.map +1 -0
- package/dist/flow-guard-leak.js +135 -0
- package/dist/flow-guard-leak.js.map +1 -0
- package/dist/flow-guard-paths.d.ts +21 -0
- package/dist/flow-guard-paths.d.ts.map +1 -0
- package/dist/flow-guard-paths.js +72 -0
- package/dist/flow-guard-paths.js.map +1 -0
- package/dist/flow-guard-policy.d.ts +15 -0
- package/dist/flow-guard-policy.d.ts.map +1 -0
- package/dist/flow-guard-policy.js +179 -0
- package/dist/flow-guard-policy.js.map +1 -0
- package/dist/flow-guard-post.d.ts +6 -0
- package/dist/flow-guard-post.d.ts.map +1 -0
- package/dist/flow-guard-post.js +131 -0
- package/dist/flow-guard-post.js.map +1 -0
- package/dist/flow-guard-pre.d.ts +13 -0
- package/dist/flow-guard-pre.d.ts.map +1 -0
- package/dist/flow-guard-pre.js +335 -0
- package/dist/flow-guard-pre.js.map +1 -0
- package/dist/flow-guard-screen.d.ts +85 -0
- package/dist/flow-guard-screen.d.ts.map +1 -0
- package/dist/flow-guard-screen.js +558 -0
- package/dist/flow-guard-screen.js.map +1 -0
- package/dist/flow-guard-state.d.ts +12 -0
- package/dist/flow-guard-state.d.ts.map +1 -0
- package/dist/flow-guard-state.js +121 -0
- package/dist/flow-guard-state.js.map +1 -0
- package/dist/flow-guard-text.d.ts +9 -0
- package/dist/flow-guard-text.d.ts.map +1 -0
- package/dist/flow-guard-text.js +46 -0
- package/dist/flow-guard-text.js.map +1 -0
- package/dist/flow-guard-types.d.ts +154 -0
- package/dist/flow-guard-types.d.ts.map +1 -0
- package/dist/flow-guard-types.js +2 -0
- package/dist/flow-guard-types.js.map +1 -0
- package/dist/flow-guard.d.ts +20 -215
- package/dist/flow-guard.d.ts.map +1 -1
- package/dist/flow-guard.js +26 -1400
- package/dist/flow-guard.js.map +1 -1
- package/dist/mcp-client.d.ts +1 -2
- package/dist/mcp-client.d.ts.map +1 -1
- package/dist/mcp-client.js +2 -19
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp-server.js +5 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +46 -9
- package/dist/mcp-tools.js.map +1 -1
- package/dist/public-address.d.ts +3 -0
- package/dist/public-address.d.ts.map +1 -0
- package/dist/public-address.js +58 -0
- package/dist/public-address.js.map +1 -0
- package/dist/review-diff.d.ts +36 -0
- package/dist/review-diff.d.ts.map +1 -1
- package/dist/review-diff.js +177 -7
- package/dist/review-diff.js.map +1 -1
- package/dist/task-adapter.d.ts +73 -0
- package/dist/task-adapter.d.ts.map +1 -0
- package/dist/task-adapter.js +379 -0
- package/dist/task-adapter.js.map +1 -0
- package/dist/task-cli.d.ts +3 -0
- package/dist/task-cli.d.ts.map +1 -0
- package/dist/task-cli.js +162 -0
- package/dist/task-cli.js.map +1 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wrap-cli.d.ts +5 -0
- package/dist/wrap-cli.d.ts.map +1 -1
- package/dist/wrap-cli.js +29 -2
- package/dist/wrap-cli.js.map +1 -1
- package/dist/wrap-screen.d.ts +3 -0
- package/dist/wrap-screen.d.ts.map +1 -0
- package/dist/wrap-screen.js +20 -0
- package/dist/wrap-screen.js.map +1 -0
- package/dist/wrap-state.d.ts +8 -1
- package/dist/wrap-state.d.ts.map +1 -1
- package/dist/wrap-state.js +21 -4
- package/dist/wrap-state.js.map +1 -1
- package/dist/wrap.d.ts.map +1 -1
- package/dist/wrap.js +204 -10
- package/dist/wrap.js.map +1 -1
- package/docs/flow-guard-model-comparison-2026-10-25.es.md +82 -0
- package/docs/flow-guard-model-comparison-2026-10-25.fr.md +82 -0
- package/docs/flow-guard-model-comparison-2026-10-25.md +82 -0
- package/docs/flow-guard-model-comparison-2026-10-25.ru.md +82 -0
- package/docs/flow-guard-model-comparison-2026-10-25.zh.md +82 -0
- package/docs/flow-guard.es.md +307 -12
- package/docs/flow-guard.fr.md +307 -12
- package/docs/flow-guard.md +315 -13
- package/docs/flow-guard.ru.md +314 -13
- package/docs/flow-guard.zh.md +306 -8
- package/docs/scan-limits.es.md +39 -0
- package/docs/scan-limits.fr.md +39 -0
- package/docs/scan-limits.md +39 -0
- package/docs/scan-limits.ru.md +39 -0
- package/docs/scan-limits.zh.md +39 -0
- package/docs/scan.es.md +2 -0
- package/docs/scan.fr.md +2 -0
- package/docs/scan.md +2 -0
- package/docs/scan.ru.md +2 -0
- package/docs/scan.zh.md +2 -0
- package/docs/security-hardening.es.md +1 -1
- package/docs/security-hardening.fr.md +1 -1
- package/docs/security-hardening.md +1 -1
- package/docs/security-hardening.ru.md +1 -1
- package/docs/security-hardening.zh.md +1 -1
- package/examples/atomic-screen-server.mjs +42 -0
- package/native/macos/Package.swift +9 -0
- package/native/macos/Sources/WardenDesktop/NativeSurface.swift +246 -0
- package/native/macos/Sources/WardenDesktop/StandardIO.swift +139 -0
- package/native/macos/Sources/WardenDesktopHost/Main.swift +211 -0
- package/package.json +18 -6
- package/scripts/build-macos.mjs +22 -0
package/README-zh.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
|
|
9
9
|
<a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm 版本" /></a>
|
|
10
10
|
<img src="docs/badges/deps.svg" alt="零运行时依赖" />
|
|
11
|
-
<img src="docs/badges/tests.svg" alt="
|
|
11
|
+
<img src="docs/badges/tests.svg" alt="1256 项测试通过" />
|
|
12
12
|
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
13
13
|
<a href="LICENSE"><img src="docs/badges/license.svg" alt="许可证:MIT" /></a>
|
|
14
14
|
</p>
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
**检查你机器上的所有 MCP 服务器。** 无需账号、无需 API 密钥、不调用模型。唯一的网络流量是发往你配置中的服务器。
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npx -y @aimarket/warden@0.
|
|
32
|
+
npx -y @aimarket/warden@0.22.0 scan
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
```text
|
|
36
|
-
WARDEN scan 0.
|
|
36
|
+
WARDEN scan 0.21.0 · ruleset 11 sha256-RDXs+TaQoehA… · block at high
|
|
37
37
|
read .mcp.json (claude-code, 3 servers)
|
|
38
38
|
|
|
39
39
|
✓ allow notes claude-code 1 tool · score 0.90
|
|
@@ -47,7 +47,8 @@ Details: warden-mcp scan --json, or --markdown FILE. To review a changed server:
|
|
|
47
47
|
与另外四款开源扫描器在同一批服务器上对比,每款都在无网络的 gVisor 容器中运行:在 MCPTox(AAAI 2026 基准)的留出一半上(WARDEN 的规则只依据另一半编写),WARDEN 阻止了 218 个投毒工具中的 171 个,其余扫描器阻止 8 到 41 个。在 986 台公开服务器上 WARDEN 阻止 3 台,在 14,933 个 npm 和 PyPI 包上阻止 369 个;mcp-shield 分别阻止 343 台和 5,354 个。[方法与全部数字](docs/scanner-comparison.zh.md) · [scan 指南](docs/scan.zh.md)。
|
|
48
48
|
<!-- /warden-try -->
|
|
49
49
|
|
|
50
|
-
**流守卫(
|
|
50
|
+
**流守卫(2026-10-23 本地源码)。** 读取凭据和外部文本后,传出/MCP 调用及普通项目写入均需审核,即使片段极短或编码未知。操作员检查过的精确公开请求可每会话自动执行一次,但不能豁免已识别泄露或不完整扫描。测试中两种配置均自动放行 0/16 攻击;正常请求默认确认 12/12,精确规则下 0/12。这是明确的可用性取舍,不是万能解码器。同一旧案例集仍为 0/23 完整通过。生产和 npm 未更改。[策略、测试与边界](docs/flow-guard.zh.md)。
|
|
51
|
+
|
|
51
52
|
|
|
52
53
|
Ruleset v16(0.20.0,2026-10-22,仅 WARDEN):MCPTox 留出的一半 218 中 210 个(v15:194,v12–v14:171),45 台干净服务器中 0 台;在实地语料和 1,000 个 HISTOR 包样本上拦截的与 v15 完全相同。上面与其他扫描器的对比来自 2026-10-16,使用 v12。[门链中的 v15 和 v16](docs/gates.zh.md)。
|
|
53
54
|
|
|
@@ -76,6 +77,20 @@ WARDEN 在**该服务器的任何工具到达模型之前**审查它,并返回
|
|
|
76
77
|
内建模块(`fs`、`path`、`process`),仍然不拉任何包。它就是 [ARGUS](https://github.com/alexar76/argus)
|
|
77
78
|
里的那个防火墙,被单独抽出来,好让你把它放在自己的 MCP 宿主前面,而不必换用一个智能体。
|
|
78
79
|
|
|
80
|
+
新增十进制/NATO 密钥编码及 MCP Git commit/push 回归;完整 28 项 hook replay 成为本地可验证的 CI/release 门禁(`npm run test:redteam`)。
|
|
81
|
+
|
|
82
|
+
Auten 后续:`taskAdapter.mode: "review"` / `task_run` 按固定结果确认一次;`wrap --screen-read` 在新目标变化时取消已批准 GUI 操作。默认严格 GUI;截图和点击不是原子的。
|
|
83
|
+
|
|
84
|
+
原子模式:`wrap --flow screen --screen-read screenshot --screen-atomic` 要求可信服务器端修订/目标 CAS(`AtomicScreenServer`、`warden.screen.atomic.v1`)。不退回普通点击;仅支持完全由服务器控制的同步表面。附可运行虚拟表单示例,不声称任意原生桌面具有原子性。
|
|
85
|
+
|
|
86
|
+
原生 macOS:`npm run build:macos` 构建目标应用内 AppKit SDK 和真实窗口示例;`npm run test:macos` 运行独立集成门禁。原子性要求将 `WardenDesktopSurface` 接入拥有控件的应用,其他应用被拒绝。参见 [macOS 接入](docs/flow-guard.zh.md)。
|
|
87
|
+
|
|
88
|
+
## 受限任务模式:减少确认并控制实际操作
|
|
89
|
+
|
|
90
|
+
默认仍为严格 GUI 模式。旧的 `gui.mode: "policy"` / `gui.allow` 仅为迁移保留解析,外部文本出现后不再自动放行点击、输入或导航按键。页面可伪造 origin、角色和 accessibility ID;几何、焦点和翻译只用于解释。`press_keys_then_execute` 等复合工具名不能使用 Copy 例外;额外 RPC 元数据也不能获得自动授权。
|
|
91
|
+
|
|
92
|
+
明确启用受限操作:`node dist/mcp-server.js task`。[适配器、支付、边界与最新 AgentDojo replay](docs/flow-guard.zh.md)。仅本地代码,没有发布或部署。
|
|
93
|
+
|
|
79
94
|
## 通过一层代理保护 Claude Desktop 或 Cursor
|
|
80
95
|
|
|
81
96
|
**请使用 0.8.2 或更高版本:0.8.0 和 0.8.1 会让被包装的服务器伪造对客户端自身 `tools/list` 的响应**([CHANGELOG](CHANGELOG.md))。从源码运行 `node /绝对路径/warden/dist/mcp-server.js wrap ...` 效果相同。单独添加 WARDEN MCP 服务器不会检查其他服务器。已用 MCP TypeScript SDK 和 fixture 服务器做过端到端测试;在 Claude Desktop 和 Cursor 本身中的运行尚未记录([发布验证](docs/wrap-validation.md))。请把每个需要保护的服务器命令替换为 `wrap`:
|
|
@@ -85,14 +100,14 @@ WARDEN 在**该服务器的任何工具到达模型之前**审查它,并返回
|
|
|
85
100
|
"mcpServers": {
|
|
86
101
|
"filesystem": {
|
|
87
102
|
"command": "npx",
|
|
88
|
-
"args": ["-y", "@aimarket/warden@0.
|
|
103
|
+
"args": ["-y", "@aimarket/warden@0.22.0", "wrap", "--id", "filesystem", "--",
|
|
89
104
|
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/me/docs"]
|
|
90
105
|
}
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
```
|
|
94
109
|
|
|
95
|
-
始终指定 `--id`:否则修改命令参数会创建新身份并重新执行首次信任。稳定 ID 会把命令或路径变更识别为 `SERVER_IDENTITY_DRIFT`。子进程继承环境变量,身份哈希不包含环境。可运行 `npm install -g @aimarket/warden@0.
|
|
110
|
+
始终指定 `--id`:否则修改命令参数会创建新身份并重新执行首次信任。稳定 ID 会把命令或路径变更识别为 `SERVER_IDENTITY_DRIFT`。子进程继承环境变量,身份哈希不包含环境。可运行 `npm install -g @aimarket/warden@0.22.0`,使用 `warden-mcp` 避免反复进行 `npx` 冷启动。
|
|
96
111
|
|
|
97
112
|
**敏感工具。** 策略文件中的 `sensitiveToolPatterns`(`--policy policy.json`,内容如 `{"sensitiveToolPatterns": ["*delete*", "*transfer*"]}`,`*` 通配、不区分大小写)让每次调用匹配的工具都要等人确认:`wrap` 通过客户端自己的表单(MCP elicitation)询问,列出工具、服务器和参数,只有勾选并接受的回答才放行这一次调用。复选框默认不勾选,所以按默认值自动接受表单的客户端什么也批准不了。不提供 elicitation 表单的客户端会收到拒绝,服务器永远收不到这次调用;`--ask refuse` 不询问直接拒绝。若客户端在等人决定时取消调用,或其间定义发生变化,调用不会发出。0.20.0 之前 `wrap` 接受这些模式却不执行。配合 `--flow`,流守卫会询问的发送也以同样方式询问。
|
|
98
113
|
|
|
@@ -120,16 +135,16 @@ macOS 上 Claude Desktop 日志:`~/Library/Logs/Claude/mcp-server-<名称>.log
|
|
|
120
135
|
## 扫描客户端启动的所有服务器
|
|
121
136
|
|
|
122
137
|
```bash
|
|
123
|
-
npx -y @aimarket/warden@0.
|
|
138
|
+
npx -y @aimarket/warden@0.22.0 scan
|
|
124
139
|
```
|
|
125
140
|
|
|
126
|
-
读取 Claude Code、Claude Desktop、Cursor、VS Code 和 Windsurf 的 MCP 配置,连接其中启动的每个服务器,并在模型看到工具定义之前对其进行审查。输出表格、JSON、SARIF 或 Markdown,有服务器被阻止时以 1 退出;`--no-launch` 不启动任何程序。在仓库中,`warden.lock.json` 把工具定义的改动变成可审查的 diff,CI 会在出现新的或改动过的服务器时失败。也提供 GitHub Action(`uses: alexar76/warden@v0.
|
|
141
|
+
读取 Claude Code、Claude Desktop、Cursor、VS Code 和 Windsurf 的 MCP 配置,连接其中启动的每个服务器,并在模型看到工具定义之前对其进行审查。输出表格、JSON、SARIF 或 Markdown,有服务器被阻止时以 1 退出;`--no-launch` 不启动任何程序。在仓库中,`warden.lock.json` 把工具定义的改动变成可审查的 diff,CI 会在出现新的或改动过的服务器时失败。也提供 GitHub Action(`uses: alexar76/warden@v0.21.0`)、pre-commit 钩子和 Claude Code 插件(`/plugin marketplace add alexar76/warden`)。`--histor` 询问公开的 HISTOR 日志:服务器给你的是否与给所有人的相同;对远程服务器发送端点和摘要,对从 npm 或 PyPI 启动的 stdio 服务器发送软件包名(`npm:<名称>`、`pypi:<名称>`,HISTOR 会在 gVisor 沙箱中运行它)和摘要,从不发送工具文本。可选的分类器(`--classifier-url`、`--classifier-model`)会加入你所选模型(本地或托管)的判断,用于覆盖任何规则都没有提到的内容。详见 [scan 指南](docs/scan.zh.md)。与另外两款扫描器在同一批服务器上的对比,见[扫描器对比](docs/scanner-comparison.zh.md)。
|
|
127
142
|
|
|
128
|
-
**面向服务器发布者。** 如果在安装了 HISTOR 沙箱所没有的东西(启动器的运行器、每个操作系统各一个)的地方,你的服务器列出的是别的工具,`npx -y @aimarket/warden@0.
|
|
143
|
+
**面向服务器发布者。** 如果在安装了 HISTOR 沙箱所没有的东西(启动器的运行器、每个操作系统各一个)的地方,你的服务器列出的是别的工具,`npx -y @aimarket/warden@0.22.0 declare --out mcp-tool-sets.json -- <你的服务器>` 会把这些工具集写进一个文件,由你发布在自己的域名上;拿到这些工具集的人看到的将是发布者的声明,而不是陌生的工具集。参见 [scan 指南](docs/scan.zh.md)。
|
|
129
144
|
|
|
130
145
|
## WARDEN 不做什么
|
|
131
146
|
|
|
132
|
-
仅代理 stdio。工具结果用同一套规则读取(默认 `--results warn`:结果读起来像对模型的指令时,在其前面加一段提醒;`block`:扣下结果,此时工具已经执行;`off`);调用参数、`prompts/*`、`resources/*`、sampling 和 elicitation 内容不被扫描。没有已知字面量的改写或非英语指令可能通过;可另用 [HISTOR 分类器](https://github.com/alexar76/histor)。WARDEN
|
|
147
|
+
仅代理 stdio。工具结果用同一套规则读取(默认 `--results warn`:结果读起来像对模型的指令时,在其前面加一段提醒;`block`:扣下结果,此时工具已经执行;`off`);调用参数、`prompts/*`、`resources/*`、sampling 和 elicitation 内容不被扫描。没有已知字面量的改写或非英语指令可能通过;可另用 [HISTOR 分类器](https://github.com/alexar76/histor)。WARDEN 不是沙箱,也不保护客户端配置免遭修改。没有可信且参与原子屏幕协议的后端时,检查和另一个进程内的执行无法原子化:发送前检测到变更会阻止调用;执行期间检测到变更会隐藏结果,但操作可能已发生。最后一次列表响应之后的静默变更无法被观察。不支持通知的客户端可能显示旧列表,但每次调用前仍会核对定义。
|
|
133
148
|
|
|
134
149
|
判定记录未签名,仅用于诊断,不是合规证明。`--audit-only` 在日志中标记 `AUDIT-ONLY`,放行被安全检查拒绝的描述和调用,不建立 pin;协议及资源限制仍有效。此模式用于调查误报,不提供防护。
|
|
135
150
|
|
|
@@ -307,6 +322,7 @@ WARDEN 是 [Base 上的 `96684` 号](https://8004scan.io/agents/base/96684) ERC-
|
|
|
307
322
|
| [实地普查:1 108 个公开 MCP 服务器](docs/mcp-survey.zh.md) | WARDEN 在真实的第三方工具定义上判了什么——拦截 50 个服务器,4 项成立,其余判错的六种方式 |
|
|
308
323
|
| [扫描器对比](docs/scanner-comparison.zh.md) | WARDEN、mcp-audit、mcp-shield、truecopy 与 mcpsentry 在同一批服务器、14,933 个 npm 和 PyPI 包以及 MCPTox 基准上的结果,附全部判定、清单和测试工具 |
|
|
309
324
|
| [流向守卫](docs/flow-guard.zh.md) | Claude Code 插件的运行时守卫:外部文本、私有数据、外发调用;AgentDojo 测量及其局限 |
|
|
325
|
+
| [新的模型对比](docs/flow-guard-model-comparison-2026-10-25.zh.md) | 720 个答案:已知案例上的改善、不确定性、警告内容边界,以及未观察到 Stop 纠正 ID |
|
|
310
326
|
| [Glama / Docker](docs/GLAMA.md) | stdio MCP、健康检查、Build steps / CMD |
|
|
311
327
|
| [Security](SECURITY.md) | 如何报告防火墙绕过 |
|
|
312
328
|
| [Contributing](CONTRIBUTING.md) | 零依赖规则、规则表 PR |
|
|
@@ -326,7 +342,7 @@ WARDEN 是 [Base 上的 `96684` 号](https://8004scan.io/agents/base/96684) ERC-
|
|
|
326
342
|
## 开发
|
|
327
343
|
|
|
328
344
|
```bash
|
|
329
|
-
npm install && npm run build && npm test #
|
|
345
|
+
npm install && npm run build && npm test # 1256 项测试
|
|
330
346
|
```
|
|
331
347
|
|
|
332
348
|
`test/packaging.test.ts` 正是让标题保持诚实的东西:一旦出现运行时依赖、任何源文件从包外 import、或者入口点不
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
|
|
9
9
|
<a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
|
|
10
10
|
<img src="docs/badges/deps.svg" alt="Zero runtime dependencies" />
|
|
11
|
-
<img src="docs/badges/tests.svg" alt="
|
|
11
|
+
<img src="docs/badges/tests.svg" alt="1256 tests passing" />
|
|
12
12
|
<img src="docs/badges/node.svg" alt="Node >= 20" />
|
|
13
13
|
<a href="LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
|
|
14
14
|
</p>
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
**Check every MCP server on your machine.** No account, no API key, no model. The only network traffic is to the servers in your config.
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npx -y @aimarket/warden@0.
|
|
32
|
+
npx -y @aimarket/warden@0.22.0 scan
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
```text
|
|
36
|
-
WARDEN scan 0.
|
|
36
|
+
WARDEN scan 0.21.0 · ruleset 11 sha256-RDXs+TaQoehA… · block at high
|
|
37
37
|
read .mcp.json (claude-code, 3 servers)
|
|
38
38
|
|
|
39
39
|
✓ allow notes claude-code 1 tool · score 0.90
|
|
@@ -47,7 +47,8 @@ Details: warden-mcp scan --json, or --markdown FILE. To review a changed server:
|
|
|
47
47
|
Against four other open-source scanners on the same servers, each in a gVisor container with no network: on the held-out half of MCPTox (an AAAI 2026 benchmark; WARDEN's rules were written from the other half) WARDEN blocks 171 of 218 poisoned tools, the others 8 to 41. On 986 public servers it blocks 3 and on 14,933 npm and PyPI packages 369; mcp-shield blocks 343 and 5,354. [Method and every number](docs/scanner-comparison.md) · [scan guide](docs/scan.md).
|
|
48
48
|
<!-- /warden-try -->
|
|
49
49
|
|
|
50
|
-
**Flow guard (
|
|
50
|
+
**Flow guard (local source, 2026-10-23).** After credentials and outside text, outbound/MCP calls and ordinary project writes require review, regardless of short fragments or unknown encodings. Exact operator-reviewed public requests can run once per session; known leaks and incomplete scans stay blocked from this exemption. Fixture: 0/16 attacks automatically allowed in both modes; 12/12 benign calls reviewed by default, 0/12 with exact public rules. This is an explicit usability tradeoff, not universal decoding. The unchanged 23-case replay remains 0/23 fully delivered. Production/npm are unchanged. [Policy, tests and limits](docs/flow-guard.md).
|
|
51
|
+
|
|
51
52
|
|
|
52
53
|
Ruleset v16 (0.20.0, 2026-10-22, WARDEN only): MCPTox held-out half 210 of 218 (v15: 194, v12–v14: 171), 0 of 45 clean servers; on the field corpus and a 1,000-package HISTOR sample it blocks exactly what v15 blocked. The comparison with other scanners above is from 2026-10-16 with v12. [v15 and v16 in the gate chain](docs/gates.md).
|
|
53
54
|
|
|
@@ -79,6 +80,20 @@ server adds other `node:` builtins (`fs`, `path`, `process`) and still pulls in
|
|
|
79
80
|
the firewall out of [ARGUS](https://github.com/alexar76/argus), extracted so you can put it in front
|
|
80
81
|
of your own MCP host without adopting an agent.
|
|
81
82
|
|
|
83
|
+
Decimal/NATO secret encodings and MCP Git commit/push now have regressions; the complete 28-case hook replay is a failing local CI/release gate (`npm run test:redteam`).
|
|
84
|
+
|
|
85
|
+
Auten follow-up: opt-in `taskAdapter.mode: "review"` / `task_run` asks once for a fixed outcome; `wrap --screen-read` can cancel an approved GUI action after a fresh target check. Strict GUI remains default; snapshot and click are not atomic.
|
|
86
|
+
|
|
87
|
+
Atomic screen opt-in: `wrap --flow screen --screen-read screenshot --screen-atomic` requires a trusted server-side revision/target CAS (`AtomicScreenServer`, `warden.screen.atomic.v1`). No raw-click fallback; server-owned synchronous surfaces only. A runnable virtual-form example is included; arbitrary native desktop atomicity is not claimed.
|
|
88
|
+
|
|
89
|
+
Native macOS: `npm run build:macos` builds the target-side AppKit SDK and a real native demo window; `npm run test:macos` runs its separate integration gate. Atomicity requires embedding `WardenDesktopSurface` in the application that owns the controls; unrelated apps are refused. See [macOS integration](docs/flow-guard.md).
|
|
90
|
+
|
|
91
|
+
## Constrained task mode: fewer confirmations, actual effect control
|
|
92
|
+
|
|
93
|
+
Strict GUI remains the default. Legacy `gui.mode: "policy"` / `gui.allow` are parsed for migration but no longer authorize a click, typing or a navigation key after outside text. Even an exact origin, role and accessibility ID can be spoofed by the page. Geometry, focus and translated labels explain the request only. Composite keyboard names such as `press_keys_then_execute` cannot use the Copy exception. Extra RPC metadata never receives automatic authorization.
|
|
94
|
+
|
|
95
|
+
Opt-in constrained operations: `node dist/mcp-server.js task`. See the [task adapter, payments, limits and fresh AgentDojo replay](docs/flow-guard.md). Local source only; no publication or deployment.
|
|
96
|
+
|
|
82
97
|
## Protect Claude Desktop or Cursor with one wrapper
|
|
83
98
|
|
|
84
99
|
**Use 0.8.2 or later: 0.8.0 and 0.8.1 let a wrapped server forge the reply to the client's own `tools/list`** ([CHANGELOG](CHANGELOG.md)). From source, `node /absolute/path/warden/dist/mcp-server.js wrap ...` works the same. Adding WARDEN as a separate MCP server does not inspect other servers. Tested end to end with the MCP TypeScript SDK and fixture servers; runs in Claude Desktop and Cursor themselves are not yet recorded ([release validation](docs/wrap-validation.md)). Replace each protected server’s command with `wrap`:
|
|
@@ -88,7 +103,7 @@ of your own MCP host without adopting an agent.
|
|
|
88
103
|
"mcpServers": {
|
|
89
104
|
"filesystem": {
|
|
90
105
|
"command": "npx",
|
|
91
|
-
"args": ["-y", "@aimarket/warden@0.
|
|
106
|
+
"args": ["-y", "@aimarket/warden@0.22.0", "wrap", "--id", "filesystem", "--",
|
|
92
107
|
"npx", "-y", "@modelcontextprotocol/server-filesystem", "/Users/me/docs"]
|
|
93
108
|
}
|
|
94
109
|
}
|
|
@@ -97,7 +112,7 @@ of your own MCP host without adopting an agent.
|
|
|
97
112
|
|
|
98
113
|
For explicit first-contact review, add `--require-approval` to `wrap`. It withholds new definitions until `warden-mcp pins approve --id filesystem` in an operator terminal, then rejects any changed field or launch identity. Automatic/legacy pins need reapproval. See the [language-independent admission boundary](docs/security-hardening-language-boundary.md).
|
|
99
114
|
|
|
100
|
-
Always specify `--id`: without it, changing command arguments creates a new identity and a new first contact. A stable ID turns a command or path change into `SERVER_IDENTITY_DRIFT`. Environment variables are inherited by the child and excluded from identity. To avoid repeated cold `npx` starts, install globally with `npm install -g @aimarket/warden@0.
|
|
115
|
+
Always specify `--id`: without it, changing command arguments creates a new identity and a new first contact. A stable ID turns a command or path change into `SERVER_IDENTITY_DRIFT`. Environment variables are inherited by the child and excluded from identity. To avoid repeated cold `npx` starts, install globally with `npm install -g @aimarket/warden@0.22.0`, then use `warden-mcp`.
|
|
101
116
|
|
|
102
117
|
**Sensitive tools.** A policy file's `sensitiveToolPatterns` (`--policy policy.json` with `{"sensitiveToolPatterns": ["*delete*", "*transfer*"]}`, case-insensitive `*` globs) makes every call to a matching tool wait for a person: `wrap` asks through the client's own form (MCP elicitation) with the tool, the server and the arguments, and only a ticked, accepted answer lets that one call through. The box starts unticked, so a client that accepts forms with their defaults approves nothing. A client that offers no elicitation form gets a refusal and the server never receives the call; `--ask refuse` refuses without asking. If the client cancels the call while the person decides, or the definitions change meanwhile, it is not sent. Before 0.20.0 `wrap` accepted these patterns and did not enforce them. With `--flow`, a send the flow guard would ask about is asked the same way.
|
|
103
118
|
|
|
@@ -125,16 +140,16 @@ Claude Desktop on macOS writes stderr to `~/Library/Logs/Claude/mcp-server-<name
|
|
|
125
140
|
## Scan every server your clients start
|
|
126
141
|
|
|
127
142
|
```bash
|
|
128
|
-
npx -y @aimarket/warden@0.
|
|
143
|
+
npx -y @aimarket/warden@0.22.0 scan
|
|
129
144
|
```
|
|
130
145
|
|
|
131
|
-
Reads the MCP configs of Claude Code, Claude Desktop, Cursor, VS Code and Windsurf, connects to every server they start, and vets the tool definitions before a model sees them. It prints a table, JSON, SARIF or Markdown, and exits 1 when a server is blocked; `--no-launch` starts nothing. In a repository, `warden.lock.json` turns a change in tool definitions into a reviewable diff, and CI fails on a new or changed server. Also as a GitHub Action (`uses: alexar76/warden@v0.
|
|
146
|
+
Reads the MCP configs of Claude Code, Claude Desktop, Cursor, VS Code and Windsurf, connects to every server they start, and vets the tool definitions before a model sees them. It prints a table, JSON, SARIF or Markdown, and exits 1 when a server is blocked; `--no-launch` starts nothing. In a repository, `warden.lock.json` turns a change in tool definitions into a reviewable diff, and CI fails on a new or changed server. Also as a GitHub Action (`uses: alexar76/warden@v0.21.0`), pre-commit hooks and a Claude Code plugin (`/plugin marketplace add alexar76/warden`). `--histor` asks the public HISTOR log whether a server serves you what it serves everyone; for a remote server it sends the endpoint and a digest, for a stdio server started from npm or PyPI the package name (`npm:<name>`, `pypi:<name>`, which HISTOR runs in a gVisor sandbox) and a digest — never tool text. An opt-in classifier (`--classifier-url`, `--classifier-model`) adds the reading of a model you choose, local or hosted, for what no rule names. See the [scan guide](docs/scan.md). How it compares with two other scanners, on the same servers: [scanner comparison](docs/scanner-comparison.md).
|
|
132
147
|
|
|
133
|
-
**For a server's publisher.** If your server lists different tools where something HISTOR's sandbox lacks is installed — a launcher's runner, one per operating system — `npx -y @aimarket/warden@0.
|
|
148
|
+
**For a server's publisher.** If your server lists different tools where something HISTOR's sandbox lacks is installed — a launcher's runner, one per operating system — `npx -y @aimarket/warden@0.22.0 declare --out mcp-tool-sets.json -- <your server>` writes those sets to a file you publish on your own domain, and the people who get them see the publisher's declaration instead of an unseen tool set. See [declare your server's tool sets](docs/scan.md#for-publishers-declare-your-servers-tool-sets).
|
|
134
149
|
|
|
135
150
|
## What WARDEN does not do
|
|
136
151
|
|
|
137
|
-
Only stdio is wrapped. Tool results are read by the same rules (`--results warn`, the default, puts a notice in front of a result that reads as instructions to the model; `block` withholds it after the tool ran; `off`); call arguments, `prompts/*`, `resources/*`, sampling and elicitation content pass through without scanning. Paraphrases and non-English instructions without recognized literals can pass; see the separate [HISTOR classifier](https://github.com/alexar76/histor). WARDEN is not a sandbox and does not protect the client config from edits.
|
|
152
|
+
Only stdio is wrapped. Tool results are read by the same rules (`--results warn`, the default, puts a notice in front of a result that reads as instructions to the model; `block` withholds it after the tool ran; `off`); call arguments, `prompts/*`, `resources/*`, sampling and elicitation content pass through without scanning. Paraphrases and non-English instructions without recognized literals can pass; see the separate [HISTOR classifier](https://github.com/alexar76/histor). WARDEN is not a sandbox and does not protect the client config from edits. Without a trusted participating atomic screen backend, it cannot make verification and execution inside another process atomic: a detected change before dispatch blocks the call; a change during execution withholds the result, but the action may already have happened. A silent change after the last list response is not observable. Clients ignoring list-change notifications may display old tools, but changed definitions are checked again before calls.
|
|
138
153
|
|
|
139
154
|
Verdicts are unsigned diagnostic records, not compliance attestations. `--audit-only` logs `AUDIT-ONLY`, forwards security-blocked descriptions/calls and does not establish pins; framing and resource limits still apply. Use it to investigate false positives, not for protection.
|
|
140
155
|
|
|
@@ -327,6 +342,7 @@ check them: [ERC-8004 identities](https://github.com/alexar76/aicom/blob/main/do
|
|
|
327
342
|
| [Field survey: 1 108 public MCP servers](docs/mcp-survey.md) | What WARDEN decided on real third-party tool definitions — 50 servers blocked, 4 substantiated, and the six ways the rest were wrong |
|
|
328
343
|
| [Scanner comparison](docs/scanner-comparison.md) | WARDEN, mcp-audit, mcp-shield, truecopy and mcpsentry on the same servers, on 14,933 npm and PyPI packages and on the MCPTox benchmark, with every judgment, the manifest and the harness |
|
|
329
344
|
| [Flow guard](docs/flow-guard.md) | The Claude Code plugin's runtime guard: outside text, private data, outbound calls; the AgentDojo measurement and its limits |
|
|
345
|
+
| [Fresh model comparison](docs/flow-guard-model-comparison-2026-10-25.md) | 720 answers; known-case improvement, uncertainty, warning custody and no observed Stop choice correction |
|
|
330
346
|
| [Glama / Docker](docs/GLAMA.md) | stdio MCP server, health check, admin Build steps / CMD |
|
|
331
347
|
| [MCP registries](docs/REGISTRIES.md) | Official Registry, mcp.so / Pulse |
|
|
332
348
|
| [Security](SECURITY.md) | How to report a firewall bypass |
|
|
@@ -350,7 +366,7 @@ check them: [ERC-8004 identities](https://github.com/alexar76/aicom/blob/main/do
|
|
|
350
366
|
## Development
|
|
351
367
|
|
|
352
368
|
```bash
|
|
353
|
-
npm install && npm run build && npm test #
|
|
369
|
+
npm install && npm run build && npm test # 1256 tests
|
|
354
370
|
```
|
|
355
371
|
|
|
356
372
|
`test/packaging.test.ts` is what keeps the headline honest: it fails if an npm runtime dependency
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FlowPolicy } from './flow-guard-types.js';
|
|
2
|
+
import type { CapabilityBinding, McpServerRef, ToolDef } from './types.js';
|
|
3
|
+
export interface AnnotationReview {
|
|
4
|
+
/** Acknowledgement is valid only inside this wrapper for this exact identity/tool set. */
|
|
5
|
+
key: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
/** Deliberately independent of tool arguments, destination allowlists and server titles. */
|
|
9
|
+
export declare function annotationReview(server: McpServerRef, tools: ToolDef[], toolName: string, flowName: string, policy: FlowPolicy, bindings?: readonly CapabilityBinding[]): AnnotationReview | undefined;
|
|
10
|
+
//# sourceMappingURL=annotation-review.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-review.d.ts","sourceRoot":"","sources":["../src/annotation-review.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI3E,MAAM,WAAW,gBAAgB;IAC/B,0FAA0F;IAC1F,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAC1E,MAAM,EAAE,UAAU,EAAE,QAAQ,GAAE,SAAS,iBAAiB,EAAO,GAC9D,gBAAgB,GAAG,SAAS,CAsB9B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Server-authored MCP annotations can request caution, never establish authority. */
|
|
2
|
+
import { globMatch } from './flow-guard-policy.js';
|
|
3
|
+
import { pinToolsHash, serverIdentityHash } from './pinning.js';
|
|
4
|
+
import { boundCapabilities } from './toxic-flow.js';
|
|
5
|
+
const HINTS = ['readOnlyHint', 'destructiveHint', 'openWorldHint'];
|
|
6
|
+
/** Deliberately independent of tool arguments, destination allowlists and server titles. */
|
|
7
|
+
export function annotationReview(server, tools, toolName, flowName, policy, bindings = []) {
|
|
8
|
+
if (!flowName || policy.annotationReview !== 'first-use' || policy.mode === 'off')
|
|
9
|
+
return undefined;
|
|
10
|
+
const qualified = `mcp__${flowName}__${toolName}`;
|
|
11
|
+
if (Object.keys(policy.labels).some(glob => globMatch(glob, qualified)))
|
|
12
|
+
return undefined;
|
|
13
|
+
if (boundCapabilities(server, tools, bindings))
|
|
14
|
+
return undefined;
|
|
15
|
+
const tool = tools.find(t => t.name === toolName);
|
|
16
|
+
if (!tool || tool.annotations === undefined)
|
|
17
|
+
return undefined;
|
|
18
|
+
const annotations = tool.annotations;
|
|
19
|
+
let claims;
|
|
20
|
+
if (!annotations || typeof annotations !== 'object' || Array.isArray(annotations))
|
|
21
|
+
claims = ['invalid annotation object'];
|
|
22
|
+
else {
|
|
23
|
+
const fields = annotations;
|
|
24
|
+
claims = HINTS.filter(hint => Object.hasOwn(fields, hint))
|
|
25
|
+
.map(hint => `${hint}=${typeof fields[hint] === 'boolean' ? String(fields[hint]) : 'invalid'}`);
|
|
26
|
+
if (!claims.length)
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
key: JSON.stringify([server.id, serverIdentityHash(server), pinToolsHash(tools), toolName]),
|
|
31
|
+
reason: `The server supplied unverified MCP annotations (${claims.join(', ')}), and the operator has not labelled this tool. ` +
|
|
32
|
+
'Review them before first use. Even readOnlyHint=true does not prove a call is read-only or free. ' +
|
|
33
|
+
'Acceptance records this annotation review only for this exact server and tool set in this running wrapper; later calls still face every other guard.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=annotation-review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotation-review.js","sourceRoot":"","sources":["../src/annotation-review.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAGpD,MAAM,KAAK,GAAG,CAAC,cAAc,EAAE,iBAAiB,EAAE,eAAe,CAAU,CAAC;AAQ5E,4FAA4F;AAC5F,MAAM,UAAU,gBAAgB,CAC9B,MAAoB,EAAE,KAAgB,EAAE,QAAgB,EAAE,QAAgB,EAC1E,MAAkB,EAAE,WAAyC,EAAE;IAE/D,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,gBAAgB,KAAK,WAAW,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;QAAE,OAAO,SAAS,CAAC;IACpG,MAAM,SAAS,GAAG,QAAQ,QAAQ,KAAK,QAAQ,EAAE,CAAC;IAClD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC1F,IAAI,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACjE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,WAAW,GAAY,IAAI,CAAC,WAAW,CAAC;IAC9C,IAAI,MAAgB,CAAC;IACrB,IAAI,CAAC,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;QAAE,MAAM,GAAG,CAAC,2BAA2B,CAAC,CAAC;SACrH,CAAC;QACJ,MAAM,MAAM,GAAG,WAAsC,CAAC;QACtD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;aACvD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAClG,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,OAAO,SAAS,CAAC;IACvC,CAAC;IACD,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,kBAAkB,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC3F,MAAM,EAAE,mDAAmD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,kDAAkD;YAC5H,mGAAmG;YACnG,sJAAsJ;KACzJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export declare const ATOMIC_SCREEN_PROTOCOL = "warden.screen.atomic.v1";
|
|
2
|
+
export declare const ATOMIC_SCREEN_PREPARE = "warden/screen/prepare";
|
|
3
|
+
export declare const ATOMIC_SCREEN_COMMIT = "warden/screen/commit";
|
|
4
|
+
export declare const ATOMIC_SCREEN_CANCEL = "warden/screen/cancel";
|
|
5
|
+
export interface AtomicScreenAction {
|
|
6
|
+
name: string;
|
|
7
|
+
arguments: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface AtomicScreenExpectation {
|
|
10
|
+
revision: string;
|
|
11
|
+
stamp: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AtomicScreenPrepared extends AtomicScreenExpectation {
|
|
14
|
+
protocol: typeof ATOMIC_SCREEN_PROTOCOL;
|
|
15
|
+
ticket: string;
|
|
16
|
+
expiresAt: number;
|
|
17
|
+
observation: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export type AtomicScreenReceipt = {
|
|
20
|
+
protocol: typeof ATOMIC_SCREEN_PROTOCOL;
|
|
21
|
+
status: 'refused';
|
|
22
|
+
reason: 'stale' | 'expired' | 'unknown' | 'mismatch' | 'unavailable';
|
|
23
|
+
} | {
|
|
24
|
+
protocol: typeof ATOMIC_SCREEN_PROTOCOL;
|
|
25
|
+
status: 'committed';
|
|
26
|
+
revisionBefore: string;
|
|
27
|
+
revisionAfter: string;
|
|
28
|
+
stamp: string;
|
|
29
|
+
result: Record<string, unknown>;
|
|
30
|
+
};
|
|
31
|
+
export declare function atomicScreenAction(value: unknown): AtomicScreenAction;
|
|
32
|
+
export declare function atomicScreenPrepared(value: unknown, now?: number): AtomicScreenPrepared;
|
|
33
|
+
export declare function atomicScreenReceipt(value: unknown, expected: AtomicScreenExpectation): AtomicScreenReceipt;
|
|
34
|
+
/** Single authority for a surface, shared by every connection. Restart invalidates all tickets.
|
|
35
|
+
* The callback is trusted backend code, not model/page code. An async/remote click backend is unsupported. */
|
|
36
|
+
export declare class AtomicScreenServer {
|
|
37
|
+
private revision;
|
|
38
|
+
private snapshot;
|
|
39
|
+
private busy;
|
|
40
|
+
private poisoned;
|
|
41
|
+
private readonly tickets;
|
|
42
|
+
private readonly actions;
|
|
43
|
+
private readonly observation;
|
|
44
|
+
constructor(options: {
|
|
45
|
+
observation: AtomicScreenAction;
|
|
46
|
+
actions: string[];
|
|
47
|
+
snapshot: Record<string, unknown>;
|
|
48
|
+
apply: (action: AtomicScreenAction, target: string) => {
|
|
49
|
+
result: Record<string, unknown>;
|
|
50
|
+
snapshot: Record<string, unknown>;
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
private readonly apply;
|
|
54
|
+
read(): Record<string, unknown>;
|
|
55
|
+
/** Call for EVERY mutation, even an invisible handler change or A -> B -> A transition. */
|
|
56
|
+
replace(snapshot: Record<string, unknown>): void;
|
|
57
|
+
prepare(params: unknown): AtomicScreenPrepared;
|
|
58
|
+
cancel(params: unknown): void;
|
|
59
|
+
commit(params: unknown): AtomicScreenReceipt;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=atomic-screen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-screen.d.ts","sourceRoot":"","sources":["../src/atomic-screen.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,sBAAsB,4BAA4B,CAAC;AAChE,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAC7D,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAC3D,eAAO,MAAM,oBAAoB,yBAAyB,CAAC;AAe3D,MAAM,WAAW,kBAAkB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE;AACxF,MAAM,WAAW,uBAAuB;IAAG,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAC5E,MAAM,WAAW,oBAAqB,SAAQ,uBAAuB;IACnE,QAAQ,EAAE,OAAO,sBAAsB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClH;AACD,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,EAAE,OAAO,sBAAsB,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;CAAE,GACpI;IAAE,QAAQ,EAAE,OAAO,sBAAsB,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,cAAc,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AACpK,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAIrE;AACD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,SAAa,GAAG,oBAAoB,CAM3F;AACD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,uBAAuB,GAAG,mBAAmB,CAO1G;AAED;8GAC8G;AAC9G,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,QAAQ,CAA0B;IAC1C,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmH;IAC3I,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;gBACrC,OAAO,EAAE;QACnB,WAAW,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtF,KAAK,EAAE,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,KAAK;YAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;SAAE,CAAC;KAC/H;IASD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyH;IAC/I,IAAI,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAI/B,2FAA2F;IAC3F,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAOhD,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,oBAAoB;IAgB9C,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IAI7B,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,mBAAmB;CAiC7C"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/** Server-side CAS for a trusted, server-owned surface. Not a lock around remote OS input.
|
|
2
|
+
* All mutations, including handler/recipient/focus changes, must pass through replace/commit.
|
|
3
|
+
* apply must commit synchronously against the resolved target in this same authority;
|
|
4
|
+
* enqueueing a coordinate click or awaiting another process does NOT implement this contract. */
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
6
|
+
import { canonicalizeRfc8785 } from './jcs.js';
|
|
7
|
+
import { screenObservation, screenResponseEvidence } from './flow-guard.js';
|
|
8
|
+
export const ATOMIC_SCREEN_PROTOCOL = 'warden.screen.atomic.v1';
|
|
9
|
+
export const ATOMIC_SCREEN_PREPARE = 'warden/screen/prepare';
|
|
10
|
+
export const ATOMIC_SCREEN_COMMIT = 'warden/screen/commit';
|
|
11
|
+
export const ATOMIC_SCREEN_CANCEL = 'warden/screen/cancel';
|
|
12
|
+
const MAX_AGE = 300_000, MAX_TICKETS = 256;
|
|
13
|
+
const opaque = () => randomBytes(32).toString('hex');
|
|
14
|
+
const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
15
|
+
const exact = (v, keys) => record(v) && Object.keys(v).length === keys.length && keys.every(k => Object.hasOwn(v, k));
|
|
16
|
+
const hex = (v) => typeof v === 'string' && /^[a-f0-9]{64}$/.test(v);
|
|
17
|
+
const toolName = (v) => typeof v === 'string' && /^[A-Za-z][A-Za-z0-9_.-]{0,99}$/.test(v);
|
|
18
|
+
function copy(v, max = 1_048_576) {
|
|
19
|
+
const text = canonicalizeRfc8785(v);
|
|
20
|
+
if (Buffer.byteLength(text) > max)
|
|
21
|
+
throw new Error('Atomic screen value exceeds limit');
|
|
22
|
+
// These bytes were just serialized by our canonicalizer (no duplicate keys,
|
|
23
|
+
// cycles, non-JSON fields or non-finite values). Geometry may contain fractions;
|
|
24
|
+
// the separate AWR integer-only wire parser is not appropriate here.
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
}
|
|
27
|
+
export function atomicScreenAction(value) {
|
|
28
|
+
const action = copy(value, 65_536);
|
|
29
|
+
if (!exact(action, ['name', 'arguments']) || !toolName(action.name) || !record(action.arguments))
|
|
30
|
+
throw new Error('Invalid atomic screen action');
|
|
31
|
+
return action;
|
|
32
|
+
}
|
|
33
|
+
export function atomicScreenPrepared(value, now = Date.now()) {
|
|
34
|
+
const v = copy(value);
|
|
35
|
+
if (!exact(v, ['protocol', 'ticket', 'revision', 'stamp', 'expiresAt', 'observation']) || v.protocol !== ATOMIC_SCREEN_PROTOCOL ||
|
|
36
|
+
!hex(v.ticket) || !hex(v.revision) || !hex(v.stamp) || !Number.isSafeInteger(v.expiresAt) || v.expiresAt <= now ||
|
|
37
|
+
v.expiresAt > now + MAX_AGE || !record(v.observation))
|
|
38
|
+
throw new Error('Invalid or expired atomic screen preparation');
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
export function atomicScreenReceipt(value, expected) {
|
|
42
|
+
const v = copy(value, 2_097_152);
|
|
43
|
+
if (exact(v, ['protocol', 'status', 'reason']) && v.protocol === ATOMIC_SCREEN_PROTOCOL && v.status === 'refused' &&
|
|
44
|
+
['stale', 'expired', 'unknown', 'mismatch', 'unavailable'].includes(v.reason))
|
|
45
|
+
return v;
|
|
46
|
+
if (!exact(v, ['protocol', 'status', 'revisionBefore', 'revisionAfter', 'stamp', 'result']) || v.protocol !== ATOMIC_SCREEN_PROTOCOL || v.status !== 'committed' ||
|
|
47
|
+
v.revisionBefore !== expected.revision || v.stamp !== expected.stamp || !hex(v.revisionAfter) || v.revisionAfter === v.revisionBefore || !record(v.result))
|
|
48
|
+
throw new Error('Invalid atomic screen receipt; execution is uncertain');
|
|
49
|
+
return v;
|
|
50
|
+
}
|
|
51
|
+
/** Single authority for a surface, shared by every connection. Restart invalidates all tickets.
|
|
52
|
+
* The callback is trusted backend code, not model/page code. An async/remote click backend is unsupported. */
|
|
53
|
+
export class AtomicScreenServer {
|
|
54
|
+
revision = opaque();
|
|
55
|
+
snapshot;
|
|
56
|
+
busy = false;
|
|
57
|
+
poisoned = false;
|
|
58
|
+
tickets = new Map();
|
|
59
|
+
actions;
|
|
60
|
+
observation;
|
|
61
|
+
constructor(options) {
|
|
62
|
+
this.observation = atomicScreenAction(options.observation);
|
|
63
|
+
if (!screenObservation(`mcp__atomic__${this.observation.name}`, this.observation.arguments) || !options.actions.length ||
|
|
64
|
+
options.actions.length > 64 || new Set(options.actions).size !== options.actions.length || options.actions.some(n => !toolName(n)) ||
|
|
65
|
+
typeof options.apply !== 'function' || ['AsyncFunction', 'GeneratorFunction', 'AsyncGeneratorFunction'].includes(options.apply.constructor.name))
|
|
66
|
+
throw new Error('Unsupported atomic surface backend');
|
|
67
|
+
this.actions = new Set(options.actions);
|
|
68
|
+
this.apply = options.apply;
|
|
69
|
+
this.snapshot = copy(options.snapshot);
|
|
70
|
+
}
|
|
71
|
+
apply;
|
|
72
|
+
read() {
|
|
73
|
+
if (this.busy || this.poisoned)
|
|
74
|
+
throw new Error('Atomic surface unavailable');
|
|
75
|
+
return copy(this.snapshot);
|
|
76
|
+
}
|
|
77
|
+
/** Call for EVERY mutation, even an invisible handler change or A -> B -> A transition. */
|
|
78
|
+
replace(snapshot) {
|
|
79
|
+
if (this.busy)
|
|
80
|
+
throw new Error('Atomic surface transaction already running');
|
|
81
|
+
// Invalidate BEFORE parsing: a backend reporting an unrepresentable new state
|
|
82
|
+
// must never leave an old approval usable against its changed real state.
|
|
83
|
+
this.revision = opaque();
|
|
84
|
+
this.poisoned = true;
|
|
85
|
+
this.snapshot = copy(snapshot);
|
|
86
|
+
this.poisoned = false;
|
|
87
|
+
}
|
|
88
|
+
prepare(params) {
|
|
89
|
+
if (this.busy || this.poisoned)
|
|
90
|
+
throw new Error('Atomic surface unavailable');
|
|
91
|
+
const p = copy(params, 65_536);
|
|
92
|
+
if (!exact(p, ['protocol', 'observation', 'action']) || p.protocol !== ATOMIC_SCREEN_PROTOCOL || canonicalizeRfc8785(p.observation) !== canonicalizeRfc8785(this.observation))
|
|
93
|
+
throw new Error('Invalid atomic preparation');
|
|
94
|
+
const action = atomicScreenAction(p.action);
|
|
95
|
+
if (!this.actions.has(action.name))
|
|
96
|
+
throw new Error('Unsupported atomic action');
|
|
97
|
+
const evidence = screenResponseEvidence(`mcp__atomic__${action.name}`, action.arguments, `mcp__atomic__${this.observation.name}`, this.observation.arguments, this.snapshot);
|
|
98
|
+
if (!evidence)
|
|
99
|
+
throw new Error('Atomic target is ambiguous or unsupported');
|
|
100
|
+
const now = Date.now(), monotonic = process.hrtime.bigint();
|
|
101
|
+
for (const [id, ticket] of this.tickets)
|
|
102
|
+
if (ticket.expiresAt <= now || ticket.deadline <= monotonic)
|
|
103
|
+
this.tickets.delete(id);
|
|
104
|
+
if (this.tickets.size >= MAX_TICKETS)
|
|
105
|
+
throw new Error('Too many pending atomic approvals');
|
|
106
|
+
const prepared = { protocol: ATOMIC_SCREEN_PROTOCOL, ticket: opaque(), revision: this.revision, stamp: evidence.stamp, expiresAt: now + MAX_AGE, observation: copy(this.snapshot) };
|
|
107
|
+
const { observation: _observation, ...binding } = prepared;
|
|
108
|
+
this.tickets.set(prepared.ticket, { ...binding, action, deadline: monotonic + BigInt(MAX_AGE) * 1000000n });
|
|
109
|
+
return copy(prepared);
|
|
110
|
+
}
|
|
111
|
+
cancel(params) {
|
|
112
|
+
if (!exact(params, ['protocol', 'ticket']) || params.protocol !== ATOMIC_SCREEN_PROTOCOL || !hex(params.ticket))
|
|
113
|
+
throw new Error('Invalid atomic cancellation');
|
|
114
|
+
this.tickets.delete(params.ticket);
|
|
115
|
+
}
|
|
116
|
+
commit(params) {
|
|
117
|
+
const p = copy(params, 65_536);
|
|
118
|
+
if (!exact(p, ['protocol', 'ticket', 'expected', 'action']) || p.protocol !== ATOMIC_SCREEN_PROTOCOL || !hex(p.ticket) || !exact(p.expected, ['revision', 'stamp']) ||
|
|
119
|
+
!hex(p.expected.revision) || !hex(p.expected.stamp))
|
|
120
|
+
throw new Error('Invalid atomic commit');
|
|
121
|
+
const action = atomicScreenAction(p.action), prepared = this.tickets.get(p.ticket);
|
|
122
|
+
const refuse = (reason) => ({ protocol: ATOMIC_SCREEN_PROTOCOL, status: 'refused', reason });
|
|
123
|
+
if (!prepared)
|
|
124
|
+
return refuse('unknown');
|
|
125
|
+
this.tickets.delete(p.ticket); // One attempt, before any possible effect, even on mismatch/failure.
|
|
126
|
+
if (this.busy || this.poisoned)
|
|
127
|
+
return refuse('unavailable');
|
|
128
|
+
const expired = () => {
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
return prepared.expiresAt <= now || prepared.expiresAt > now + MAX_AGE || prepared.deadline <= process.hrtime.bigint();
|
|
131
|
+
};
|
|
132
|
+
if (expired())
|
|
133
|
+
return refuse('expired');
|
|
134
|
+
if (canonicalizeRfc8785(action) !== canonicalizeRfc8785(prepared.action) || p.expected.revision !== prepared.revision || p.expected.stamp !== prepared.stamp)
|
|
135
|
+
return refuse('mismatch');
|
|
136
|
+
if (this.revision !== prepared.revision)
|
|
137
|
+
return refuse('stale');
|
|
138
|
+
const evidence = screenResponseEvidence(`mcp__atomic__${action.name}`, action.arguments, `mcp__atomic__${this.observation.name}`, this.observation.arguments, this.snapshot);
|
|
139
|
+
if (!evidence || evidence.stamp !== prepared.stamp)
|
|
140
|
+
return refuse('stale');
|
|
141
|
+
const boundAction = copy(action);
|
|
142
|
+
if (expired())
|
|
143
|
+
return refuse('expired');
|
|
144
|
+
// No await/event-loop handoff between comparison and exact-target synchronous commit.
|
|
145
|
+
this.busy = true;
|
|
146
|
+
try {
|
|
147
|
+
const effect = this.apply(boundAction, evidence.target);
|
|
148
|
+
if (!exact(effect, ['result', 'snapshot']) || !record(effect.result) || !record(effect.snapshot))
|
|
149
|
+
throw new Error('Invalid synchronous atomic effect');
|
|
150
|
+
const result = copy(effect.result), snapshot = copy(effect.snapshot);
|
|
151
|
+
this.snapshot = snapshot;
|
|
152
|
+
this.revision = opaque();
|
|
153
|
+
return { protocol: ATOMIC_SCREEN_PROTOCOL, status: 'committed', revisionBefore: prepared.revision, revisionAfter: this.revision, stamp: prepared.stamp, result };
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
this.poisoned = true;
|
|
157
|
+
this.revision = opaque();
|
|
158
|
+
throw new Error('Atomic effect failed or is uncertain; reconcile the surface before authorizing again');
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
this.busy = false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=atomic-screen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-screen.js","sourceRoot":"","sources":["../src/atomic-screen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** UserPromptSubmit: remember what the user asked for, so destinations they named are theirs. */
|
|
2
|
+
/**
|
|
3
|
+
* The user's own words in a prompt: what is left after text they pasted or quoted. An address that appears only in
|
|
4
|
+
* a pasted block, a quoted line or the headers of a forwarded message was written by someone else, and naming it
|
|
5
|
+
* is what an attacker who mails the user would want. Asking once before writing to it is the price when the user
|
|
6
|
+
* meant it ("reply to the sender of this").
|
|
7
|
+
*/
|
|
8
|
+
export declare function ownWords(prompt: string): string;
|
|
9
|
+
export declare function flowPrompt(input: Record<string, unknown>, env?: NodeJS.ProcessEnv): void;
|
|
10
|
+
/** The sentence in which outside text addresses the model, from the instruction to the sentence's end. Not the
|
|
11
|
+
* sentences around it: in a list of reviews they are ordinary reviews, which an honest answer repeats. */
|
|
12
|
+
export declare function instructionText(text: string): string;
|
|
13
|
+
export declare function hintWindows(text: string): string[];
|
|
14
|
+
/** The text of the last assistant message: given directly, or read from the end of the Claude Code transcript. */
|
|
15
|
+
export declare function lastAnswer(input: Record<string, unknown>): string;
|
|
16
|
+
export interface FlowStopOutput {
|
|
17
|
+
systemMessage?: string;
|
|
18
|
+
decision?: 'block';
|
|
19
|
+
reason?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Stop: an injection that only changes the answer ("recommend this hotel") makes no call to refuse. Two levers are
|
|
23
|
+
* left. Under any answer whose turn read outside text, one line tells the user which sources it drew on. And when
|
|
24
|
+
* the answer repeats words from text that addressed the model with instructions, the answer goes back to the model
|
|
25
|
+
* once to be checked against the user's request (`decision: block` makes Claude Code continue); the user loses
|
|
26
|
+
* nothing but the time of that check.
|
|
27
|
+
*/
|
|
28
|
+
export declare function flowStop(input: Record<string, unknown>, env?: NodeJS.ProcessEnv): FlowStopOutput | undefined;
|
|
29
|
+
//# sourceMappingURL=flow-guard-answer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-guard-answer.d.ts","sourceRoot":"","sources":["../src/flow-guard-answer.ts"],"names":[],"mappings":"AAiBA,iGAAiG;AACjG;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAU/C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAqBrG;AAED;0GAC0G;AAC1G,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBpD;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAKlD;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAwBjE;AAED,MAAM,WAAW,cAAc;IAAG,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;AAE/F;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,cAAc,GAAG,SAAS,CA2CzH"}
|