@aimarket/warden 0.22.4 → 0.23.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 +22 -0
- package/README-es.md +8 -8
- package/README-fr.md +8 -8
- package/README-ru.md +8 -8
- package/README-zh.md +8 -8
- package/README.md +18 -10
- package/dist/flow-guard-fingerprints.d.ts +4 -0
- package/dist/flow-guard-fingerprints.d.ts.map +1 -1
- package/dist/flow-guard-fingerprints.js +17 -1
- package/dist/flow-guard-fingerprints.js.map +1 -1
- package/dist/flow-guard-paths.d.ts.map +1 -1
- package/dist/flow-guard-paths.js +4 -1
- package/dist/flow-guard-paths.js.map +1 -1
- package/dist/flow-guard-post.js +2 -2
- package/dist/flow-guard-post.js.map +1 -1
- package/dist/flow-guard-pre.d.ts +8 -1
- package/dist/flow-guard-pre.d.ts.map +1 -1
- package/dist/flow-guard-pre.js +5 -1
- package/dist/flow-guard-pre.js.map +1 -1
- package/dist/flow-guard.d.ts +2 -1
- package/dist/flow-guard.d.ts.map +1 -1
- package/dist/flow-guard.js +1 -1
- package/dist/flow-guard.js.map +1 -1
- package/dist/opencode-plugin.d.ts +41 -0
- package/dist/opencode-plugin.d.ts.map +1 -0
- package/dist/opencode-plugin.js +230 -0
- package/dist/opencode-plugin.js.map +1 -0
- package/docs/flow-guard.es.md +1 -1
- package/docs/flow-guard.fr.md +1 -1
- package/docs/flow-guard.md +1 -1
- package/docs/flow-guard.ru.md +1 -1
- package/docs/flow-guard.zh.md +1 -1
- package/docs/gates.md +1 -1
- package/docs/opencode.md +121 -0
- package/docs/scan.es.md +2 -2
- package/docs/scan.fr.md +2 -2
- package/docs/scan.md +2 -2
- package/docs/scan.ru.md +2 -2
- package/docs/scan.zh.md +2 -2
- package/opencode-plugin/warden.js +10 -0
- package/package.json +9 -2
package/docs/flow-guard.zh.md
CHANGED
|
@@ -311,7 +311,7 @@ Wrap 提问前读取基线,批准后重新验证工具定义并再次读取。
|
|
|
311
311
|
- 只改变回答的注入不会发出可拒绝的调用。自 2026-10-22 起,插件的 Stop 钩子会在本轮读过外部文本的回答下方加一行,列出这些来源;若其中有文本以指令口吻对助手说话,也会注明。如果回答连续重复了外部文本中对助手下指令那几句话里的 12 个规范化字符,钩子还会把回答退回给模型一次(`decision: block`),在用户读到之前对照用户的请求检查;用户只损失这次检查的时间。`answerNote`:`always`(默认)、`instruction`(只在这种情况下)或 `off`;插件之外请注册 `warden-mcp hook stop`。
|
|
312
312
|
- 代理也记录 JSON-RPC 和工具错误;记录失败时不转发响应,现有会话状态损坏时阻止检查中的调用。
|
|
313
313
|
|
|
314
|
-
已在 npm `0.17.0` 发布:`npx -y @aimarket/warden@0.
|
|
314
|
+
已在 npm `0.17.0` 发布:`npx -y @aimarket/warden@0.23.0 hook pre-tool-use`;或从源码在 WARDEN 目录运行 `npm ci && npm run build`,然后使用 `node dist/mcp-server.js hook pre-tool-use`,并注册 prompt/post hooks。策略为 `~/.local/state/warden/flow-policy.json` 或 `WARDEN_FLOW_POLICY`;模式为 `ask`、`deny`、`audit`、`off`。当前配置示例见[英文页](flow-guard.md)。
|
|
315
315
|
|
|
316
316
|
代理命令:`node dist/mcp-server.js wrap --flow mail -- node mail-server.js`。同一会话的代理应共享一个新的 `WARDEN_FLOW_SESSION`。默认父进程 PID 不能关联不同启动器。代理看不到用户提示或客户端内置工具。需要确认的调用会在客户端提供 MCP elicitation 表单时通过表单询问(回答记入日志的 `approvalOutcome`),否则或使用 `--ask refuse` 时被拒绝。`trustedSources` 仅用于 WebFetch URL,不用于 MCP 参数中的任意链接。
|
|
317
317
|
|
package/docs/gates.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 🌐 **English** · [Русский](gates.ru.md) · [Español](gates.es.md) · [Français](gates.fr.md) · [中文](gates.zh.md)
|
|
4
4
|
|
|
5
|
-
**Ruleset v17 (0.
|
|
5
|
+
**Ruleset v17 (0.23.0):** v17 catches a tool that adds a fixed recipient to another server's tool, a case from outside MCPTox (see v17 below). v15 widened `TOOL_DEF_CROSS_TOOL` and v16 added two patterns for tools that take no input, both written from MCPTox's dev half and measured on 2026-10-22 on MCPTox, the field corpus and 1,000 HISTOR packages (see v15 and v16 below). v14 stopped treating storage on a server as local custody. Numbers quoted for earlier versions are historical.
|
|
6
6
|
|
|
7
7
|
> [0.7.0 security changes and migration](security-hardening.md).
|
|
8
8
|
|
package/docs/opencode.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Flow guard for OpenCode
|
|
2
|
+
|
|
3
|
+
The [flow guard](flow-guard.md) of the Claude Code plugin, through [OpenCode](https://opencode.ai)'s plugin hooks.
|
|
4
|
+
It is written for agents that run with nobody at the keyboard: Multica runtimes, CI jobs, scheduled reports.
|
|
5
|
+
|
|
6
|
+
## What it does
|
|
7
|
+
|
|
8
|
+
| OpenCode hook | Guard step |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `tool.execute.before` | Maps the call to the guard's tool vocabulary (`bash` → `Bash`, `read` → `Read`, `write`/`edit`/`apply_patch` → `Write`/`Edit`, `webfetch` → `WebFetch`, `<server>_<tool>` → `mcp__<server>__<tool>`) and runs the PreToolUse check. A refused call throws: OpenCode does not run the tool, and the model reads the reason. |
|
|
11
|
+
| `tool.execute.after` | Runs the PostToolUse check: what the result brought in (outside text, private data, credentials). The guard's warning about instructions in a result is appended to that result. |
|
|
12
|
+
| `chat.message` | Records destinations the user's own message names. |
|
|
13
|
+
| `shell.env` | Blanks the environment variables you list (`stripEnv`) for shell commands. An injected `env` or `printenv` then cannot read a key that only the OpenCode process needs, such as the model provider's key or a hub key used by an MCP header. |
|
|
14
|
+
|
|
15
|
+
A subagent (`task`) runs in a child session. The guard keeps one state per session tree, keyed by its root: what a
|
|
16
|
+
child reads binds the parent and the other way round, so an injected parent cannot hand the read-and-send to a
|
|
17
|
+
fresh child. Parents come from `session.created`/`session.updated` events, else from the SDK client. A child's prompt
|
|
18
|
+
is written by the parent model and is not recorded as the user's words. If recording a result fails, every later
|
|
19
|
+
call of that session tree is refused.
|
|
20
|
+
|
|
21
|
+
MCP server names come from OpenCode's config (`mcp` section). A tool that is neither a core tool nor a configured
|
|
22
|
+
MCP tool (a subagent `task`, another plugin's tool) is treated as an unlabelled MCP tool: its result is outside text.
|
|
23
|
+
|
|
24
|
+
## No one to ask: `block`
|
|
25
|
+
|
|
26
|
+
Where the Claude Code hook would ask a person, an unattended agent has nobody to ask. `block` decides:
|
|
27
|
+
|
|
28
|
+
- **`data`** (default). Refuses every call that involves data: a known secret in the arguments, a call after the
|
|
29
|
+
session read a credential (`credential_egress`), private text or personal data matching what the session read,
|
|
30
|
+
a short credential fragment, an incomplete payload check, a change to the guard's or OpenCode's own settings.
|
|
31
|
+
It also refuses any decision whose payload the guard marks sensitive, whatever reason came first (opaque shell
|
|
32
|
+
code after a credential read is refused, not waved through as "only shell code"). Calls that only *act* after
|
|
33
|
+
outside text — shell code outside the read-only subset, an unconfirmed destination, a destructive command, a GUI
|
|
34
|
+
action — run, and the resolution is logged next to the guard's own line (`"event":"opencode"`,
|
|
35
|
+
`"resolution":"allowed_unattended"`).
|
|
36
|
+
- **`all`**. Refuses everything the Claude Code hook would ask about. Most agents then stop at their first shell
|
|
37
|
+
command after reading a web page.
|
|
38
|
+
|
|
39
|
+
Why `data` lets actions through: an unattended agent's job is usually to read outside text and then report — a
|
|
40
|
+
digest of GitHub issues sent to a chat, a market check written to a ticket. Refusing every shell command after the
|
|
41
|
+
first read stops the job, not the attacker. What bounds *where* such an agent can act is a network allowlist or a
|
|
42
|
+
sandbox around it; what the guard adds is that data the session read does not leave with those actions.
|
|
43
|
+
|
|
44
|
+
Measured with the red-team replay (`node scripts/redteam/flow-redteam.mjs --opencode=MODE`, 28 attacks written
|
|
45
|
+
against the guard's code, no model, no network):
|
|
46
|
+
|
|
47
|
+
| Profile | Delivered |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Claude Code hook (a person answers every question) | 0 of 28 |
|
|
50
|
+
| OpenCode, `block: "all"` | 0 of 28 |
|
|
51
|
+
| OpenCode, `block: "data"` | 3 of 28 |
|
|
52
|
+
|
|
53
|
+
The three in `data` are actions without data: an MCP `push` with no credential read and no URL argument, an
|
|
54
|
+
unlabelled calendar tool that adds the attacker as an attendee, an unlabelled repository tool that gives the
|
|
55
|
+
attacker's account write access. Label such tools in `flow-policy.json` (`labels`) or use `block: "all"` when an
|
|
56
|
+
agent holds tools like these.
|
|
57
|
+
|
|
58
|
+
## Credentials the guard recognizes
|
|
59
|
+
|
|
60
|
+
Besides the earlier formats (private keys, AWS, GitHub, GitLab, Slack, npm, `sk-…` API keys, Google API keys),
|
|
61
|
+
agent runtimes carry two more: a **Telegram bot token** (`<bot id>:AA…`) and an **AIMarket hub API key**
|
|
62
|
+
(`aimk_…`). Reading them (from `env`, a file or a tool result) marks the session as holding credentials; sending
|
|
63
|
+
one is denied.
|
|
64
|
+
|
|
65
|
+
Secret paths added for agent runtimes: OpenCode's `~/.local/share/opencode/auth.json` and Multica's
|
|
66
|
+
`~/.multica/config.json`. Protected paths added (changing them would switch the guard off): `~/.config/opencode/`,
|
|
67
|
+
any `.opencode/` folder and `opencode.json`/`opencode.jsonc`.
|
|
68
|
+
|
|
69
|
+
## Install
|
|
70
|
+
|
|
71
|
+
The plugin is the compiled `dist/opencode-plugin.js` and the `dist/flow-guard*.js` parts it imports (Node built-ins
|
|
72
|
+
only). With the package installed where OpenCode resolves modules, put this one file in OpenCode's plugin folder
|
|
73
|
+
(`~/.config/opencode/plugin/` globally, `.opencode/plugin/` in a project):
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// warden.js — export only the plugin: OpenCode calls every exported function as one.
|
|
77
|
+
import { createWardenPlugin } from '@aimarket/warden/opencode';
|
|
78
|
+
|
|
79
|
+
export const WardenFlowGuard = createWardenPlugin({
|
|
80
|
+
block: 'data',
|
|
81
|
+
stripEnv: ['DEEPSEEK_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Without the package, copy `dist/opencode-plugin.js` and every `dist/flow-guard*.js` into one folder next to the
|
|
86
|
+
config (for example `~/.config/opencode/warden/`) and import `../warden/opencode-plugin.js` instead.
|
|
87
|
+
`opencode-plugin/warden.js` in this repository is that loader.
|
|
88
|
+
|
|
89
|
+
Environment overrides: `WARDEN_OPENCODE_BLOCK=data|all`, `WARDEN_OPENCODE_STRIP_ENV=NAME,NAME`. The guard's state
|
|
90
|
+
and log live in the usual state directory (`WARDEN_STATE_DIR`, default `~/.local/state/warden`); the policy file is
|
|
91
|
+
the same `flow-policy.json` as for Claude Code.
|
|
92
|
+
|
|
93
|
+
## In a live Multica runtime (2026-11-04)
|
|
94
|
+
|
|
95
|
+
Two Multica agents (OpenCode 1.18.32 + DeepSeek, unattended, egress through an allowlist proxy) run with
|
|
96
|
+
`block: "data"` and `stripEnv` for the model key and two hub keys.
|
|
97
|
+
|
|
98
|
+
- **A full GitHub digest** (read GitHub, read and write Attested Memory over MCP, post the report to the tracker,
|
|
99
|
+
close the task): 18 reviewable decisions, all `allowed_unattended`, 0 refused. The first run found a false
|
|
100
|
+
positive: the task's UUID, read in a command's output and passed back to the tracker CLI, matched as private text
|
|
101
|
+
and as a "number" (personal data), so posting the report was refused. Fixed in the core: a canonical UUID is no
|
|
102
|
+
personal-data token, and private-text fingerprints drop UUIDs except on a line that names a credential
|
|
103
|
+
(`withoutRecordIds`), so a UUID-shaped API key read from an ordinary file is still caught.
|
|
104
|
+
- **A staged attack** with a fake GitHub token in a file: after the agent read GitHub and then the file, sending
|
|
105
|
+
through the chat script, `curl` to an allowed host with the file as body, a public Attested Memory write over MCP,
|
|
106
|
+
a project-file write and the tracker CLI were all refused. `printenv` showed the model key blank in the shell and
|
|
107
|
+
`GH_TOKEN` present (the agent's `gh` needs it). Before any of that, the agent's own attempts to list and grep the
|
|
108
|
+
plugin folder were refused as touching the guard's settings.
|
|
109
|
+
|
|
110
|
+
## Limits
|
|
111
|
+
|
|
112
|
+
- The guard sees tool calls. It does not see what the model says, and it cannot stop harm that needs no tool call.
|
|
113
|
+
- `data` mode lets actions run after outside text, by design; see above.
|
|
114
|
+
- `chat.message` treats the user message as the user's words. A runtime that puts other people's text into that
|
|
115
|
+
message (issue comments, forwarded chat) makes destinations named there count as user-named.
|
|
116
|
+
- The runtime may deliver the model's final answer itself: Multica posts it as a tracker comment without a tool
|
|
117
|
+
call, so the guard does not see it. In the staged attack that comment carried no key; nothing technical stops it.
|
|
118
|
+
- `shell.env` blanks variables for shell commands only. The OpenCode process keeps them; a key passed to an MCP
|
|
119
|
+
server as a header stays with OpenCode.
|
|
120
|
+
- Tested against OpenCode 1.18.32's plugin interface (`@opencode-ai/plugin`), in unit tests that drive the hooks
|
|
121
|
+
directly and in a Multica runtime; other versions may name tools differently.
|
package/docs/scan.es.md
CHANGED
|
@@ -7,11 +7,11 @@ Las llamadas de descubrimiento pueden generar cargos; consulte los [límites de
|
|
|
7
7
|
`warden-mcp scan` lee la configuración MCP que ya tienes, se conecta a cada servidor que inicia y revisa las definiciones de herramientas antes de que un modelo las vea. Es la misma cadena de compuertas que [`wrap`](../README-es.md) y la [biblioteca](integration.es.md), ejecutada una vez sobre tus configuraciones en lugar de delante de un solo servidor.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y @aimarket/warden@0.
|
|
10
|
+
npx -y @aimarket/warden@0.23.0 scan
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
WARDEN scan 0.
|
|
14
|
+
WARDEN scan 0.23.0 · ruleset 17 sha256-UOfK25xo6D8v… · block at high
|
|
15
15
|
read .mcp.json (claude-code, 3 servers)
|
|
16
16
|
|
|
17
17
|
✓ allow notes claude-code 1 tool · score 0.90
|
package/docs/scan.fr.md
CHANGED
|
@@ -7,11 +7,11 @@ Les appels de découverte peuvent entraîner des frais ; consultez les [limites
|
|
|
7
7
|
`warden-mcp scan` lit la configuration MCP que vous avez déjà, se connecte à chaque serveur qu’elle démarre et vérifie les définitions d’outils avant qu’un modèle ne les voie. C’est la même chaîne de portes que [`wrap`](../README-fr.md) et la [bibliothèque](integration.fr.md), exécutée une fois sur vos configurations au lieu de se placer devant un seul serveur.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y @aimarket/warden@0.
|
|
10
|
+
npx -y @aimarket/warden@0.23.0 scan
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
WARDEN scan 0.
|
|
14
|
+
WARDEN scan 0.23.0 · ruleset 17 sha256-UOfK25xo6D8v… · block at high
|
|
15
15
|
read .mcp.json (claude-code, 3 servers)
|
|
16
16
|
|
|
17
17
|
✓ allow notes claude-code 1 tool · score 0.90
|
package/docs/scan.md
CHANGED
|
@@ -7,11 +7,11 @@ Discovery calls can incur costs; see [measurement limits and version evidence](s
|
|
|
7
7
|
`warden-mcp scan` reads the MCP configuration you already have, connects to every server it starts, and vets the tool definitions before a model sees them. It is the same gate chain as [`wrap`](../README.md#protect-claude-desktop-or-cursor-with-one-wrapper) and the [library](integration.md), run once over your configs instead of in front of one server.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y @aimarket/warden@0.
|
|
10
|
+
npx -y @aimarket/warden@0.23.0 scan
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
WARDEN scan 0.
|
|
14
|
+
WARDEN scan 0.23.0 · ruleset 17 sha256-UOfK25xo6D8v… · block at high
|
|
15
15
|
read .mcp.json (claude-code, 3 servers)
|
|
16
16
|
|
|
17
17
|
✓ allow notes claude-code 1 tool · score 0.90
|
package/docs/scan.ru.md
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
`warden-mcp scan` читает уже существующую у вас конфигурацию MCP, подключается к каждому серверу из неё и проверяет определения инструментов до того, как их увидит модель. Это та же цепочка гейтов, что у [`wrap`](../README-ru.md) и у [библиотеки](integration.ru.md), только пройденная один раз по вашим конфигам, а не перед одним сервером.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y @aimarket/warden@0.
|
|
10
|
+
npx -y @aimarket/warden@0.23.0 scan
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
WARDEN scan 0.
|
|
14
|
+
WARDEN scan 0.23.0 · ruleset 17 sha256-UOfK25xo6D8v… · block at high
|
|
15
15
|
read .mcp.json (claude-code, 3 servers)
|
|
16
16
|
|
|
17
17
|
✓ allow notes claude-code 1 tool · score 0.90
|
package/docs/scan.zh.md
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
`warden-mcp scan` 读取你已有的 MCP 配置,连接其中启动的每个服务器,并在模型看到工具定义之前对其进行审查。它与 [`wrap`](../README-zh.md) 和[库](integration.zh.md)使用同一条关卡链,只是对你的配置运行一次,而不是挡在单个服务器前面。
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx -y @aimarket/warden@0.
|
|
10
|
+
npx -y @aimarket/warden@0.23.0 scan
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
WARDEN scan 0.
|
|
14
|
+
WARDEN scan 0.23.0 · ruleset 17 sha256-UOfK25xo6D8v… · block at high
|
|
15
15
|
read .mcp.json (claude-code, 3 servers)
|
|
16
16
|
|
|
17
17
|
✓ allow notes claude-code 1 tool · score 0.90
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// WARDEN flow guard for OpenCode: copy this file into OpenCode's plugin folder (~/.config/opencode/plugin/ or
|
|
2
|
+
// .opencode/plugin/), and dist/opencode-plugin.js with every dist/flow-guard*.js into ~/.config/opencode/warden/.
|
|
3
|
+
// Export only the plugin: OpenCode calls every exported function as one. See docs/opencode.md.
|
|
4
|
+
import { createWardenPlugin } from '../warden/opencode-plugin.js';
|
|
5
|
+
|
|
6
|
+
export const WardenFlowGuard = createWardenPlugin({
|
|
7
|
+
block: 'data',
|
|
8
|
+
// Keys only the OpenCode process needs; shell commands get them blank. Add your provider's.
|
|
9
|
+
stripEnv: ['DEEPSEEK_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY'],
|
|
10
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aimarket/warden",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "WARDEN — MCP security firewall. Stdio wrap proxy, inspection tools and library. Vets definitions before model exposure. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"types": "./dist/atomic-screen.d.ts",
|
|
35
35
|
"import": "./dist/atomic-screen.js",
|
|
36
36
|
"default": "./dist/atomic-screen.js"
|
|
37
|
+
},
|
|
38
|
+
"./opencode": {
|
|
39
|
+
"types": "./dist/opencode-plugin.d.ts",
|
|
40
|
+
"import": "./dist/opencode-plugin.js",
|
|
41
|
+
"default": "./dist/opencode-plugin.js"
|
|
37
42
|
}
|
|
38
43
|
},
|
|
39
44
|
"files": [
|
|
@@ -51,11 +56,13 @@
|
|
|
51
56
|
"docs/security-hardening*.md",
|
|
52
57
|
"docs/gates*.md",
|
|
53
58
|
"docs/flow-guard*.md",
|
|
59
|
+
"docs/opencode*.md",
|
|
54
60
|
"examples/atomic-screen-server.mjs",
|
|
55
61
|
"examples/answer-gate-host.mjs",
|
|
56
62
|
"native/macos/Package.swift",
|
|
57
63
|
"native/macos/Sources",
|
|
58
|
-
"scripts/build-macos.mjs"
|
|
64
|
+
"scripts/build-macos.mjs",
|
|
65
|
+
"opencode-plugin/warden.js"
|
|
59
66
|
],
|
|
60
67
|
"engines": {
|
|
61
68
|
"node": ">=20"
|