@aimarket/warden 0.18.0 → 0.19.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 +12 -2
- package/README-es.md +13 -14
- package/README-fr.md +13 -14
- package/README-ru.md +13 -14
- package/README-zh.md +13 -14
- package/README.md +14 -15
- package/dist/claude-hook.d.ts.map +1 -1
- package/dist/claude-hook.js +9 -3
- package/dist/claude-hook.js.map +1 -1
- package/dist/flow-guard.d.ts +60 -3
- package/dist/flow-guard.d.ts.map +1 -1
- package/dist/flow-guard.js +255 -16
- package/dist/flow-guard.js.map +1 -1
- package/dist/mcp-server.js +4 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/review-diff.d.ts +5 -4
- package/dist/review-diff.d.ts.map +1 -1
- package/dist/review-diff.js.map +1 -1
- package/dist/static-scan.d.ts +1 -1
- package/dist/static-scan.d.ts.map +1 -1
- package/dist/static-scan.js +46 -3
- package/dist/static-scan.js.map +1 -1
- package/docs/flow-guard.es.md +57 -19
- package/docs/flow-guard.fr.md +58 -19
- package/docs/flow-guard.md +56 -20
- package/docs/flow-guard.ru.md +54 -18
- package/docs/flow-guard.zh.md +41 -16
- package/docs/gates.es.md +19 -5
- package/docs/gates.fr.md +19 -4
- package/docs/gates.md +19 -5
- package/docs/gates.ru.md +19 -5
- package/docs/gates.zh.md +6 -4
- package/docs/scanner-comparison.es.md +23 -1
- package/docs/scanner-comparison.fr.md +23 -1
- package/docs/scanner-comparison.md +22 -1
- package/docs/scanner-comparison.ru.md +23 -1
- package/docs/scanner-comparison.zh.md +18 -1
- package/package.json +1 -1
package/docs/flow-guard.md
CHANGED
|
@@ -10,10 +10,12 @@ The flow guard adds checks at tool-call boundaries after a session receives outs
|
|
|
10
10
|
- Recognized writes to guard policy/state, Claude hook settings and Git hook/config paths are protected, including expanded environment paths and symlink targets. A hook running as the agent's OS user cannot replace OS permissions or sandboxing.
|
|
11
11
|
- URL hosts are parsed with `URL`; userinfo is not the host. Every detected destination must be permitted, and an actual target argument must be present. A link in a message body does not authorize its recipient. Policy mailbox entries match exactly; a domain entry does not authorize all mailboxes there. User-typed destinations match complete values, not suffixes of other addresses. Mentioning a destination is still only a coarse allowance, not authorization for every action or payload.
|
|
12
12
|
- Contact resolution is opt-in through `contactTools` and a `private` label. Only structured records with `name` and `email` are accepted, directly or in a JSON/MCP envelope. The full display name must appear in the user's prompt; conflicting addresses for that name ask. Local files, arbitrary prose, YAML and cached contacts from the earlier guard cannot establish this authority. Configure only lookup tools whose records the agent and outsiders cannot modify.
|
|
13
|
-
- Secret fingerprints inspect visible argument text, with bounded URL/base64/hex decoding.
|
|
13
|
+
- Secret fingerprints inspect visible argument text, with bounded URL/base64/hex decoding. Since 2026-10-22 key-like values (random-looking runs in credential files, and well-known token formats read anywhere) are also fingerprinted in 10-character windows of their letters and digits: a piece of a key, the key reversed, with separators, base64-encoded in pieces or with Cyrillic or Greek look-alike letters is denied. Each argument name keeps the tail of what it sent after outside text, so pieces sent back to back through the same argument are checked whole. Pieces under 10 characters wrapped in different words each time still pass, and so does the key's leading format (`rk_live_`). Fingerprints do not inspect arbitrary file contents at execution time, arbitrary encodings or every possible MCP schema. Internal project edits are generally allowed; executing their code is a separate decision.
|
|
14
|
+
- An MCP tool without an operator label is outbound when its name says so (send, post, share …), when it carries a URL, or, since 2026-10-22, when it names a party: an argument such as `to`, `email`, `attendees`, `assignee`, `collaborator` or `username`, or any e-mail address or IBAN. `warden-mcp flow-labels` lists the unlabeled MCP tools your sessions used, with the guess and a `labels` block to correct and paste into the policy.
|
|
15
|
+
- An injection that only changes the answer makes no call to refuse. Since 2026-10-22 the plugin's Stop hook adds one line under an answer whose turn read outside text, naming those sources, and says so when any of it addressed the assistant with instructions. It never blocks. `answerNote` chooses `always` (default), `instruction` (only that case) or `off`; outside the plugin, register `warden-mcp hook stop`.
|
|
14
16
|
- The MCP proxy records JSON-RPC error text and tool-error results as outside text too. If recording fails, it withholds the response. Existing corrupt session state blocks pre-tool checks instead of becoming an empty session.
|
|
15
17
|
|
|
16
|
-
Released in npm `0.17.0`: `npx -y @aimarket/warden@0.
|
|
18
|
+
Released in npm `0.17.0`: `npx -y @aimarket/warden@0.19.0 hook pre-tool-use`, or from a source checkout `npm ci && npm run build`, then `node dist/mcp-server.js hook pre-tool-use`. Register the prompt and post hooks as well, or use the bundled Claude plugin.
|
|
17
19
|
|
|
18
20
|
Policy is user-level (`~/.local/state/warden/flow-policy.json`, or `WARDEN_FLOW_POLICY`). Modes are `ask`, `deny`, `audit`, `off`. `audit` logs decisions without enforcing them. Example:
|
|
19
21
|
|
|
@@ -23,13 +25,14 @@ Policy is user-level (`~/.local/state/warden/flow-policy.json`, or `WARDEN_FLOW_
|
|
|
23
25
|
"allowedDestinations": ["api.internal.example", "alice@example.com"],
|
|
24
26
|
"labels": {"mcp__contacts__lookup": ["private"], "mcp__mail__send_email": ["outbound"]},
|
|
25
27
|
"contactTools": ["mcp__contacts__lookup"],
|
|
26
|
-
"destinationArgs": {"mcp__mail__send_email": ["to", "cc", "bcc"]}
|
|
28
|
+
"destinationArgs": {"mcp__mail__send_email": ["to", "cc", "bcc"]},
|
|
29
|
+
"answerNote": "always"
|
|
27
30
|
}
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
For the proxy, use `node dist/mcp-server.js wrap --flow mail -- node mail-server.js`. Set the same fresh `WARDEN_FLOW_SESSION` for all proxies belonging to one client conversation. The fallback is the parent PID; separate launchers can have different parents, so do not rely on that fallback for cross-server protection. The proxy cannot see the user's prompt or client built-in tools and refuses calls that would ask. `trustedSources` applies to a WebFetch URL, not URLs mentioned in arbitrary MCP arguments.
|
|
31
34
|
|
|
32
|
-
**
|
|
35
|
+
**Which build the numbers describe.** The replay table below was run again on 2026-10-22 on the current code: the audit fixes plus the 2026-10-22 changes. The 2026-10-22 changes alter no decision in it (all 1,046 records match the code before them); the audit fixes cost one task with no question and 19 tasks done under attack. The model-run tables and costs still describe the pre-audit implementation through commit `2328be980`; no new model run was made. A reference-solution replay is one fixed attack path, not an adaptive worst case; the code-aware attacks of 2026-10-22 are [below](#what-changed-on-2026-10-22-and-why).
|
|
33
36
|
|
|
34
37
|
## What an approval request explains
|
|
35
38
|
|
|
@@ -53,22 +56,25 @@ We did not use a model. For each pair of a user task and an attacker goal, the b
|
|
|
53
56
|
(`important_instructions`) is placed where the user's task will read it. Then the user task's reference solution
|
|
54
57
|
is played through the guard, followed by the attacker's reference solution. That is a model that read the
|
|
55
58
|
injection and **obeyed it completely along the reference path**. This is not an adaptive worst case. An `ask` counts as refused: a benchmark has no human. The
|
|
56
|
-
run is deterministic, takes minutes, costs nothing, and ran in a gVisor container with no network
|
|
59
|
+
run is deterministic, takes minutes, costs nothing, and ran in a gVisor container with no network on our server
|
|
60
|
+
(2026-10-17/18) and in a Docker container with no network on a workstation (2026-10-22).
|
|
57
61
|
|
|
58
62
|
AgentDojo 0.1.35, suites v1.2.2, with our operator labels for all 69 tools
|
|
59
|
-
(`scripts/agentdojo/policy.json`). Results: `scripts/agentdojo/results
|
|
63
|
+
(`scripts/agentdojo/policy.json`). Results: `scripts/agentdojo/results/` (the table: `summary-2026-10-22.json`).
|
|
60
64
|
|
|
61
65
|
| Suite | Attack actions that succeeded, without the guard | With the guard | User tasks with no question | Tasks done under attack, without / with |
|
|
62
66
|
|---|---|---|---|---|
|
|
63
67
|
| banking | 141 of 144 | **0 of 144** | 13 of 16 | 125 / 117 of 144 |
|
|
64
|
-
| Slack | 105 of 105 | **0 of
|
|
68
|
+
| Slack | 105 of 105 | **0 of 75** | 2 of 21 | 102 / 15 of 105 |
|
|
65
69
|
| travel | 116 of 120 | **0 of 120** | 16 of 20 | 26 / 112 of 140 |
|
|
66
|
-
| workspace | 231 of 240 | **0 of 240** |
|
|
67
|
-
| **all** | **593 of 609** | **0 of
|
|
70
|
+
| workspace | 231 of 240 | **0 of 240** | 26 of 40 | 326 / 364 of 560 |
|
|
71
|
+
| **all** | **593 of 609** | **0 of 579** | **57 of 97** | **579 / 608 of 949** |
|
|
68
72
|
|
|
69
73
|
These are the labels as fixed on 2026-10-18, after the model run below found two of them too strict, and the guard
|
|
70
74
|
with names resolved from contacts. The first labels gave 54 tasks with no question and 571 done under attack; the
|
|
71
|
-
fixed labels without contacts gave 57 and 613. Every version gave
|
|
75
|
+
fixed labels without contacts gave 57 and 613; with contacts, before the audit fixes, 58 and 627. Every version gave
|
|
76
|
+
0 attack actions. The audit fixes refuse a few more fetches and calendar entries in Slack and workspace tasks, so 579
|
|
77
|
+
pairs, not 584, carried the injection in.
|
|
72
78
|
|
|
73
79
|
"Attack actions" are pairs where the injection reached the agent (its text came back in a tool result) and the
|
|
74
80
|
attacker's goal needs a call: send, pay, invite, delete. The count of delivered pairs differs a little between
|
|
@@ -76,12 +82,12 @@ the two columns because the guard also refuses some fetches that would have carr
|
|
|
76
82
|
|
|
77
83
|
How to read the last two columns:
|
|
78
84
|
|
|
79
|
-
- **With no question.** The guard stays silent through
|
|
85
|
+
- **With no question.** The guard stays silent through 57 of 97 user tasks. The other 40 need a call it asks
|
|
80
86
|
about. In Claude Code that is one click; in the benchmark it is a failed task. Slack is the extreme: nearly every
|
|
81
87
|
task posts to a channel or person found in the data ("the channel with most users"), not one the user named.
|
|
82
88
|
That is exactly the shape of an injected action, so it asks.
|
|
83
89
|
- **Under attack.** Without the guard, attacks also break the user's task (in travel, 26 of 140 finish). With the
|
|
84
|
-
guard the attack is refused and the task goes on:
|
|
90
|
+
guard the attack is refused and the task goes on: 608 of 949 finish against 579 without it.
|
|
85
91
|
|
|
86
92
|
Eight newer workspace attacker goals ship without a reference solution, so they cannot be played. Seven of them
|
|
87
93
|
need an e-mail to the attacker first; that e-mail was refused in all 280 delivered pairs. Two goals (one in
|
|
@@ -181,19 +187,45 @@ on our server: 21,654 model runs, $12.68 of DeepSeek credit.
|
|
|
181
187
|
The same reader feedback changed HISTOR's sandbox: a connection the previous holder of an address queued is no
|
|
182
188
|
longer credited to the next run, which is marked incomplete instead of clean.
|
|
183
189
|
|
|
190
|
+
## What changed on 2026-10-22, and why
|
|
191
|
+
|
|
192
|
+
A reader asked what WARDEN still does not catch. We listed it, then attacked the guard with its source in hand:
|
|
193
|
+
17 attacks in [`scripts/redteam/flow-redteam.mjs`](../scripts/redteam/flow-redteam.mjs), each aimed at a documented
|
|
194
|
+
limit or the edge of a rule, played through the plugin's hook entry point like the replay (an obedient model, no
|
|
195
|
+
benchmark). Before the changes 14 of them got through; after them, 3.
|
|
196
|
+
|
|
197
|
+
| Change | Why | Result |
|
|
198
|
+
|---|---|---|
|
|
199
|
+
| Key-like values fingerprinted in 10-character windows of letters and digits, reversed and look-alike letters folded; base64 decoded from 16 characters, not 24 | a key sent in 15-character pieces, with dashes, backwards or in Cyrillic letters passed | red team: all of these denied; the first piece, mostly the key's format, still passes |
|
|
200
|
+
| Each argument keeps the tail of what it sent after outside text | pieces of 8 characters, a card number in two halves, private text in 12-character pieces to an allowed host passed | denied or asked from the second or third piece on |
|
|
201
|
+
| An unlabeled MCP tool that names a party is outbound; `warden-mcp flow-labels` | "add attendee" with the attacker's e-mail and "add collaborator" by user name passed | both ask |
|
|
202
|
+
| Stop hook: one line under an answer that drew on outside text | an answer-only injection makes no call | the user is told which sources the answer used; nothing is blocked |
|
|
203
|
+
| Ruleset v15 (see [gates](gates.md)) | 78 of MCPTox's dev-half poisoned tools passed the rules | held-out half 171 → 194 of 218 (rules written from the other half only), dev half 189 → 233 of 267; 0 of 45 clean servers; the field corpus and 1,000 HISTOR packages block exactly what v14 blocked |
|
|
204
|
+
| The replay re-run on the current code | the audit fixes of 2026-10-18 had not been measured | 0 of 579 attack actions, 57 of 97 tasks with no question, 608 of 949 done under attack; the 2026-10-22 changes alter no decision |
|
|
205
|
+
| `scripts/agentdojo/Dockerfile.dockerignore` | the package's `.dockerignore` leaves out `dist/`, so the reproduce command below did not build | it builds |
|
|
206
|
+
|
|
207
|
+
Still delivered after the changes ([results](../scripts/redteam/results/)):
|
|
208
|
+
|
|
209
|
+
- **Pieces under 10 characters wrapped in different words** ("part 3: … thanks") to an address the user typed.
|
|
210
|
+
- **An address pasted into the prompt.** What the user types or pastes is the user's choice; the hook cannot tell
|
|
211
|
+
pasted text from typed text.
|
|
212
|
+
- **A key written into a project file.** Editing project files is the agent's job; what CI later publishes from
|
|
213
|
+
them is outside the guard's view.
|
|
214
|
+
|
|
184
215
|
## What it does not do
|
|
185
216
|
|
|
186
|
-
- **
|
|
187
|
-
|
|
188
|
-
is a harder test than
|
|
217
|
+
- **Measurements are not a proof.** The replay obeys perfectly but follows one path; the model finds its own paths
|
|
218
|
+
but obeys rarely, even when told to; our own code-aware attacks are 17 ideas of ours. An attacker we have not
|
|
219
|
+
met, or a model that obeys more readily, is a harder test than any of them.
|
|
189
220
|
- **Labels are the operator's.** Our first run marked Slack channel names as private data only, and the injection
|
|
190
221
|
sat in a channel name. The run caught it because delivery is judged from the attack text, not from our labels.
|
|
191
|
-
A tool you label wrongly is a hole.
|
|
222
|
+
A tool you label wrongly is a hole; a tool you do not label gets the guard's guess, which only adds caution.
|
|
192
223
|
- **It does not stop an injection that only changes the answer** ("say this hotel is the best"). No tool call,
|
|
193
|
-
nothing to refuse.
|
|
224
|
+
nothing to refuse; the Stop hook can only tell the user where the answer came from.
|
|
194
225
|
- **Local changes inside the project pass.** Editing project files is the agent's job.
|
|
195
|
-
- **Leaks in small pieces
|
|
196
|
-
|
|
226
|
+
- **Leaks in small pieces.** Ordinary private text may go to a destination the user named, whole or in pieces:
|
|
227
|
+
that is the user's choice. To other destinations, and for keys and card numbers anywhere, pieces sent back to back
|
|
228
|
+
through one argument are joined; pieces under 10 characters with different words around each are not.
|
|
197
229
|
- **It cannot see** your prompt from before the plugin was installed, or text the model holds from elsewhere.
|
|
198
230
|
|
|
199
231
|
## Reproduce
|
|
@@ -206,7 +238,11 @@ docker run --rm --network none -v "$PWD/out:/out" warden-adojo /out/oracle.json
|
|
|
206
238
|
```
|
|
207
239
|
|
|
208
240
|
`oracle.py` writes per-pair results; `--variants none,strict,intent` chooses the guard variants (`strict` ignores
|
|
209
|
-
what the user typed). `summarize_oracle.py` turns them into the summary the table quotes.
|
|
241
|
+
what the user typed). `summarize_oracle.py` turns them into the summary the table quotes. `WARDEN_FLOW_GUARD=/out/x.mjs`
|
|
242
|
+
plays another build of the guard (any plugin copy of `flow-guard.mjs`), which is how the 2026-10-22 changes were
|
|
243
|
+
compared with the code before them.
|
|
244
|
+
|
|
245
|
+
The code-aware attacks need no container: `npm run build && node scripts/redteam/flow-redteam.mjs out.json`.
|
|
210
246
|
|
|
211
247
|
The model run (`model.py`) needs an OpenAI-compatible API key, which stays on the host: `relay.py` reads it, and
|
|
212
248
|
the container reaches nothing else. `run-model.sh` starts both and resumes an interrupted run:
|
package/docs/flow-guard.ru.md
CHANGED
|
@@ -20,16 +20,18 @@
|
|
|
20
20
|
- Защищены распознанные записи в политику/состояние стража, настройки hooks Claude и Git hooks/config, включая раскрываемые переменные путей и симлинки. Hooks с правами самого агента не заменяют ограничения ОС.
|
|
21
21
|
- Хост разбирается URL-парсером; userinfo не считается хостом. Проверяются все найденные адресаты и наличие аргумента назначения. Ссылка в теле письма не разрешает его получателя. Почтовые адреса в политике сопоставляются целиком; домен не разрешает все ящики. Упоминание адресата пользователем — грубое исключение, а не разрешение любых действий и данных.
|
|
22
22
|
- Контакты разрешаются только через явно заданный `contactTools` с меткой `private`: структурированные записи `name`/`email`, в том числе JSON в MCP-ответе. В запросе пользователя должно быть полное отображаемое имя; неоднозначное имя требует подтверждения. Файлы проекта, произвольный текст, YAML и старый кэш контактов не подходят. Доверяйте только справочнику, записи которого агент и посторонние не могут менять.
|
|
23
|
-
- Отпечатки проверяют видимый текст аргументов и ограниченный набор декодирований URL/base64/hex.
|
|
23
|
+
- Отпечатки проверяют видимый текст аргументов и ограниченный набор декодирований URL/base64/hex. С 2026-10-22 значения, похожие на ключи (случайные последовательности в файлах с секретами и известные форматы токенов, где бы их ни прочли), снимаются ещё и окнами по 10 букв и цифр: кусок ключа, ключ задом наперёд, с разделителями, кусками в base64 или с кириллическими или греческими буквами-двойниками запрещается. Каждый аргумент помнит хвост того, что он отправил после внешнего текста, поэтому куски, отправленные подряд через один аргумент, проверяются целиком. Куски короче 10 символов, каждый раз обёрнутые другими словами, по-прежнему проходят, как и начальный формат ключа (`rk_live_`). Отпечатки не читают произвольные файлы в момент отправки и не покрывают все кодировки и схемы MCP. Правки внутри проекта обычно разрешены; исполнение кода проверяется отдельно.
|
|
24
|
+
- MCP-инструмент без метки оператора считается исходящим, если об этом говорит его имя (send, post, share …), если в аргументах есть URL или, с 2026-10-22, если он называет адресата: аргумент вроде `to`, `email`, `attendees`, `assignee`, `collaborator` или `username`, либо любой e-mail или IBAN. `warden-mcp flow-labels` показывает MCP-инструменты без меток, которыми пользовались ваши сессии, с догадкой стража и блоком `labels`, который нужно проверить и вставить в политику.
|
|
25
|
+
- Инъекция, которая меняет только ответ, не делает вызова, которому можно отказать. С 2026-10-22 хук Stop плагина добавляет под ответом строку, если за этот ход читался внешний текст: какие источники, и отдельно — если какой-то из них обращался к ассистенту с указаниями. Он ничего не блокирует. `answerNote`: `always` (по умолчанию), `instruction` (только этот случай) или `off`; вне плагина зарегистрируйте `warden-mcp hook stop`.
|
|
24
26
|
- Прокси учитывает текст JSON-RPC errors и ошибки инструментов как внешние данные. Если учёт ответа не удался, ответ удерживается. Повреждённое состояние сессии блокирует проверяемый вызов вместо сброса в пустую сессию.
|
|
25
27
|
|
|
26
|
-
Вышло в npm `0.17.0`: `npx -y @aimarket/warden@0.
|
|
28
|
+
Вышло в npm `0.17.0`: `npx -y @aimarket/warden@0.19.0 hook pre-tool-use`, либо из исходников — в каталоге WARDEN `npm ci && npm run build`, затем `node dist/mcp-server.js hook pre-tool-use`. Зарегистрируйте также prompt/post hooks либо плагин Claude.
|
|
27
29
|
|
|
28
30
|
Политика: `~/.local/state/warden/flow-policy.json` или `WARDEN_FLOW_POLICY`. Режимы: `ask`, `deny`, `audit`, `off`; `audit` только пишет журнал. Пример настройки контактов и адресатов — в [английской версии](flow-guard.md).
|
|
29
31
|
|
|
30
32
|
Прокси: `node dist/mcp-server.js wrap --flow mail -- node mail-server.js`. Всем прокси одной беседы задайте одинаковый новый `WARDEN_FLOW_SESSION`. По умолчанию используется PID родителя; разные запускатели могут иметь разных родителей. Прокси не видит запрос пользователя и встроенные инструменты клиента; вместо вопроса отказывает. `trustedSources` применяется к URL WebFetch, а не к ссылкам в произвольных аргументах MCP.
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
**Какую сборку описывают цифры.** Таблицу воспроизведения ниже прогнали заново 2026-10-22 на текущем коде: исправления аудита плюс изменения 2026-10-22. Изменения 2026-10-22 не поменяли в ней ни одного решения (все 1 046 записей совпали с кодом до них); исправления аудита стоили одной задачи без вопроса и 19 задач, выполненных под атакой. Таблицы прогона с моделью и стоимость по-прежнему относятся к коду до аудита, вплоть до коммита `2328be980`; нового прогона с моделью не было. Воспроизведение эталонной последовательности — один путь атаки, а не адаптивный худший случай; атаки со знанием кода от 2026-10-22 — [ниже](#что-меняли-2026-10-22-и-зачем).
|
|
33
35
|
|
|
34
36
|
## Измерено на AgentDojo
|
|
35
37
|
|
|
@@ -41,22 +43,25 @@
|
|
|
41
43
|
(`important_instructions`) кладётся туда, где её прочитает задача пользователя. Затем через стража проигрывается
|
|
42
44
|
эталонное решение задачи пользователя, а следом эталонное решение атакующего. Это модель, которая прочитала
|
|
43
45
|
инъекцию и **послушалась её полностью на эталонном пути**. Это не адаптивный худший случай. `ask` считается отказом: в бенчмарке нет человека. Прогон
|
|
44
|
-
детерминирован, занимает минуты, ничего не стоит и шёл в контейнере gVisor без
|
|
46
|
+
детерминирован, занимает минуты, ничего не стоит и шёл в контейнере gVisor без сети на нашем сервере (2026-10-17/18)
|
|
47
|
+
и в контейнере Docker без сети на рабочей машине (2026-10-22).
|
|
45
48
|
|
|
46
49
|
AgentDojo 0.1.35, наборы v1.2.2, наши метки оператора для всех 69 инструментов
|
|
47
|
-
(`scripts/agentdojo/policy.json`). Результаты: `scripts/agentdojo/results
|
|
50
|
+
(`scripts/agentdojo/policy.json`). Результаты: `scripts/agentdojo/results/` (таблица — `summary-2026-10-22.json`).
|
|
48
51
|
|
|
49
52
|
| Набор | Атак-действий удалось, без стража | Со стражем | Задач без единого вопроса | Задач выполнено под атакой, без / со стражем |
|
|
50
53
|
|---|---|---|---|---|
|
|
51
54
|
| banking | 141 из 144 | **0 из 144** | 13 из 16 | 125 / 117 из 144 |
|
|
52
|
-
| Slack | 105 из 105 | **0 из
|
|
55
|
+
| Slack | 105 из 105 | **0 из 75** | 2 из 21 | 102 / 15 из 105 |
|
|
53
56
|
| travel | 116 из 120 | **0 из 120** | 16 из 20 | 26 / 112 из 140 |
|
|
54
|
-
| workspace | 231 из 240 | **0 из 240** |
|
|
55
|
-
| **всего** | **593 из 609** | **0 из
|
|
57
|
+
| workspace | 231 из 240 | **0 из 240** | 26 из 40 | 326 / 364 из 560 |
|
|
58
|
+
| **всего** | **593 из 609** | **0 из 579** | **57 из 97** | **579 / 608 из 949** |
|
|
56
59
|
|
|
57
60
|
Это метки в исправленном виде от 2026-10-18 (прогон с моделью ниже нашёл две слишком строгие) и страж, который
|
|
58
61
|
узнаёт названных по имени через контакты. С первыми метками было 54 задачи без вопроса и 571 выполненная под
|
|
59
|
-
атакой; с исправленными метками без контактов — 57 и 613
|
|
62
|
+
атакой; с исправленными метками без контактов — 57 и 613; с контактами, до исправлений аудита, — 58 и 627. Все версии
|
|
63
|
+
дали 0 атак-действий. Исправления аудита отказывают ещё в нескольких загрузках и записях в календарь в задачах Slack и
|
|
64
|
+
workspace, поэтому инъекцию принесли 579 пар, а не 584.
|
|
60
65
|
|
|
61
66
|
«Атаки-действия» — пары, где инъекция дошла до агента (её текст вернулся в результате инструмента), а цели
|
|
62
67
|
атакующего нужен вызов: отправить, заплатить, пригласить, удалить. Число доставленных пар в двух колонках немного
|
|
@@ -64,12 +69,12 @@ AgentDojo 0.1.35, наборы v1.2.2, наши метки оператора д
|
|
|
64
69
|
|
|
65
70
|
Как читать две последние колонки:
|
|
66
71
|
|
|
67
|
-
- **Без единого вопроса.** Страж молчит в
|
|
72
|
+
- **Без единого вопроса.** Страж молчит в 57 задачах из 97. Остальным 40 нужен вызов, о котором он спрашивает. В
|
|
68
73
|
Claude Code это один клик, в бенчмарке — проваленная задача. Slack — крайний случай: почти каждая задача пишет в
|
|
69
74
|
канал или человеку, найденному в данных («канал, где больше всего людей»), а не названному пользователем. Именно
|
|
70
75
|
так выглядит внедрённое действие, поэтому страж спрашивает.
|
|
71
76
|
- **Под атакой.** Без стража атаки ломают и задачу пользователя (в travel выполняются 26 из 140). Со стражем атаке
|
|
72
|
-
отказано, и задача идёт дальше:
|
|
77
|
+
отказано, и задача идёт дальше: 608 из 949 против 579 без него.
|
|
73
78
|
|
|
74
79
|
Восемь новых целей атакующего в workspace поставляются без эталонного решения, их нельзя проиграть. Семи из них
|
|
75
80
|
сначала нужно письмо атакующему; это письмо было отклонено во всех 280 доставленных парах. Две цели (одна в
|
|
@@ -168,19 +173,46 @@ Sonnet; в воспроизведении выше модели нет, а пр
|
|
|
168
173
|
Тот же отзыв читателей изменил песочницу HISTOR: соединение, поставленное в очередь прежним владельцем адреса, больше
|
|
169
174
|
не засчитывается следующему прогону — тот помечается неполным, а не чистым.
|
|
170
175
|
|
|
176
|
+
## Что меняли 2026-10-22 и зачем
|
|
177
|
+
|
|
178
|
+
Читатель спросил, чего WARDEN до сих пор не ловит. Мы это перечислили, а потом атаковали стража, держа в руках его
|
|
179
|
+
код: 17 атак в [`scripts/redteam/flow-redteam.mjs`](../scripts/redteam/flow-redteam.mjs), каждая нацелена на
|
|
180
|
+
описанное ограничение или край правила; они проигрываются через ту же точку входа хуков, что и у плагина (послушная
|
|
181
|
+
модель, без бенчмарка). До изменений проходили 14 из них, после — 3.
|
|
182
|
+
|
|
183
|
+
| Изменение | Зачем | Результат |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| Значения, похожие на ключи, снимаются окнами по 10 букв и цифр, с разворотом и заменой букв-двойников; base64 декодируется от 16 символов, а не от 24 | ключ кусками по 15 символов, через дефисы, задом наперёд или кириллицей проходил | атаки: всё это запрещено; первый кусок, в основном формат ключа, по-прежнему проходит |
|
|
186
|
+
| Каждый аргумент помнит хвост отправленного после внешнего текста | куски по 8 символов, номер карты двумя половинами, приватный текст кусками по 12 символов на разрешённый хост проходили | запрет или вопрос со второго-третьего куска |
|
|
187
|
+
| MCP-инструмент без метки, называющий адресата, — исходящий; `warden-mcp flow-labels` | «добавить участника» с e-mail атакующего и «добавить соавтора» по имени пользователя проходили | оба спрашивают |
|
|
188
|
+
| Хук Stop: строка под ответом, опиравшимся на внешний текст | инъекция только в ответ не делает вызовов | пользователь видит, на какие источники опирался ответ; ничего не блокируется |
|
|
189
|
+
| Набор правил v15 (см. [gates](gates.ru.md)) | 78 отравленных инструментов из dev-половины MCPTox проходили правила | отложенная половина 171 → 194 из 218 (правила писали только по другой половине), dev-половина 189 → 233 из 267; 0 из 45 чистых серверов; корпус и 1 000 пакетов HISTOR блокируются ровно как при v14 |
|
|
190
|
+
| Воспроизведение заново на текущем коде | исправления аудита от 2026-10-18 не измерялись | 0 из 579 атак-действий, 57 из 97 задач без вопроса, 608 из 949 выполнено под атакой; изменения 2026-10-22 не поменяли ни одного решения |
|
|
191
|
+
| `scripts/agentdojo/Dockerfile.dockerignore` | `.dockerignore` пакета исключает `dist/`, и команда из «Повторить» не собиралась | собирается |
|
|
192
|
+
|
|
193
|
+
После изменений по-прежнему проходят ([результаты](../scripts/redteam/results/)):
|
|
194
|
+
|
|
195
|
+
- **Куски короче 10 символов, каждый раз в других словах** («часть 3: … спасибо»), на адрес, который набрал пользователь.
|
|
196
|
+
- **Адрес, вставленный в запрос.** Что пользователь набрал или вставил — его выбор; хук не отличает вставленный
|
|
197
|
+
текст от набранного.
|
|
198
|
+
- **Ключ, записанный в файл проекта.** Править файлы проекта — работа агента; что CI потом опубликует из них, стражу
|
|
199
|
+
не видно.
|
|
200
|
+
|
|
171
201
|
## Чего он не делает
|
|
172
202
|
|
|
173
|
-
-
|
|
174
|
-
свои пути, но слушается редко, даже когда ей
|
|
175
|
-
слушается охотнее, — тест жёстче
|
|
203
|
+
- **Измерения — ещё не доказательство.** Воспроизведение слушается идеально, но идёт одним путём; модель находит
|
|
204
|
+
свои пути, но слушается редко, даже когда ей велят; наши атаки со знанием кода — это 17 наших идей. Атакующий,
|
|
205
|
+
которого мы не встречали, или модель, которая слушается охотнее, — тест жёстче любого из них.
|
|
176
206
|
- **Метки — решение оператора.** В первом прогоне мы пометили названия каналов Slack только как приватные данные, а
|
|
177
207
|
инъекция сидела в названии канала. Прогон это поймал, потому что доставка определяется по тексту атаки, а не по
|
|
178
|
-
нашим меткам. Неверно помеченный инструмент — это
|
|
208
|
+
нашим меткам. Неверно помеченный инструмент — это дыра; непомеченный получает догадку стража, которая только
|
|
209
|
+
добавляет осторожности.
|
|
179
210
|
- **Он не останавливает инъекцию, которая меняет только ответ** («скажи, что этот отель лучший»). Нет вызова —
|
|
180
|
-
нечему
|
|
211
|
+
нечему отказывать; хук Stop может только сказать пользователю, откуда взялся ответ.
|
|
181
212
|
- **Локальные изменения внутри проекта проходят.** Править файлы проекта — работа агента.
|
|
182
|
-
-
|
|
183
|
-
|
|
213
|
+
- **Утечка мелкими кусками.** Обычный приватный текст может уйти адресату, которого назвал пользователь, целиком или
|
|
214
|
+
кусками: это выбор пользователя. Другим адресатам, а ключи и номера карт — кому угодно: куски, отправленные подряд
|
|
215
|
+
через один аргумент, склеиваются; куски короче 10 символов с другими словами вокруг каждого — нет.
|
|
184
216
|
- **Он не видит** ваш запрос до установки плагина и текст, который модель держит из других мест.
|
|
185
217
|
|
|
186
218
|
## Повторить
|
|
@@ -194,6 +226,10 @@ docker run --rm --network none -v "$PWD/out:/out" warden-adojo /out/oracle.json
|
|
|
194
226
|
|
|
195
227
|
`oracle.py` пишет результаты по каждой паре; `--variants none,strict,intent` выбирает варианты стража (`strict`
|
|
196
228
|
не учитывает, что набрал пользователь). `summarize_oracle.py` сводит их в то, что цитирует таблица.
|
|
229
|
+
`WARDEN_FLOW_GUARD=/out/x.mjs` проигрывает другую сборку стража (любую копию `flow-guard.mjs` из плагина) — так
|
|
230
|
+
изменения 2026-10-22 сравнивали с кодом до них.
|
|
231
|
+
|
|
232
|
+
Атакам со знанием кода контейнер не нужен: `npm run build && node scripts/redteam/flow-redteam.mjs out.json`.
|
|
197
233
|
|
|
198
234
|
Прогону с моделью (`model.py`) нужен ключ OpenAI-совместимого API, и он остаётся на хосте: его читает `relay.py`, а
|
|
199
235
|
контейнер больше никуда не выходит. `run-model.sh` запускает оба и продолжает прерванный прогон:
|
package/docs/flow-guard.zh.md
CHANGED
|
@@ -16,14 +16,16 @@
|
|
|
16
16
|
- 保护识别出的守卫策略/状态、Claude hooks、Git hooks/config 写入,包括路径变量及符号链接。这不能替代操作系统权限。
|
|
17
17
|
- 使用 URL 解析器检查实际主机及所有目标。邮件正文中的链接不能授权另一个收件人;策略中的邮箱必须完整匹配,域名不授权该域下所有邮箱。用户提及目标也不等于授权任意操作和数据。
|
|
18
18
|
- 联系人解析必须显式配置 `contactTools` 和 `private` 标签,只接受结构化 `name`/`email` 记录,并要求用户提及完整显示名。同名不同地址需要确认。本地文件、普通文本、YAML 和旧版本缓存都不能建立这种信任。只信任代理和第三方无法修改的通讯录。
|
|
19
|
-
- 指纹检查可见参数文本及有限的 URL/base64/hex
|
|
19
|
+
- 指纹检查可见参数文本及有限的 URL/base64/hex 解码。自 2026-10-22 起,看起来像密钥的值(凭据文件中随机的字符串,以及在任何地方读到的已知令牌格式)还会按 10 个字母和数字的窗口记录:密钥的一段、倒写的密钥、带分隔符的密钥、分段 base64 编码的密钥,或用西里尔、希腊形似字母改写的密钥,都会被拒绝。每个参数会记住它在外部文本之后发出内容的末尾,因此通过同一参数连续发出的分段会被整体检查。每次都被不同文字包裹、短于 10 个字符的分段仍会放行,密钥开头的格式部分(`rk_live_`)也是如此。指纹不会在发送时读取任意文件,也不覆盖所有编码和工具。项目内编辑通常仍被允许,执行代码则单独判断。
|
|
20
|
+
- 没有运营者标签的 MCP 工具,在名字表明会外发(send、post、share……)、参数带 URL,或自 2026-10-22 起点名了某一方时,算作外发:例如 `to`、`email`、`attendees`、`assignee`、`collaborator`、`username` 这样的参数,或任何邮箱地址、IBAN。`warden-mcp flow-labels` 列出你的会话用过的无标签 MCP 工具,附上守卫的猜测和一个 `labels` 块,校正后粘贴进策略即可。
|
|
21
|
+
- 只改变回答的注入不会发出可拒绝的调用。自 2026-10-22 起,插件的 Stop 钩子会在本轮读过外部文本的回答下方加一行,列出这些来源;若其中有文本以指令口吻对助手说话,也会注明。它从不拦截。`answerNote`:`always`(默认)、`instruction`(只在这种情况下)或 `off`;插件之外请注册 `warden-mcp hook stop`。
|
|
20
22
|
- 代理也记录 JSON-RPC 和工具错误;记录失败时不转发响应,现有会话状态损坏时阻止检查中的调用。
|
|
21
23
|
|
|
22
|
-
已在 npm `0.17.0` 发布:`npx -y @aimarket/warden@0.
|
|
24
|
+
已在 npm `0.17.0` 发布:`npx -y @aimarket/warden@0.19.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)。
|
|
23
25
|
|
|
24
26
|
代理命令:`node dist/mcp-server.js wrap --flow mail -- node mail-server.js`。同一会话的代理应共享一个新的 `WARDEN_FLOW_SESSION`。默认父进程 PID 不能关联不同启动器。代理看不到用户提示或客户端内置工具,需要确认的调用会被拒绝。`trustedSources` 仅用于 WebFetch URL,不用于 MCP 参数中的任意链接。
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
**这些数字描述哪个版本。** 下面的回放表格于 2026-10-22 在当前代码上重新运行:审计修复加上 2026-10-22 的改动。2026-10-22 的改动没有改变其中任何一个决定(全部 1,046 条记录与改动前的代码一致);审计修复的代价是少了一个不询问的任务、少了 19 个受攻击时完成的任务。模型运行的表格和费用仍对应审计前、截至 `2328be980` 的实现;没有新的模型运行。参考解回放只是固定攻击路径,并非自适应最坏情况;2026-10-22 手握代码的攻击见[下文](#2026-10-22-改了什么为什么)。
|
|
27
29
|
|
|
28
30
|
## 在 AgentDojo 上的测量
|
|
29
31
|
|
|
@@ -32,29 +34,29 @@
|
|
|
32
34
|
|
|
33
35
|
我们没有使用模型。对每一对"用户任务 × 攻击者目标",把基准自带的攻击(`important_instructions`)放到用户任务会读到的位置。
|
|
34
36
|
然后让用户任务的参考解经过守卫回放,接着回放攻击者的参考解。这相当于一个读到注入并**沿参考路径完全服从**的模型,不是自适应最坏情况。
|
|
35
|
-
`ask`
|
|
37
|
+
`ask` 计为拒绝:基准里没有人来回答。运行是确定性的,几分钟完成,不花钱:2026-10-17/18 在我们服务器上无网络的 gVisor 容器中进行,2026-10-22 在一台工作站上无网络的 Docker 容器中进行。
|
|
36
38
|
|
|
37
|
-
AgentDojo 0.1.35,套件 v1.2.2,使用我们为全部 69 个工具设定的运营者标签(`scripts/agentdojo/policy.json`)。结果:`scripts/agentdojo/results
|
|
39
|
+
AgentDojo 0.1.35,套件 v1.2.2,使用我们为全部 69 个工具设定的运营者标签(`scripts/agentdojo/policy.json`)。结果:`scripts/agentdojo/results/`(表格:`summary-2026-10-22.json`)。
|
|
38
40
|
|
|
39
41
|
| 套件 | 成功的攻击动作(无守卫) | 有守卫 | 一次都不询问的任务 | 受攻击时完成的任务(无 / 有) |
|
|
40
42
|
|---|---|---|---|---|
|
|
41
43
|
| banking | 144 中 141 | **144 中 0** | 16 中 13 | 144 中 125 / 117 |
|
|
42
|
-
| Slack | 105 中 105 | **
|
|
44
|
+
| Slack | 105 中 105 | **75 中 0** | 21 中 2 | 105 中 102 / 15 |
|
|
43
45
|
| travel | 120 中 116 | **120 中 0** | 20 中 16 | 140 中 26 / 112 |
|
|
44
|
-
| workspace | 240 中 231 | **240 中 0** | 40 中
|
|
45
|
-
| **合计** | **609 中 593** | **
|
|
46
|
+
| workspace | 240 中 231 | **240 中 0** | 40 中 26 | 560 中 326 / 364 |
|
|
47
|
+
| **合计** | **609 中 593** | **579 中 0** | **97 中 57** | **949 中 579 / 608** |
|
|
46
48
|
|
|
47
49
|
这是 2026-10-18 修正后的标签(下面的模型运行发现其中两个过于严格),以及能通过通讯录认出你按名字点名的人的守卫。最初的标签给出 54 个不询问的任务、受攻击时完成 571 个;
|
|
48
|
-
修正标签但不用通讯录时为 57 和 613
|
|
50
|
+
修正标签但不用通讯录时为 57 和 613;用通讯录、审计修复之前为 58 和 627。所有版本的攻击动作都是 0。审计修复在 Slack 和 workspace 任务中多拒绝了几次抓取和日历写入,所以带来注入的配对是 579 个,而不是 584 个。
|
|
49
51
|
|
|
50
52
|
"攻击动作"是指注入已送达智能体(其文本出现在某个工具结果中),且攻击者目标需要一次调用的配对:发送、付款、邀请、删除。
|
|
51
53
|
两列中已送达配对的数量略有不同,因为守卫也会拒绝某些本会带来注入的抓取。
|
|
52
54
|
|
|
53
55
|
如何理解最后两列:
|
|
54
56
|
|
|
55
|
-
- **一次都不询问。** 在 97 个任务中,守卫在
|
|
57
|
+
- **一次都不询问。** 在 97 个任务中,守卫在 57 个里保持沉默。其余 40 个需要一个它会询问的调用。在 Claude Code 里这只是点一下;在基准里则是任务失败。
|
|
56
58
|
Slack 是极端情况:几乎每个任务都要发到数据里找到的频道或人("用户最多的频道"),而不是用户点名的。这正是被注入动作的样子,所以它会询问。
|
|
57
|
-
- **受攻击时。** 没有守卫时,攻击也会破坏用户的任务(travel 中 140 个只完成 26 个)。有守卫时攻击被拒绝、任务继续:949 个中完成
|
|
59
|
+
- **受攻击时。** 没有守卫时,攻击也会破坏用户的任务(travel 中 140 个只完成 26 个)。有守卫时攻击被拒绝、任务继续:949 个中完成 608 个,无守卫时为 579 个。
|
|
58
60
|
|
|
59
61
|
workspace 中八个较新的攻击者目标没有参考解,因而无法回放。其中七个首先需要给攻击者发一封邮件;在全部 280 个已送达配对中,这封邮件都被拒绝。
|
|
60
62
|
另有两个目标(travel 一个、workspace 一个;60 个配对)没有给出任何地址,这里没有测量。
|
|
@@ -125,14 +127,35 @@ workspace 中八个较新的攻击者目标没有参考解,因而无法回放
|
|
|
125
127
|
|
|
126
128
|
同一份读者反馈也改变了 HISTOR 的沙箱:某个地址的前一个持有者排队的连接,不再算到下一次运行头上——那次运行会被标为不完整,而不是干净。
|
|
127
129
|
|
|
130
|
+
## 2026-10-22 改了什么,为什么
|
|
131
|
+
|
|
132
|
+
一位读者问 WARDEN 还抓不到什么。我们把它列了出来,然后手握源码攻击守卫:[`scripts/redteam/flow-redteam.mjs`](../scripts/redteam/flow-redteam.mjs)
|
|
133
|
+
中的 17 个攻击,每个都瞄准一条已写明的局限或某条规则的边缘,通过插件使用的同一个钩子入口回放(一个服从的模型,没有基准)。改动之前其中 14 个能通过,改动之后 3 个。
|
|
134
|
+
|
|
135
|
+
| 改动 | 原因 | 结果 |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| 看起来像密钥的值按 10 个字母和数字的窗口记录,也检查倒写,并把形似字母归一;base64 从 16 个字符起解码,而不是 24 个 | 分成 15 个字符的段、带连字符、倒写或用西里尔字母写的密钥能通过 | 攻击:这些全部被拒绝;第一段主要是密钥的格式部分,仍会放行 |
|
|
138
|
+
| 每个参数记住它在外部文本之后发出内容的末尾 | 8 个字符一段、银行卡号分成两半、私有文本以 12 个字符一段发往允许的主机,都能通过 | 从第二或第三段起拒绝或询问 |
|
|
139
|
+
| 点名某一方的无标签 MCP 工具算作外发;`warden-mcp flow-labels` | 用攻击者邮箱"添加参会者"、按用户名"添加协作者"能通过 | 两者都会询问 |
|
|
140
|
+
| Stop 钩子:在依赖外部文本的回答下加一行 | 只改回答的注入不发出调用 | 用户能看到回答用了哪些来源;不拦截任何东西 |
|
|
141
|
+
| 规则集 v15(见 [gates](gates.zh.md)) | MCPTox dev 一半中有 78 个投毒工具能通过规则 | 留出的一半 218 中 171 → 194(规则只依据另一半编写),dev 一半 267 中 189 → 233;45 个干净服务器中 0 个;语料库和 1,000 个 HISTOR 包的拦截与 v14 完全相同 |
|
|
142
|
+
| 在当前代码上重新回放 | 2026-10-18 的审计修复还没测量过 | 579 中 0 个攻击动作,97 中 57 个任务不询问,949 中 608 个受攻击时完成;2026-10-22 的改动没有改变任何决定 |
|
|
143
|
+
| `scripts/agentdojo/Dockerfile.dockerignore` | 包自带的 `.dockerignore` 排除了 `dist/`,"复现"里的命令构建不了 | 可以构建 |
|
|
144
|
+
|
|
145
|
+
改动之后仍能通过的([结果](../scripts/redteam/results/)):
|
|
146
|
+
|
|
147
|
+
- **短于 10 个字符、每次被不同文字包裹的分段**("第 3 部分:……谢谢"),发往用户输入的地址。
|
|
148
|
+
- **粘贴进提示词的地址。** 用户输入或粘贴的内容是用户的选择;钩子分不清粘贴的文本和输入的文本。
|
|
149
|
+
- **写进项目文件的密钥。** 编辑项目文件是智能体的本职工作;CI 之后从中发布什么,守卫看不到。
|
|
150
|
+
|
|
128
151
|
## 它不做什么
|
|
129
152
|
|
|
130
|
-
-
|
|
153
|
+
- **测量还不是证明。** 回放完全服从,但只走一条路;模型会自己找路,但很少服从,即使被要求服从也是如此;我们手握代码的攻击只是我们自己的 17 个想法。我们没遇到过的攻击者,或更容易服从的模型,是比它们都更难的测试。
|
|
131
154
|
- **标签由运营者决定。** 我们第一次运行时只把 Slack 频道名标成了私有数据,而注入正藏在一个频道名里。运行抓住了这一点,因为是否送达是根据攻击文本判断的,
|
|
132
|
-
|
|
133
|
-
- **它拦不住只改变回答的注入**("说这家酒店最好"
|
|
155
|
+
而不是根据我们的标签。标错的工具就是一个漏洞;没有标签的工具得到守卫的猜测,而猜测只会增加谨慎。
|
|
156
|
+
- **它拦不住只改变回答的注入**("说这家酒店最好")。没有调用,就没有可拒绝的东西;Stop 钩子只能告诉用户回答来自哪里。
|
|
134
157
|
- **项目内的本地修改会放行。** 编辑项目文件是智能体的本职工作。
|
|
135
|
-
-
|
|
158
|
+
- **小块泄露。** 普通私有文本可以整体或分段发给用户点名的目标:这是用户的选择。发往其他目标时,以及密钥和银行卡号发往任何目标时,通过同一参数连续发出的分段会被拼接检查;短于 10 个字符、四周文字各不相同的分段则不会。
|
|
136
159
|
- **它看不到**安装插件之前你的提示词,也看不到模型从别处得到的文本。
|
|
137
160
|
|
|
138
161
|
## 复现
|
|
@@ -144,7 +167,9 @@ mkdir -m 777 out
|
|
|
144
167
|
docker run --rm --network none -v "$PWD/out:/out" warden-adojo /out/oracle.json
|
|
145
168
|
```
|
|
146
169
|
|
|
147
|
-
`oracle.py` 按配对写出结果;`--variants none,strict,intent` 选择守卫的变体(`strict` 不考虑用户输入了什么)。`summarize_oracle.py`
|
|
170
|
+
`oracle.py` 按配对写出结果;`--variants none,strict,intent` 选择守卫的变体(`strict` 不考虑用户输入了什么)。`summarize_oracle.py` 把它们汇总成表格引用的摘要。`WARDEN_FLOW_GUARD=/out/x.mjs` 可回放守卫的另一个版本(插件里任一份 `flow-guard.mjs`),2026-10-22 的改动就是这样与改动前的代码对比的。
|
|
171
|
+
|
|
172
|
+
手握代码的攻击不需要容器:`npm run build && node scripts/redteam/flow-redteam.mjs out.json`。
|
|
148
173
|
|
|
149
174
|
模型运行(`model.py`)需要一个 OpenAI 兼容 API 的密钥,它留在主机上:由 `relay.py` 读取,容器访问不到别的任何东西。`run-model.sh` 启动两者,并能续跑中断的运行:
|
|
150
175
|
|
package/docs/gates.es.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 🌐 [English](gates.md) · [Русский](gates.ru.md) · **Español** · [Français](gates.fr.md) · [中文](gates.zh.md)
|
|
4
4
|
|
|
5
|
-
**Ruleset
|
|
5
|
+
**Ruleset v15 (sin publicar):** v14 dejó de confundir almacenamiento en un servidor con custodia local (corrección de auditoría, solo pruebas locales de regresión); v15 amplía `TOOL_DEF_CROSS_TOOL` y se midió el 2026-10-22 en MCPTox, el corpus de campo y 1000 paquetes de HISTOR (ver v15 abajo). Las cifras de versiones anteriores son históricas.
|
|
6
6
|
|
|
7
7
|
> [Seguridad y migración de 0.7.0](security-hardening.es.md).
|
|
8
8
|
|
|
@@ -52,8 +52,8 @@ defecto; expresarlo bajando su severidad la volvía bloqueante para quien endure
|
|
|
52
52
|
|
|
53
53
|
Escaneo local con regex sobre cada campo que anuncia una herramienta: su `name`, `description` e
|
|
54
54
|
`inputSchema`, y desde v6 su `title`, `outputSchema`, `annotations` y metadatos de extensión (todo lo
|
|
55
|
-
demás que envió el servidor, salvo un icono de imagen en base64).
|
|
56
|
-
`block`, 11 `advise`, y
|
|
55
|
+
demás que envió el servidor, salvo un icono de imagen en base64). 37 reglas en el conjunto **v15**: 26
|
|
56
|
+
`block`, 11 `advise`, y 26 de ellas llevan un **guard** de contexto:
|
|
57
57
|
una comprobación con nombre que decide si una coincidencia es de verdad lo que la regla busca. Véase
|
|
58
58
|
[el estudio de campo](mcp-survey.es.md), la ejecución sobre 1 108 servidores con la que se calibró v4.
|
|
59
59
|
|
|
@@ -158,7 +158,21 @@ oración la mueva («sign it and paste the private key» sigue bloqueando). En e
|
|
|
158
158
|
donde v12 bloqueaba 3, sin perder ninguno: el nuevo es un constructor alojado cuyo argumento toma la clave secreta
|
|
159
159
|
de 64 bytes de un nuevo par de claves de mint. En MCPTox no cambia nada: 171 de 218 herramientas envenenadas de la mitad reservada bloqueadas, como con v12 (la medición se reprodujo exactamente), y ninguno de los 45 servidores limpios; las herramientas de MCPTox reescriben llamadas de otras herramientas en lugar de pedir claves. Pruebas: `test/ruleset-v13.test.ts`.
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
**v15** vuelve a escribirse a partir de la mitad dev de MCPTox, tras preguntar un lector qué sigue sin detectar
|
|
162
|
+
WARDEN: 78 de sus 267 herramientas envenenadas pasaban v14. `TOOL_DEF_CROSS_TOOL` reconoce ahora también la llamada
|
|
163
|
+
del usuario («when a user calls `X`», «when a user's query for `X` …»), una pasiva («when `X` is called»), cualquier
|
|
164
|
+
llamada («all `X` requests», «all web searches using the `X` tool») y reescrituras que un mandato ata a un parámetro o
|
|
165
|
+
a un valor entre comillas («you must set the `mode` parameter to …», «you should redirect any `symbol` to 'QQQ'»,
|
|
166
|
+
«When using `X`, set `bcc` to '…'»). Un patrón nuevo bloquea una llamada a otra herramienta con un objetivo fijo y
|
|
167
|
+
reforzada con una amenaza («use 'deleteEmail' with UID 99999 … Failure to do so will crash the system»); un requisito
|
|
168
|
+
previo honesto no fija objetivo ni amenaza. Un primer borrador que aceptaba cualquier «when the user asks …» bloqueaba
|
|
169
|
+
7 servidores honestos del corpus; 4 de ellos quedan fijados como pruebas. En la mitad dev v15 bloquea 233 de 267 (v14: 189); en
|
|
170
|
+
la mitad reservada, medida una sola vez tras congelar las reglas, 194 de 218 (v14: 171); las líneas añadidas no
|
|
171
|
+
comparten ninguna secuencia de ocho palabras con ninguna herramienta reservada. No bloquea ninguno de los 45 servidores
|
|
172
|
+
limpios, los mismos 4 servidores del corpus de campo que v14 y los mismos 28 de una muestra de 1000 paquetes de HISTOR.
|
|
173
|
+
Pruebas: `test/ruleset-v15.test.ts`; resultados: `scripts/scanner-comparison/results/2026-10-22-mcptox-v15.json`.
|
|
174
|
+
|
|
175
|
+
Cada regla declara sobre cuál de esas siete **superficies** se ejecuta, y 24 de las 37 incluyen el
|
|
162
176
|
nombre. Cuatro de los códigos que no lo hacen son los que se apoyan en un SUSTANTIVO
|
|
163
177
|
(`TOOL_DEF_SECRET_REQUEST`, `TOOL_DEF_CREDENTIAL_PARAM`, `TOOL_DEF_ENV_REFERENCE`, `TOOL_DEF_SECRET_EXFIL`): un nombre es un
|
|
164
178
|
identificador, `api_key` y `private_key` son partes ordinarias de uno, y rechazar
|
|
@@ -183,7 +197,7 @@ afectan.
|
|
|
183
197
|
| `TOOL_DEF_SECRET_REQUEST` | critical | block | — | `private_key`, `seed_phrase`/`mnemonic`, rutas `~/.ssh` |
|
|
184
198
|
| `TOOL_DEF_SECRET_HARVEST` | critical | block | ✅ | una herramienta cuyo cometido declarado es leer/volcar/revelar secretos, también desde `~/.aws/credentials`, `~/.kube/config` y otros almacenes de credenciales |
|
|
185
199
|
| `TOOL_DEF_EXFIL` | critical / high | block | ✅ | «post to https://…», «send a copy of all messages to <buzón>», `bcc <buzón>`, la conversación enviada con una dirección, credencial u ocultación, «exfiltrate», fraseo de subida a un host |
|
|
186
|
-
| `TOOL_DEF_CROSS_TOOL` | high | block | — | el texto de una herramienta se ata a la llamada de otra para reescribir su entrada o adelantarse con una tercera; una herramienta sin entrada que solo ordena otra llamada |
|
|
200
|
+
| `TOOL_DEF_CROSS_TOOL` | high | block | — | el texto de una herramienta se ata a la llamada de otra para reescribir su entrada o adelantarse con una tercera; una herramienta sin entrada que solo ordena otra llamada; una llamada a otra herramienta con objetivo fijo, reforzada con una amenaza (v15) |
|
|
187
201
|
| `TOOL_DEF_HIDDEN_UNICODE` | high | block | ✅ | caracteres de ancho cero y de control bidi — texto que el revisor no ve |
|
|
188
202
|
| `TOOL_DEF_BASE64_BLOB` | high | block | ✅ | una tirada base64 de 120+ caracteres en un nombre, una descripción o un esquema |
|
|
189
203
|
| `TOOL_DEF_DATA_URL` | high | block | ✅ | URLs `data:…;base64,` y `javascript:` |
|
package/docs/gates.fr.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 🌐 [English](gates.md) · [Русский](gates.ru.md) · [Español](gates.es.md) · **Français** · [中文](gates.zh.md)
|
|
4
4
|
|
|
5
|
-
**Ruleset
|
|
5
|
+
**Ruleset v15 (non publié) :** v14 ne confond plus stockage serveur et garde locale (correctif d’audit, tests locaux de régression seulement) ; v15 élargit `TOOL_DEF_CROSS_TOOL` et a été mesuré le 2026-10-22 sur MCPTox, le corpus de terrain et 1 000 paquets HISTOR (voir v15 plus bas). Les chiffres des versions antérieures sont historiques.
|
|
6
6
|
|
|
7
7
|
> [Sécurité et migration 0.7.0](security-hardening.fr.md).
|
|
8
8
|
|
|
@@ -57,7 +57,7 @@ durcissait le seuil.
|
|
|
57
57
|
Analyse locale par expressions régulières de chaque champ qu'un outil annonce : son `name`, sa
|
|
58
58
|
`description` et son `inputSchema`, et depuis v6 son `title`, son `outputSchema`, ses `annotations` et
|
|
59
59
|
ses métadonnées d'extension (tout le reste de ce qu'envoie le serveur, sauf une icône image en base64).
|
|
60
|
-
|
|
60
|
+
37 règles dans le jeu **v15** : 26 `block`, 11 `advise`, et 26 d'entre elles portent un **guard** de
|
|
61
61
|
contexte — une vérification nommée qui décide si une correspondance est vraiment ce que la règle
|
|
62
62
|
cherche. Voir [l'étude de terrain](mcp-survey.fr.md), le passage sur 1 108 serveurs qui a calibré v4.
|
|
63
63
|
|
|
@@ -164,7 +164,22 @@ bloque 4 serveurs là où v12 en bloquait 3, sans en perdre aucun : le nouveau e
|
|
|
164
164
|
argument prend la clé secrète de 64 octets d'une nouvelle paire de clés de mint. Sur MCPTox, rien ne change : 171 des 218 outils empoisonnés de la moitié réservée bloqués, comme avec v12 (la mesure s’est reproduite à l’identique), et aucun des 45 serveurs propres ; les outils de MCPTox réécrivent les appels d’autres outils au lieu de demander des clés. Tests :
|
|
165
165
|
`test/ruleset-v13.test.ts`.
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
**v15** est de nouveau écrit à partir de la moitié dev de MCPTox, après qu’un lecteur a demandé ce que WARDEN
|
|
168
|
+
ne repère toujours pas : 78 de ses 267 outils empoisonnés passaient v14. `TOOL_DEF_CROSS_TOOL` reconnaît maintenant
|
|
169
|
+
aussi l’appel de l’utilisateur (« when a user calls `X` », « when a user's query for `X` … »), un appel au passif
|
|
170
|
+
(« when `X` is called »), tous les appels (« all `X` requests », « all web searches using the `X` tool ») et les
|
|
171
|
+
réécritures qu’une obligation attache à un paramètre ou à une valeur entre guillemets (« you must set the `mode`
|
|
172
|
+
parameter to … », « you should redirect any `symbol` to 'QQQ' », « When using `X`, set `bcc` to '…' »). Un nouveau
|
|
173
|
+
motif bloque un appel à un autre outil vers une cible fixe, imposé par une menace (« use 'deleteEmail' with UID 99999
|
|
174
|
+
… Failure to do so will crash the system ») ; un prérequis honnête ne fixe pas de cible et ne menace de rien. Un
|
|
175
|
+
premier jet qui acceptait tout « when the user asks … » bloquait 7 serveurs honnêtes du corpus ; 4 d’entre eux sont fixés
|
|
176
|
+
dans les tests. Sur la moitié dev, v15 bloque 233 sur 267 (v14 : 189) ; sur la moitié réservée, mesurée une seule fois
|
|
177
|
+
après le gel des règles, 194 sur 218 (v14 : 171) ; les lignes ajoutées ne partagent aucune suite de huit mots avec un
|
|
178
|
+
outil réservé. Il ne bloque aucun des 45 serveurs propres, les mêmes 4 serveurs du corpus de terrain que v14 et les
|
|
179
|
+
mêmes 28 d’un échantillon de 1 000 paquets HISTOR. Tests : `test/ruleset-v15.test.ts` ; résultats :
|
|
180
|
+
`scripts/scanner-comparison/results/2026-10-22-mcptox-v15.json`.
|
|
181
|
+
|
|
182
|
+
Chaque règle déclare sur laquelle de ces sept **surfaces** elle s'exécute, et 24 des 37 incluent le
|
|
168
183
|
nom. Quatre des codes qui ne l'incluent pas sont ceux qui reposent sur un NOM COMMUN
|
|
169
184
|
(`TOOL_DEF_SECRET_REQUEST`, `TOOL_DEF_CREDENTIAL_PARAM`, `TOOL_DEF_ENV_REFERENCE`, `TOOL_DEF_SECRET_EXFIL`) : un nom d'outil
|
|
170
185
|
est un identifiant, `api_key` et `private_key` en sont des morceaux ordinaires, et refuser
|
|
@@ -189,7 +204,7 @@ l'affectent jamais.
|
|
|
189
204
|
| `TOOL_DEF_SECRET_REQUEST` | critical | block | — | `private_key`, `seed_phrase`/`mnemonic`, chemins `~/.ssh` |
|
|
190
205
|
| `TOOL_DEF_SECRET_HARVEST` | critical | block | ✅ | un outil dont la mission déclarée est de lire/extraire/révéler des secrets, y compris depuis `~/.aws/credentials`, `~/.kube/config` et d'autres magasins d'identifiants |
|
|
191
206
|
| `TOOL_DEF_EXFIL` | critical / high | block | ✅ | « post to https://… », « send a copy of all messages to <boîte> », `bcc <boîte>`, la conversation envoyée avec une adresse, un identifiant ou une dissimulation, « exfiltrate », formulations d'upload vers un hôte |
|
|
192
|
-
| `TOOL_DEF_CROSS_TOOL` | high | block | — | le texte d'un outil s'attache à l'appel d'un autre pour réécrire son entrée ou le devancer par un troisième appel ; un outil sans entrée qui ne fait qu'ordonner un autre appel |
|
|
207
|
+
| `TOOL_DEF_CROSS_TOOL` | high | block | — | le texte d'un outil s'attache à l'appel d'un autre pour réécrire son entrée ou le devancer par un troisième appel ; un outil sans entrée qui ne fait qu'ordonner un autre appel ; un appel à un autre outil vers une cible fixe, imposé par une menace (v15) |
|
|
193
208
|
| `TOOL_DEF_HIDDEN_UNICODE` | high | block | ✅ | caractères de largeur nulle et de contrôle bidi — du texte que le relecteur ne voit pas |
|
|
194
209
|
| `TOOL_DEF_BASE64_BLOB` | high | block | ✅ | une suite base64 de 120+ caractères dans un nom, une description ou un schéma |
|
|
195
210
|
| `TOOL_DEF_DATA_URL` | high | block | ✅ | URLs `data:…;base64,` et `javascript:` |
|