@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.
Files changed (160) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README-es.md +27 -11
  3. package/README-fr.md +27 -11
  4. package/README-ru.md +27 -11
  5. package/README-zh.md +27 -11
  6. package/README.md +27 -11
  7. package/dist/annotation-review.d.ts +10 -0
  8. package/dist/annotation-review.d.ts.map +1 -0
  9. package/dist/annotation-review.js +36 -0
  10. package/dist/annotation-review.js.map +1 -0
  11. package/dist/atomic-screen.d.ts +61 -0
  12. package/dist/atomic-screen.d.ts.map +1 -0
  13. package/dist/atomic-screen.js +165 -0
  14. package/dist/atomic-screen.js.map +1 -0
  15. package/dist/flow-guard-answer.d.ts +29 -0
  16. package/dist/flow-guard-answer.d.ts.map +1 -0
  17. package/dist/flow-guard-answer.js +201 -0
  18. package/dist/flow-guard-answer.js.map +1 -0
  19. package/dist/flow-guard-classify.d.ts +14 -0
  20. package/dist/flow-guard-classify.d.ts.map +1 -0
  21. package/dist/flow-guard-classify.js +224 -0
  22. package/dist/flow-guard-classify.js.map +1 -0
  23. package/dist/flow-guard-decode.d.ts +17 -0
  24. package/dist/flow-guard-decode.d.ts.map +1 -0
  25. package/dist/flow-guard-decode.js +149 -0
  26. package/dist/flow-guard-decode.js.map +1 -0
  27. package/dist/flow-guard-destinations.d.ts +23 -0
  28. package/dist/flow-guard-destinations.d.ts.map +1 -0
  29. package/dist/flow-guard-destinations.js +158 -0
  30. package/dist/flow-guard-destinations.js.map +1 -0
  31. package/dist/flow-guard-fingerprints.d.ts +27 -0
  32. package/dist/flow-guard-fingerprints.d.ts.map +1 -0
  33. package/dist/flow-guard-fingerprints.js +164 -0
  34. package/dist/flow-guard-fingerprints.js.map +1 -0
  35. package/dist/flow-guard-fragments.d.ts +17 -0
  36. package/dist/flow-guard-fragments.d.ts.map +1 -0
  37. package/dist/flow-guard-fragments.js +122 -0
  38. package/dist/flow-guard-fragments.js.map +1 -0
  39. package/dist/flow-guard-instructions.d.ts +12 -0
  40. package/dist/flow-guard-instructions.d.ts.map +1 -0
  41. package/dist/flow-guard-instructions.js +36 -0
  42. package/dist/flow-guard-instructions.js.map +1 -0
  43. package/dist/flow-guard-labels.d.ts +10 -0
  44. package/dist/flow-guard-labels.d.ts.map +1 -0
  45. package/dist/flow-guard-labels.js +55 -0
  46. package/dist/flow-guard-labels.js.map +1 -0
  47. package/dist/flow-guard-leak.d.ts +29 -0
  48. package/dist/flow-guard-leak.d.ts.map +1 -0
  49. package/dist/flow-guard-leak.js +135 -0
  50. package/dist/flow-guard-leak.js.map +1 -0
  51. package/dist/flow-guard-paths.d.ts +21 -0
  52. package/dist/flow-guard-paths.d.ts.map +1 -0
  53. package/dist/flow-guard-paths.js +72 -0
  54. package/dist/flow-guard-paths.js.map +1 -0
  55. package/dist/flow-guard-policy.d.ts +15 -0
  56. package/dist/flow-guard-policy.d.ts.map +1 -0
  57. package/dist/flow-guard-policy.js +179 -0
  58. package/dist/flow-guard-policy.js.map +1 -0
  59. package/dist/flow-guard-post.d.ts +6 -0
  60. package/dist/flow-guard-post.d.ts.map +1 -0
  61. package/dist/flow-guard-post.js +131 -0
  62. package/dist/flow-guard-post.js.map +1 -0
  63. package/dist/flow-guard-pre.d.ts +13 -0
  64. package/dist/flow-guard-pre.d.ts.map +1 -0
  65. package/dist/flow-guard-pre.js +335 -0
  66. package/dist/flow-guard-pre.js.map +1 -0
  67. package/dist/flow-guard-screen.d.ts +85 -0
  68. package/dist/flow-guard-screen.d.ts.map +1 -0
  69. package/dist/flow-guard-screen.js +558 -0
  70. package/dist/flow-guard-screen.js.map +1 -0
  71. package/dist/flow-guard-state.d.ts +12 -0
  72. package/dist/flow-guard-state.d.ts.map +1 -0
  73. package/dist/flow-guard-state.js +121 -0
  74. package/dist/flow-guard-state.js.map +1 -0
  75. package/dist/flow-guard-text.d.ts +9 -0
  76. package/dist/flow-guard-text.d.ts.map +1 -0
  77. package/dist/flow-guard-text.js +46 -0
  78. package/dist/flow-guard-text.js.map +1 -0
  79. package/dist/flow-guard-types.d.ts +154 -0
  80. package/dist/flow-guard-types.d.ts.map +1 -0
  81. package/dist/flow-guard-types.js +2 -0
  82. package/dist/flow-guard-types.js.map +1 -0
  83. package/dist/flow-guard.d.ts +20 -215
  84. package/dist/flow-guard.d.ts.map +1 -1
  85. package/dist/flow-guard.js +26 -1400
  86. package/dist/flow-guard.js.map +1 -1
  87. package/dist/mcp-client.d.ts +1 -2
  88. package/dist/mcp-client.d.ts.map +1 -1
  89. package/dist/mcp-client.js +2 -19
  90. package/dist/mcp-client.js.map +1 -1
  91. package/dist/mcp-server.js +5 -0
  92. package/dist/mcp-server.js.map +1 -1
  93. package/dist/mcp-tools.d.ts.map +1 -1
  94. package/dist/mcp-tools.js +46 -9
  95. package/dist/mcp-tools.js.map +1 -1
  96. package/dist/public-address.d.ts +3 -0
  97. package/dist/public-address.d.ts.map +1 -0
  98. package/dist/public-address.js +58 -0
  99. package/dist/public-address.js.map +1 -0
  100. package/dist/review-diff.d.ts +36 -0
  101. package/dist/review-diff.d.ts.map +1 -1
  102. package/dist/review-diff.js +177 -7
  103. package/dist/review-diff.js.map +1 -1
  104. package/dist/task-adapter.d.ts +73 -0
  105. package/dist/task-adapter.d.ts.map +1 -0
  106. package/dist/task-adapter.js +379 -0
  107. package/dist/task-adapter.js.map +1 -0
  108. package/dist/task-cli.d.ts +3 -0
  109. package/dist/task-cli.d.ts.map +1 -0
  110. package/dist/task-cli.js +162 -0
  111. package/dist/task-cli.js.map +1 -0
  112. package/dist/types.d.ts +9 -0
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/wrap-cli.d.ts +5 -0
  115. package/dist/wrap-cli.d.ts.map +1 -1
  116. package/dist/wrap-cli.js +29 -2
  117. package/dist/wrap-cli.js.map +1 -1
  118. package/dist/wrap-screen.d.ts +3 -0
  119. package/dist/wrap-screen.d.ts.map +1 -0
  120. package/dist/wrap-screen.js +20 -0
  121. package/dist/wrap-screen.js.map +1 -0
  122. package/dist/wrap-state.d.ts +8 -1
  123. package/dist/wrap-state.d.ts.map +1 -1
  124. package/dist/wrap-state.js +21 -4
  125. package/dist/wrap-state.js.map +1 -1
  126. package/dist/wrap.d.ts.map +1 -1
  127. package/dist/wrap.js +204 -10
  128. package/dist/wrap.js.map +1 -1
  129. package/docs/flow-guard-model-comparison-2026-10-25.es.md +82 -0
  130. package/docs/flow-guard-model-comparison-2026-10-25.fr.md +82 -0
  131. package/docs/flow-guard-model-comparison-2026-10-25.md +82 -0
  132. package/docs/flow-guard-model-comparison-2026-10-25.ru.md +82 -0
  133. package/docs/flow-guard-model-comparison-2026-10-25.zh.md +82 -0
  134. package/docs/flow-guard.es.md +307 -12
  135. package/docs/flow-guard.fr.md +307 -12
  136. package/docs/flow-guard.md +315 -13
  137. package/docs/flow-guard.ru.md +314 -13
  138. package/docs/flow-guard.zh.md +306 -8
  139. package/docs/scan-limits.es.md +39 -0
  140. package/docs/scan-limits.fr.md +39 -0
  141. package/docs/scan-limits.md +39 -0
  142. package/docs/scan-limits.ru.md +39 -0
  143. package/docs/scan-limits.zh.md +39 -0
  144. package/docs/scan.es.md +2 -0
  145. package/docs/scan.fr.md +2 -0
  146. package/docs/scan.md +2 -0
  147. package/docs/scan.ru.md +2 -0
  148. package/docs/scan.zh.md +2 -0
  149. package/docs/security-hardening.es.md +1 -1
  150. package/docs/security-hardening.fr.md +1 -1
  151. package/docs/security-hardening.md +1 -1
  152. package/docs/security-hardening.ru.md +1 -1
  153. package/docs/security-hardening.zh.md +1 -1
  154. package/examples/atomic-screen-server.mjs +42 -0
  155. package/native/macos/Package.swift +9 -0
  156. package/native/macos/Sources/WardenDesktop/NativeSurface.swift +246 -0
  157. package/native/macos/Sources/WardenDesktop/StandardIO.swift +139 -0
  158. package/native/macos/Sources/WardenDesktopHost/Main.swift +211 -0
  159. package/package.json +18 -6
  160. package/scripts/build-macos.mjs +22 -0
@@ -2,6 +2,291 @@
2
2
 
3
3
  > 🌐 **English** · [Русский](flow-guard.ru.md) · [Español](flow-guard.es.md) · [Français](flow-guard.fr.md) · [中文](flow-guard.zh.md)
4
4
 
5
+ See [measurement limits and version evidence](scan-limits.md) for billing, package pins, launcher comparisons and what approval counts establish.
6
+
7
+ Follow-up audit, 2026-10-23: binary `image`/`audio` data in tool results no longer consumes the text budget, so a large screenshot before its text does not hide element labels, credential-file window titles or JSON text blocks. Screens returned by pointer and keyboard actions (including `computer` actions) update the view too. A plain acknowledgment keeps the previous view; an image-only action result clears it. Outgoing arguments receive no binary exemption and remain subject to secret checks. Regression tests cover both bypasses through the hooks and the real stdio proxy. The local source now also gates opaque GUI actions as described below.
8
+
9
+ These screen-control fixes ship in npm `0.21.0`. npm `0.20.0` did not include them.
10
+
11
+ ## Annotation first-use review and answer checks (local source, 2026-10-25)
12
+
13
+ Optional user-level policy `{"annotationReview":"first-use"}` adds a first-use question in **`wrap --flow NAME` only**. Default is `off`; ordinary wrap and hooks alone gain no new requirement. For an operator-unlabelled tool, `readOnlyHint`, `destructiveHint` and `openWorldHint` are unverified server claims: either boolean value, or an invalid known hint, can require review. Even `readOnlyHint: true` proves neither read-only behavior nor free execution. Explicit operator labels and exact identity/tool-set capability bindings take precedence; destination allowlists and annotations in call arguments grant nothing.
14
+
15
+ Review is remembered only in this wrapper for the exact launch identity and complete tool snapshot. After acceptance, the proxy waits for any old list and requests a new list, then rechecks shared flow risk before forwarding; drift, a new denial/ask basis, decline or no approval form refuses the call. Unchanged legitimate ask-bases remain usable. These checks are not an atomic transaction against other proxies. A new wrapper asks again. Every-call sensitive-tool, credential and GUI guards remain independent. `audit` only logs; `off` disables this check. This is not billing verification or a general effect classifier.
16
+
17
+ Known friction: an initial precheck may already consume an exact one-use `credentialEgress` rule. The post-approval precheck then conservatively refuses even an unchanged payload, and the grant remains spent. This control is recorded as extra friction, not a harmless automatic pass.
18
+
19
+ The Stop hook no longer exempts a sentence merely because it says “ignored” or “flagged”: it can still repeat the injected recommendation. An exact normalized 12-character overlap with a recorded instruction fragment requests an additional answer check. **An honest warning pays that extra pass too.** The host's `stop_hook_active` flag bounds the continuation; no independent persistent loop counter is claimed. Paraphrases, poisoned row selection without a matching instruction and semantic correctness remain unsolved. Source notes/rechecks are not prevention scores. The [study](../../docs/growth/kevin-annotations-answer-review-2026-10-25.ru.md) distinguishes deterministic Linux tests from model evaluation; these changes are local and unreleased, and historical model results below remain historical.
20
+
21
+ ## Credential windows (local source, 2026-10-25)
22
+
23
+ Strong windows remain **8 non-hex / 10 hex** after the Linux 6/7/8 comparison. The local index now includes mixed letter/digit windows before a key's first digit after recognized normalized provider prefixes, while keeping generic/DER exclusions and all old hashes. A credential reread is needed for an existing session to gain those windows. Six-character pieces inside longer words still rely on the credential boundary when not otherwise recognized.
24
+
25
+ On 14,400 attack fixtures the change converts 717 decisions from ask to deny; none execute automatically before or after. The 7,680 benign requests per profile keep their decisions: all ask by default, all pass once with exact reviewed public rules. Deliberately selected public-text controls show false hard denials at 6 and a known example at 7; these are not production rates. The [study and raw evidence](../../docs/growth/warden-thresholds-2026-10-25.ru.md) separate corpus substring hits, hook decisions and detector-only results. The change is local and unreleased.
26
+
27
+ ## Credential boundary and encoded fragments (local source, 2026-10-23)
28
+
29
+ After a recorded credential/key read and outside text, **every supported outbound/MCP call requires `ask` before execution** (`deny` refuses), regardless of fragment length, encoding, user-named destination or read-only MCP label. Ordinary project writes are reviewed too; credential-file writes retain their existing exemption. The sticky `credentialRead` flag survives source-history rotation and user turns. This closes automatic transmission of one-to-four-character pieces and unknown encoded blobs by enforcing a boundary, not by claiming to decode arbitrary information.
30
+
31
+ Known secret matches still deny; ambiguous short matches use `secret_fragment`. Other boundary reviews use `credential_egress`, not a false assertion that harmless text is a secret. Strict GUI stays the default; GUI/control exemptions, including copy chords, do not waive this credential boundary. `audit` only logs, `off` disables enforcement; the client must honor decisions.
32
+
33
+ **Reducing unnecessary reviews:** an operator may put exact, independently reviewed public requests in the protected user-level `flow-policy.json`. Rules bind the full tool name and all arguments, including destination, via `publicRequestDigest(tool, args)` from `dist/flow-guard.js`. Object keys are sorted; array order matters. Every rule grants at most one call per session, atomically across proxies, and is spent even if the call fails. Repeated/changed requests, extra RPC metadata and malformed arguments are not covered. Known secret/private/PII/weak-fragment matches, incomplete scans, saturated stores, GUI controls, shell code, destructive actions and protected settings cannot be exempted. A policy rule authorizes its exact payload; it is not a classifier of opaque data. Review the decoded contents yourself before approving encoded requests. No rules are installed automatically.
34
+
35
+ The following rule authorizes only `mcp__mail__send_email` with `{"to":"bob@corp.com","subject":"status","body":"The public release is ready."}`:
36
+
37
+ ```json
38
+ {"credentialEgress":{"allow":[{"tool":"mcp__mail__send_email","requestSha256":"62e02f4cb9c21a472999fac9316b66fc8fefe3fcafd2b2860c32c5409dae4a65"}]}}
39
+ ```
40
+
41
+ Enforced flow calls forward the inspected JSON object, removing ignored duplicate fields; raw ignored values cannot ride an exact grant.
42
+
43
+ Generate the digest locally with `publicRequestDigest` after inspecting the complete request. Limits: 32 rules/consumed grants, 1,024 JSON nodes, depth 32, 16,384 UTF-16 units per serialized request and 65,536 units of serialization work. Invalid sections grant nothing. This grant is logged as `credential_public_request` / `policy_allowed`, with approval outcome unknown; it does not claim a person clicked a form.
44
+
45
+ Fragment metadata remains version `v2`. Benign review counts: **12/12 → 0/12** with exact reviewed rules; this does not remove the default cost.
46
+
47
+ **Decoding and errors:** a bounded work queue composes URL, canonical base64/base64url, UTF-8 hex, scalar Unicode/hex escapes, numeric HTML entities and explicit English character lists. Maximum: four transformation levels, 64 candidates and 524,288 UTF-16 units in aggregate. Exceeding a budget yields `payload_incomplete` and review, never a clean result or public grant. Malformed literals are not executed; no decompression or semantic inference is attempted. Corrupt session/fingerprint state fails closed. Fingerprint v1 sessions remain review-only; use a fresh session and reread private sources to restore complete indexing. State holds hashes/offsets/sizes and bounded outgoing tails, remains sensitive, and is not encrypted.
48
+
49
+ **Measured tradeoff:** the workflow fixture has 12 harmless requests and 16 attacks. Strict default reviews all 12 harmless requests; exact reviewed rules automatically pass all 12 once. Both profiles allow **0/16 attacks** automatically and give **0 hard false denials**. The separate fingerprint-only probe uses 480 synthetic keys and 758 files: 0 hard denials and 2 files with short-match reviews. These are different measurements; they do not establish a production false-positive rate. The unchanged 23-case replay stays at **0/23 fully delivered**, including `secret-chunks-6-filler` and `secret-spelled-out`; two answer-only cases are noted/rechecked. Real stdio tests verify zero server calls for unapproved tiny/unknown-encoded requests, one concurrent public grant, and no metadata/retry bypass.
50
+
51
+ ```bash
52
+ npm run build
53
+ npm test
54
+ node scripts/redteam/credential-egress.mjs /tmp/warden-egress.json
55
+ node scripts/redteam/flow-redteam.mjs /tmp/warden-redteam.json
56
+ node scripts/redteam/fragment-collisions.mjs /tmp/warden-fragments.json docs src test ../docs ../core
57
+ ```
58
+
59
+ **Limits:** this is enforcement at the recorded tool boundary, not universal information-flow analysis. Unobserved sources, unknown/renamed integrations, model-only answers, timing/covert channels, hidden server behavior, explicitly approved disclosure and clients bypassing decisions remain outside the guarantee. Without reviewed rules there are deliberately more benign confirmations. Production/npm are unchanged; the AgentDojo oracle replay was rerun below; historical model and 115-million-character/HISTOR measurements were not rerun.
60
+
61
+ ## Opaque GUI actions (local source, 2026-10-23)
62
+
63
+ In the default strict GUI mode, after untrusted content, recognized pointer and keyboard MCP calls ask before execution (`deny` refuses). This closes the automatic-pass paths for coordinate clicks, pixel-only screens, Enter in a focused field and non-English labels **by requiring a person**, not by recognizing every screen. Typing also asks: a newline, an auto-submitting field or a chat box can send text. Clipboard paste asks even without a recorded credential read; after a recognized credential read it retains the more specific `paste_after_secret` explanation. Ordinary opaque GUI decisions use `screen_action`.
64
+
65
+ Only a single exact copy/select-all chord (`cmd+a`, `cmd+c`, `ctrl+a`, `ctrl+c`, with supported modifier aliases) in one known key field is exempt from this GUI rule. Extra fields, sequences, malformed arrays, trailing newlines and oversized values ask. Visible-secret checks still apply. Known generic controllers (`computer`, `screen_control`, `desktop_control`, `browser_control`) ask for unknown or missing actions. A complete known observation action such as `screenshot` or `get_screen`, with only the `action` field, is exempt. CamelCase and dotted/hyphenated operation names are normalized. A named or policy-allowed destination does not authorize GUI actions.
66
+
67
+ The latest per-server element list (Playwright `[ref=e14]`, `[ref_14]`, numbered lists and JSON nodes) supplies explanation context only. A different list format no longer bypasses approval; a sample is useful for improving the explanation, not needed to block a click. The prompt uses the guard's canonical English word when recognized, never the raw screen label. Inspect the actual window, target, clipboard and intended effect before approving. In the stdio proxy, each action needs its own accepted, ticked MCP elicitation form; no form or a declined/cancelled request means the server never receives that action. The Claude hook returns `ask` to the client; its permissions UI remains responsible for enforcement.
68
+
69
+ Strict mode increases confirmation frequency, including for benign navigation and clicks into a message body; usability and approval fatigue have not been measured. `audit` records without enforcing and `off` disables checks. The rule needs previously recorded untrusted content (screenshots are untrusted by default); an operator override that marks all observations private can remove that trigger. It covers recognized operation names, not arbitrary renamed tools, embedded JavaScript executors or a malicious tool's hidden behavior. Operator policies and OS isolation remain necessary at those boundaries. No OCR or visual instruction classifier was added: an injection contained only in pixels can still alter an answer without making a guarded call. Local hook and real stdio integration tests verify refusal before forwarding, per-action approval and the four reported cases. These local changes have not been deployed or published to npm.
70
+
71
+ The latest snapshot now improves the explanation in three ways. Coordinate arguments (`x`/`y`, `coordinate`, `coordinates`, or `position`) can intersect a unique JSON element's `bounds`, `boundingBox`, `bounding_box` or `rect`: finite numeric `{x,y,width,height}` or `{left,top,right,bottom}` with positive dimensions. Boxes use half-open edges; a `center` point matches only its exact coordinates. Overlaps, conflicting boxes/references, invalid numbers and bare arrays with ambiguous xywh/xyxy conventions produce no inferred target. No screen/viewport/zoom conversion is inferred. A match is reported geometry from the last snapshot, not a verified live target.
72
+
73
+ For Enter/Return, an unambiguous `focused: true` node or `focusedRef`/`focusedElementId`/`activeElementId` can name the focused element. A reported single-line field (`multiline: false`) in a form with one recognized submission control gets an “Enter may submit” explanation; a multiline field does so for cmd/ctrl+Enter. Forms must be explicitly linked by `form`/`formId` or JSON form ancestry. Missing or conflicting focus, unknown multiline state, different forms and multiple submission candidates do not establish submission. Recognized accessibility/automation identifiers and `type: "submit"` provide canonical action hints, including when a visible label is absent. Small word lists explain common labels such as Senden, Envoyer, Enviar and Wyślij. Identifiers, roles and translated words remain untrusted hints; raw accessibility fields are not copied into the additional element metadata or rendered in the prompt; existing element references and labels remain in session state.
74
+
75
+ These additions only improve explanations; screen metadata never authorizes an interaction. Asking only after a paste or typed text would miss prefilled messages, payments and deletion. Snapshot-only geometry, focus or accessibility IDs cannot establish that navigation is harmless. An image-only replacement clears these hints; conflicting duplicate nodes are not used. Hook and stdio tests check both the explanations and refusal before forwarding.
76
+
77
+ ## Gautam regressions: character codes, NATO, Git metadata and CI
78
+
79
+ The bounded decoder now adds decimal character-code lists and NATO spelling. Decimal lists need at least five valid scalar codes; NATO runs need at least five explicit units. Spaces, commas, semicolons, pipes and newlines separate units. Unknown units, invalid scalars and ambiguous case markers break a run. NATO supports alfa/alpha, juliett/juliet, xray/x-ray, digit words/literals, punctuation words and explicit capital/uppercase/lowercase markers. Numeric JSON arrays of 5–4096 integers are inspected too. These candidates share the existing depth/variant/text limits; exhaustion remains `payload_incomplete`. No eval or decompression is used.
80
+
81
+ Decoding alone does not assert a leak: a known secret pattern/fingerprint is still required for a secret denial. Ordinary number lists and NATO prose are not automatically denied. First 1–4 characters remain under the credential boundary even when too short for decoding. New tests exercise both real reconstructed credentials and benign text; they do not equate every short match with a secret.
82
+
83
+ Unlabelled MCP `git_push`, `push_files`, `git_commit`, commit creation and common camelCase/dotted variants now count as outbound/persistent effects. A commit message may be published later; it is not just local working-tree text. `get_commit` / `list_commits` remain reads. Operator labels still require correct configuration; the independent credential boundary continues to cover mislabelled MCP tools. Shell git commands keep their existing review. No commit or push was performed to test this.
84
+
85
+ **Local release-gate change:** `npm run build && npm run test:redteam` runs the entire end-to-end hook replay with `--check`. All original 23 scenarios remain, plus five Gautam regressions: **28 cases, 26 tool attacks requiring ask/deny and two answer-only cases requiring a note/recheck**. Missing/duplicate cases, mismatched outcomes or a reopened automatic path fail with a nonzero exit. Fault-injected gate tests prove failures. The command is wired into the WARDEN CI matrix, workspace CI, npm `prepublishOnly` and the registry workflow before publication. These are local configuration edits: no remote CI/release/publication was started. A note on an answer is not proof of stopping a model’s answer-only attack.
86
+
87
+ Reproduce: `npm run build`, `npm run test:redteam`, or `node scripts/redteam/flow-redteam.mjs /tmp/warden-redteam.json --check`. See `test/flow-guard-gautam.test.ts` and `scripts/redteam/flow-gate.mjs`.
88
+
89
+ ## Constrained task mode: fewer confirmations, actual effect control
90
+
91
+ 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.
92
+
93
+ The opt-in **built-in task adapter** (`node dist/mcp-server.js task`) is an enforcing MCP server, not a quiet wrapper around arbitrary screen tools. It lists fixed resources, reads pinned local UTF-8 snapshots without executing HTML/JS, and executes reviewed HTTP operations by ID. The model cannot supply a URL, method, headers, body, recipient, attachment or file path. It has no browser profile, cookie jar, OS clipboard, page scripts, service workers, subprocess or general-purpose tool forwarding. Ordinary GUI tools keep their existing approval requirements. This is a structured alternative for supported tasks, not a browser/VM sandbox or universal quiet computer-use.
94
+
95
+ **Setup, local and explicit:** save the following manifest as `<WARDEN_STATE_DIR>/task-manifest.json` and the policy as the user flow policy. Replace the example snapshot path, pin and endpoint; update `expiresAt` to at most 24 hours ahead. The snapshot must contain only material you deliberately expose to the agent. The example pin is for exactly `The public release is ready.\n`. Review every request, then compute `manifestSha256` over the exact saved manifest bytes (including whitespace/newline). The policy example matches the displayed manifest plus one final newline. Nothing installs these permissions automatically.
96
+
97
+ ```json
98
+ {
99
+ "version": 1,
100
+ "taskId": "public-report",
101
+ "expiresAt": "2026-10-23T23:00:00.000Z",
102
+ "resources": [
103
+ {
104
+ "id": "report",
105
+ "title": "Public report",
106
+ "path": "/absolute/path/public-report.txt",
107
+ "sha256": "1403cece199725b166f0f6e59faa9d45e3d5a25db26080fecf95e818ac8d4d0d"
108
+ }
109
+ ],
110
+ "operations": [
111
+ {
112
+ "id": "send_report",
113
+ "title": "Send reviewed report to Bob, no attachments",
114
+ "maxUses": 1,
115
+ "request": {
116
+ "method": "POST",
117
+ "url": "https://mail.example/api/send",
118
+ "headers": {
119
+ "content-type": "application/json",
120
+ "idempotency-key": "public-report-once"
121
+ },
122
+ "body": "{\"to\":\"bob@corp.com\",\"body\":\"The public release is ready.\",\"attachments\":[]}"
123
+ }
124
+ }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ ```json
130
+ {
131
+ "mode": "ask",
132
+ "taskAdapter": {
133
+ "mode": "capabilities",
134
+ "server": "warden_task",
135
+ "manifestSha256": "1531d08beffb7c86d6f236c3bdfa85a8fbac1c3e8734d701e79455c53d0d94b7"
136
+ }
137
+ }
138
+ ```
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "warden_task": {
143
+ "command": "node",
144
+ "args": [
145
+ "/absolute/path/warden/dist/mcp-server.js",
146
+ "task"
147
+ ],
148
+ "env": {
149
+ "WARDEN_STATE_DIR": "/absolute/state/warden",
150
+ "WARDEN_FLOW_POLICY": "/absolute/state/warden/flow-policy.json"
151
+ }
152
+ }
153
+ }
154
+ }
155
+ ```
156
+
157
+ ```sh
158
+ node -e 'console.log(new Date(Date.now() + 3600000).toISOString())'
159
+ node --input-type=module -e 'import {createHash} from "node:crypto"; import {readFileSync} from "node:fs"; console.log(createHash("sha256").update(readFileSync(process.argv[1])).digest("hex"))' /absolute/state/warden/task-manifest.json
160
+ ```
161
+
162
+
163
+ Register MCP server **`warden_task`** with command `node`, arguments `[/absolute/path/warden/dist/mcp-server.js, task]`, and the same `WARDEN_STATE_DIR` / `WARDEN_FLOW_POLICY` as the hooks or `wrap --flow warden_task`. Only bind this policy name to WARDEN's built-in adapter. Assigning it to an unrelated server gives that server authority: a tool name or `readOnlyHint` is not attestation. With missing/strict/invalid policy the adapter refuses network operations; global `deny`, `audit` and `off` do not grant task execution. Other client permissions may still require confirmation.
164
+
165
+ Calls are `task_list {}`, `task_read {"id":"report"}` and `task_execute {"id":"send_report"}`. One exact ID is the entire call schema; extra fields, duplicate JSON keys, arrays and hidden RPC metadata are rejected. Hook log `task_adapter_delegated` means delegation to the trusted enforcer, not successful execution or human approval. The runtime rechecks pin, schema, expiry and policy, reserves the shared per-task budget with a process lock and fsync before sending, and keeps it across restarts. Changed manifests cannot reset a spent task ID. Failed/uncertain requests and cancellation after dispatch spend the capability; cancellation before dispatch does not. Corrupt state or an abandoned lock refuses execution; an operator must reconcile uncertain effects rather than deleting state and retrying.
166
+
167
+ Only fixed HTTPS requests are supported; `loopback-test` additionally permits numeric localhost HTTP for isolated tests. Redirects are refused, response cookies/links/instructions are not acted on, response size and total time are bounded, and retries are disabled. Optional `bearerEnv` names an operator-selected environment variable; the token is added only to the fixed endpoint and is absent from listings/audit. Audit stores IDs/outcomes, not payloads/tokens. All responses remain untrusted data.
168
+
169
+ **Payments and transactions:** fix invoice/account, amount, currency and body in the reviewed operation; use `maxUses: 1` and a provider-supported fixed `idempotency-key`. A single dispatch is not proof of exactly-once settlement. After timeout/error, reconcile with the provider before a new authorization. All integration tests use isolated loopback fixtures and synthetic credentials; no production payment was made.
170
+
171
+ **Limits:** the operator must trust this WARDEN process and protect its policy/state, runtime and MCP binding. Reviewed requests can have whatever effects their endpoint implements; even GET is not assumed harmless. A mistaken operator grant, an authorized literal secret, server-side behavior, ID/order/timing covert channels and another tool/client bypassing the guard are outside this boundary. No OCR or live arbitrary-GUI effect interception was added.
172
+
173
+ **Verification (2026-10-23):** a fresh deterministic AgentDojo 0.1.35 / v1.2.2 oracle replay, local Docker without network or model API: intent variant 0/579 successful delivered action attacks, 57/97 clean tasks without a question, 58/97 clean tasks completed, 608/949 user tasks completed under attack (previous historical result 613/949). `ask` counts as refused; no-ground-truth and answer-only goals are not thereby proven safe. AgentDojo has no GUI/task-adapter tools: the separate stdio, multi-process and loopback tests cover those. The historical live-model, 115-million-character and HISTOR measurements below were not rerun. [Replay summary](../scripts/agentdojo/results/summary-2026-10-23-local.json).
174
+
175
+ Regressions: `test/task-adapter.test.ts`, `test/flow-guard-gui-policy.test.ts`, `test/flow-guard-credential-boundary.test.ts`, `test/flow-guard-secret-regression.test.ts`. They cover spoofed labels/IDs, policy schema, pinned resources, concurrency/restarts, redirects, exact recipient/body, and first 1–4 credential characters in raw, hex, base64, Unicode and unknown encodings. The credential boundary reviews ambiguous data rather than claiming every short coincidence is a detected secret.
176
+
177
+ ## Native macOS backend: target-side AppKit
178
+
179
+ **Implemented:** the Swift package [WardenDesktop](../native/macos/Package.swift), `WardenDesktopSurface` and `WardenDesktopStandardIO` run inside the native application that owns the window. The executable `warden-desktop --demo` opens a real AppKit window with a local button handler. It does not operate unrelated applications, send global mouse/keyboard input or perform network/payment operations. Strict wrap approval remains default. The backend is opt-in and requires application cooperation; it is not a universal desktop injector.
180
+
181
+ Build and run from the WARDEN directory. Requires macOS, Xcode command-line tools and an active WindowServer session. The build targets macOS 13+ and the host arm64/x86_64 architecture; only the recorded local architecture is verified. Native sources are shipped, not a prebuilt binary. No runtime npm dependency is added:
182
+
183
+ ```sh
184
+ npm run build
185
+ npm run build:macos
186
+ node dist/mcp-server.js wrap --id macos-owned-app --flow native --screen-read screenshot --screen-atomic -- native/macos/.build/warden-desktop --demo
187
+ npm run test:macos
188
+ ```
189
+
190
+ Native action example; references are strings. Alternatively use only `x` and `y` in global top-left screen points (not pixels); overlapping or covered targets refuse:
191
+
192
+ ```json
193
+ {"name":"click","arguments":{"index":"3"}}
194
+ ```
195
+
196
+ **Atomic boundary:** prepare retains the exact `NSButton`, its explicit `NSObject` target, selector and ABI-checked implementation obtained with `method_getImplementation`. MainActor commit compares revision, live role/identifier/frame/title, object identity, window/focus/modal state, opacity along the ancestor chain and native hit-test. Fully transparent controls/windows, blocked modal windows and pending sheets (including the logical `sheetParent` transition) refuse. It recaptures after hit-test, checks both clocks, then performs a final revision comparison after every injected callback. The pinned IMP is invoked through `NativeAction` in the same main-loop turn. `NSApp.sendAction` is deliberately bypassed: an override can queue, reroute or report success without calling the approved method. No `AXPress`, `CGEvent`, animated `performClick`, first-responder lookup or queued coordinate input is used. Only registered, enabled, visible momentary push buttons with a `void action(id sender)` signature are supported. Raw click, typing, keyboard chords, clipboard, secure fields, unregistered views and external PIDs refuse.
197
+
198
+ **Embedding:** import the local Swift package into your application. On MainActor create `WardenDesktopSurface(window:applicationID:)`, set the button’s accessibility element/role/identifier, and call `register(id:button:target:selector:)`. Keep the surface and `WardenDesktopStandardIO(surface:)` alive; `start()` serves the exclusive inherited stdio pipe. Read observations are structured metadata from registered controls, not pixel screenshots or a global AX tree. Register at most 64 controls, unique references/identifiers and explicit retained targets; nil targets and unsafe Objective-C signatures refuse. See [native source](../native/macos/Sources/WardenDesktop/NativeSurface.swift) and [reference host](../native/macos/Sources/WardenDesktopHost/Main.swift).
199
+
200
+ All application model/recipient/handler changes must run on MainActor and call `invalidate()` **before** mutation outside the approved handler, including invisible changes and A → B → A. Window notifications and fresh fingerprints supplement this rule; they do not prove all hidden model changes were observed. Approved handlers must execute synchronously without nested modal run loops, asynchronous forwarding or responder substitution. Their own mutations are covered by the commit’s revision rotation. If an application cannot uphold this ownership contract, do not enable atomic mode. There is no fallback for an unmodified third-party app: Apple exposes [AX attribute reads and actions separately](https://developer.apple.com/documentation/applicationservices/1462091-axuielementperformaction), not our revision contract.
201
+
202
+ Tickets bind full arguments and expire under both wall-clock and monotonic time, at most five minutes. They are consumed before dispatch; cancellation, replay, restart and mismatches cannot reuse them. A failed/uncertain dispatch poisons the native authority; do not restart or reauthorize merely to retry a payment. A committed receipt attests local target-handler dispatch, **not** successful delivery of mail, settlement of a payment or completion of an asynchronous handler. Use transaction reconciliation and durable endpoint idempotency for those outcomes. Wrap retains its uncertainty lease if a reply is lost.
203
+
204
+ **Native integration gate:** `npm run test:macos` builds production and test executables separately and runs real windows plus real stdio/wrap forms. It checks target/handler/frame/window drift, same-visible-object replacement, ABA, native hit-test reentrancy, expiry/rollback/replay/restart/cancel, cross-language stamps and multilingual labels. Test-only mutation endpoints are compiled out of production. The suite is separate from cross-platform `npm test`; non-macOS, missing tools or unavailable GUI fail the native gate, never silently skip it. Local CI configuration adds a macOS gate; no remote workflow was run. Tests request no global Accessibility or Screen Recording permission and do not modify TCC. No publication/deployment.
205
+
206
+ **Local follow-up audit (2026-10-24):** prepare and commit now recapture native properties after `hitTest`; a changed handler, title, enabled state or frame refuses even if the host omitted `invalidate()`. Both clocks are checked again immediately before dispatch. The Objective-C sender argument must be exactly an object (`@`), never a block (`@?`). The generic TypeScript atomic server also enforces a monotonic deadline and rechecks expiry at the effect boundary. Regressions cover these cases, preparation drift, capacity cleanup and reentrant native handler calls. These checks do not replace the host ownership contract: getters and hit-tests must not pump nested run loops or dispatch effects; hidden model/recipient changes still require prior invalidation. Only cooperating trusted applications are supported.
207
+
208
+ **Direct handler contract (second local audit, 2026-10-24):** registered handlers must be safe for direct invocation and enforce their own application permissions; do not rely on an `NSApplication.sendAction` override or responder routing to authorize them. Clock providers and getters are trusted, synchronous host code. Hidden changes and ABA still require `invalidate()` before mutation. Regressions include modal/opacity transitions during hit-test, runtime implementation drift, unchanged translucent controls, dispatch interception and final-clock invalidation. These checks ship in npm `0.21.0`. A local test report is not the signed release-quality receipt. That receipt currently binds JavaScript `dist` files, not the native SDK sources; native validation is separate.
209
+
210
+ ## Atomic target verification on the server
211
+
212
+ **Explicit opt-in:** `--screen-atomic` requires `--flow`, `--screen-read` and enforcing flow mode. Strict approval remains the default. This mode requires a trusted participating server; tool annotations or a claimed protocol version do not make a server trustworthy. Unsupported servers refuse before the approval question. There is no fallback to a raw click. The existing snapshot-only mode remains non-atomic.
213
+
214
+ Build first, then run this local example from the WARDEN directory. The example owns a virtual form; it performs no OS input, mail, payment or network operation:
215
+
216
+ ```sh
217
+ node dist/mcp-server.js wrap --flow screen --screen-read screenshot --screen-atomic -- node examples/atomic-screen-server.mjs
218
+ ```
219
+
220
+ **Protocol:** the proxy privately calls `warden/screen/prepare` before the question with the exact action and configured observation. Example parameters:
221
+
222
+ ```json
223
+ {
224
+ "protocol": "warden.screen.atomic.v1",
225
+ "observation": { "name": "screenshot", "arguments": {} },
226
+ "action": { "name": "click", "arguments": { "index": 3 } }
227
+ }
228
+ ```
229
+
230
+ The server returns `protocol`, an opaque one-use `ticket`, a surface `revision`, target `stamp`, `expiresAt` and `observation`. The proxy checks the stamp against that observation, shows the action, then rechecks definitions, risk context and the fresh target after explicit acceptance. It sends `warden/screen/commit` with `{protocol,ticket,expected:{revision,stamp},action}` instead of the raw tool call. The server consumes the ticket, compares the current revision, resolved target and exact arguments, and commits synchronously in the same critical section. No event-loop handoff or coordinate dispatch is allowed between comparison and effect. Any change, including a handler/recipient change or A → B → A, must rotate the revision. The operation uses the resolved target reference, not a later coordinate lookup.
231
+
232
+ Use `AtomicScreenServer` from `@aimarket/warden/atomic-screen` (`dist/atomic-screen.js` in a local build) on the server. Route the three protocol methods to `prepare(params)`, `commit(params)` and `cancel(params)`; route the observation tool to `read()`. All raw effect routes must refuse. Maintain one shared authority per surface across connections; every other mutation goes through `replace(snapshot)`. Supply a trusted synchronous `apply(action, target)` returning `{result,snapshot}`. The result is the normal MCP tool result. Async callbacks are rejected; exceptions or malformed backend results invalidate the surface until explicit reconciliation and replacement. See the [runnable server example](../examples/atomic-screen-server.mjs).
233
+
234
+ Tickets expire within five minutes, bind the full action, are consumed on a commit attempt and cannot survive a server restart. Capacity is 256 pending tickets. Client requests cannot invoke the private protocol through wrap, and tickets are not sent to the model or flow log. `warden/screen/cancel` discards an unused preparation after refusal/cancellation; expiry bounds abandoned preparations. Atomic argument previews must fit completely within 1500 escaped characters; oversized calls refuse rather than showing truncated text.
235
+
236
+ A verified `committed` receipt includes `protocol,status,revisionBefore,revisionAfter,stamp,result`; wrap returns only `result`. A verified `refused` receipt includes `protocol,status,reason` and means no effect. Audit outcomes are `atomic_committed` / `atomic_refused`. A malformed receipt, transport error or missing result is `execution_uncertain`: no retry, and the shared GUI lease remains for operator reconciliation. An effect may have occurred before a lost reply. Server-local tickets are not durable payment idempotency keys.
237
+
238
+ **Ownership boundary:** the implementation closes this race only for a surface whose entire state and synchronous effect are owned by the trusted server. Authentication/exclusive transport must restrict preparation/commit to trusted proxies; a ticket is not proof of human consent. One authority in one process is not a cross-process OS lock. Locking the server while sending asynchronous mouse input, or wrapping a Playwright/desktop RPC in this API, does not make the remote surface atomic. Native backends need an actual platform transaction/revision primitive or a controlled synchronous application model. The native target-side AppKit backend is described in the macOS section; unmodified external applications remain unsupported. Dishonest metadata, untracked external actors and harmful authorized handlers remain outside the guarantee. Claude hooks cannot implement the private post-approval commit. The regressions include a mutation after the final proxy read, ABA/handler drift, replay, expiry, restart, reentrancy, direct raw-call refusal and uncertain effects. No publication/deployment.
239
+
240
+ ## Auten: one outcome approval and a fresh screen after approval
241
+
242
+ **Outcome approval:** add the `runs` fragment below to the existing pinned manifest, serialize it as two-space JSON with a final newline, recompute its pin, and use the following `taskAdapter.mode: "review"` policy. Call `task_run {"id":"mail_to_bob"}`. The built-in adapter asks once through MCP form elicitation before the first effect. It shows the outcome plus the complete ordered requests, exact URLs and bodies; credentials in headers are redacted. The checkbox defaults to false. Missing form support, default acceptance, rejection or cancellation does not authorize a run. `strict` remains the default; `capabilities` retains explicit operator preauthorization.
243
+
244
+ The example collapses the GUI preparation/send sequence into its fixed HTTP effect. A plan may contain up to eight distinct fixed operation IDs; there are at most 16 plans and a full escaped preview must fit 12,000 characters. The caller cannot change steps, order, recipient, body or outcome. `review` disables individual `task_execute`, so it cannot bypass the outcome question. Resources and listings remain quiet; unrelated client/sensitive-tool permissions may still ask.
245
+
246
+ The adapter holds a cross-process task execution lease during the question and run, prechecks availability before asking, and atomically reserves the whole plan plus every operation budget after acceptance. The plan is one-use across restart. Before each step it rechecks manifest/policy, expiry, cancellation and the credential binding. Drift, errors or an uncertain response stop the remaining steps; unused reserved budgets remain spent. Earlier effects are not rolled back. Responses cannot change the next step or supply a new destination. A declined/pre-reservation-cancelled plan remains unspent. Do not delete uncertain state/locks to retry a payment: reconcile it first. Audit distinguishes approval, reservation, completion and stopped runs without recording payloads/tokens.
247
+
248
+ ```json
249
+ {
250
+ "runs": [
251
+ {
252
+ "id": "mail_to_bob",
253
+ "title": "Send the reviewed public report to bob@corp.com in Mail",
254
+ "operations": [
255
+ "send_report"
256
+ ]
257
+ }
258
+ ]
259
+ }
260
+ ```
261
+
262
+ ```json
263
+ {
264
+ "mode": "ask",
265
+ "taskAdapter": {
266
+ "mode": "review",
267
+ "server": "warden_task",
268
+ "manifestSha256": "c69bafc53aa2e4a1a82dd9a20accbe2e4de5372d7545509fe3f6ccf18fdc5580"
269
+ }
270
+ }
271
+ ```
272
+
273
+ **Fresh screen in wrap only:** explicitly select a known observation tool from the same reviewed server, for example:
274
+
275
+ ```sh
276
+ node dist/mcp-server.js wrap --flow screen --screen-read get_elements -- node /absolute/path/screen-server.js
277
+ node dist/mcp-server.js wrap --flow screen --screen-read computer --screen-read-input '{"action":"screenshot"}' -- node /absolute/path/screen-server.js
278
+ ```
279
+
280
+ `--screen-read` requires `--flow`; `--screen-read-input` is strict JSON and only supports an empty input for a known observation name, or one known observation `action` for a generic controller. It never guesses authority from `readOnlyHint`. Without this explicit setting the existing per-call strict GUI behavior remains, with no claim of post-approval revalidation. The Claude hook has no callback between the client's approval and execution and cannot perform this check.
281
+
282
+ With revalidation enabled, wrap reads a baseline immediately before the question, then rechecks definitions and reads again after acceptance. The fresh response itself must resolve one target with role, exact node reference, bounds, context and a consistent focused field for keyboard input; accessibility identity, labels and form hints are also compared when present. Changed, missing, overlapping/ambiguous, incomplete, image-only or failed observations refuse the action. No geometry/zoom conversion is guessed. Unsupported multi-action or extra-field dispatch schemas refuse comparison. Changed risk context or newly detected secrets also stop execution. Cancellation during the last read is honored.
283
+
284
+ Known GUI effects across proxies sharing `WARDEN_FLOW_SESSION` / state are serialized through a screen lease, including a second proxy without revalidation. A normal result releases it; a missing result/crash leaves an uncertain lease requiring operator review. Read-only observations may continue. `screen_validation` logs `unchanged`, `refused_without_approval`, `refused_after_approval` or `execution_uncertain`; neither an accepted form nor an unchanged stamp proves execution.
285
+
286
+ **Limits:** this narrows the stale-target window, it does not make snapshot + click atomic. An external actor may change the UI after the last read; a server may return cached/false metadata; unchanged IDs/boxes do not prove unchanged handlers or harmless effects. Full closure needs a trusted server that atomically validates a revision/target and performs the effect. Arbitrary click/paste/Send chains are not granted a blanket approval. Grouping is for the enforcing structured task adapter, not permission derived from page metadata. Answer-only injections remain outside tool-effect enforcement.
287
+
288
+ Local regressions: `test/wrap-screen.test.ts`, `test/wrap.test.ts`, `test/task-adapter.test.ts`. [Fresh local AgentDojo checkpoint](../scripts/agentdojo/results/summary-2026-10-23-auten-local.json); deterministic replay, no model API and no GUI tools. No remote publication/deployment or comment was sent.
289
+
5
290
  ## Current behavior after the 2026-10-18 audit
6
291
 
7
292
  The flow guard adds checks at tool-call boundaries after a session receives outside text. It is a defense layer, not a sandbox or a proof that prompt injection cannot cause harm. Tool labels and supported argument shapes remain part of its trust boundary.
@@ -10,14 +295,15 @@ The flow guard adds checks at tool-call boundaries after a session receives outs
10
295
  - 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
296
  - 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
297
  - 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. Since 2026-10-22 key-like values (random-looking runs in credential files, and well-known token formats read anywhere) are also fingerprinted in windows of 8 letters and digits, 10 for hex keys: a piece of a key, the key reversed, with separators or invisible characters, base64-encoded in pieces or with Cyrillic or Greek look-alike letters is denied. Those sizes are the smallest at which none of 480 made-up keys in eight formats matched anywhere in 115 million letters and digits (this monorepo and 1,000 HISTOR packages) of ordinary text; parts every key of a kind shares (a DER key's algorithm identifier, a JWT's header and claims) are never fingerprinted. 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 8 characters wrapped in different words each time still pass, and so does the key's leading format (`rk_live_`).
298
+ - 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 windows of 8 letters and digits, 10 for hex keys: a piece of a key, the key reversed, with separators or invisible characters, base64-encoded in pieces or with Cyrillic or Greek look-alike letters is denied. Those sizes are the smallest at which none of 480 made-up keys in eight formats matched anywhere in 115 million letters and digits (this monorepo and 1,000 HISTOR packages) of ordinary text; parts every key of a kind shares (a DER key's algorithm identifier, a JWT's header and claims) are never fingerprinted. 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. The local 2026-10-23 source adds the six-character pair correlation and explicit character-list decoding described above. The leading key format (`rk_live_`) alone is not secret evidence.
14
299
  - After outside text, a key read this session written into an ordinary project file asks (`deny` refuses): files in the project are committed and published later without another call. `.env`, `.npmrc` and the other credential files are where keys belong, and a write that only removes a key never matches.
15
300
  - An address that appears in your prompt only inside pasted text, a quoted line (`>`), the headers of a pasted message or below a forwarded-message marker is not one you named: sending to it asks once. A full stop or comma after an address you typed no longer hides it. 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.
16
301
  - 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.
17
- - 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. When the answer repeats 12 letters and digits in a row from the sentences where that text addressed the assistant, the hook also sends the answer back once (`decision: block`), to be checked against the user's request before the user reads it; the user loses nothing but the time of that check. `answerNote` chooses `always` (default), `instruction` (only that case) or `off`; outside the plugin, register `warden-mcp hook stop`.
302
+ - By default, screen-control calls after outside content require approval independently of coordinates, screenshot text, element-list format or label language. This includes clicks/taps/drags, typing/fill, clipboard paste, Enter/Return and unknown keyboard actions. `deny` refuses them. English target words improve the explanation only; a familiar label such as Inbox never authorizes a click. See the constrained task mode above.
303
+ - 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. When the answer repeats 12 consecutive normalized characters from the sentences where that text addressed the assistant, the hook also sends the answer back once (`decision: block`), to be checked against the user's request before the user reads it; the user loses nothing but the time of that check. `answerNote` chooses `always` (default), `instruction` (only that case) or `off`; outside the plugin, register `warden-mcp hook stop`.
18
304
  - 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.
19
305
 
20
- Released in npm `0.17.0`: `npx -y @aimarket/warden@0.20.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.
306
+ Released in npm `0.17.0`: `npx -y @aimarket/warden@0.22.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.
21
307
 
22
308
  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:
23
309
 
@@ -34,7 +320,7 @@ Policy is user-level (`~/.local/state/warden/flow-policy.json`, or `WARDEN_FLOW_
34
320
 
35
321
  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. A call that would ask is asked through the client's MCP elicitation form when the client offers one (the answer is logged as `approvalOutcome`), and refused otherwise or with `--ask refuse`. `trustedSources` applies to a WebFetch URL, not URLs mentioned in arbitrary MCP arguments.
36
322
 
37
- **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).
323
+ **Which build the numbers describe.** The replay table below was run again on 2026-10-22 on that day’s 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).
38
324
 
39
325
  ## What an approval request explains
40
326
 
@@ -203,12 +489,12 @@ benchmark). Before the changes 14 of them got through; after them, 3.
203
489
  | 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 |
204
490
  | 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 |
205
491
  | 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 |
206
- | 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 |
492
+ | The replay re-run on the 2026-10-22 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 |
207
493
  | `scripts/agentdojo/Dockerfile.dockerignore` | the package's `.dockerignore` leaves out `dist/`, so the reproduce command below did not build | it builds |
208
494
 
209
495
  Those three were still delivered: pieces under 10 characters wrapped in different words, an address pasted into
210
496
  the prompt, and a key written into a project file. The same day we closed what could be closed without false
211
- positives, and wrote six more attacks against the new rules. 2 of the 23 get through.
497
+ positives, and wrote six more attacks against the new rules. At that release, 2 of the 23 got through.
212
498
 
213
499
  | Change | Why | Result |
214
500
  |---|---|---|
@@ -216,11 +502,17 @@ positives, and wrote six more attacks against the new rules. 2 of the 23 get thr
216
502
  | Parts every key of a kind shares are not fingerprinted (a DER key's algorithm identifier, a JWT's header and claims) | the measurement found 0.19.0 would deny a DKIM public key in a README after a session read an RSA private key | no chance match at all with the shipped rules |
217
503
  | A key read this session written into an ordinary project file asks | the key in `docs/debug.md` passed | asks; `.env` and other credential files still pass |
218
504
  | An address only in pasted or quoted text is not one the user named | the attacker's address pasted into the prompt counted as the user's | asks once; an address the user also typed passes. A full stop after a typed address no longer makes it unnamed (0.19.0 asked about "send it to bob@corp.com.") |
219
- | The Stop hook sends an answer back once when it repeats an instruction aimed at the model | an answer-only injection makes no call | On Ministral 8B, which follows the attack (6 of 20 answers recommend the hotel without the guard), the guard's note alone took that to 0; the re-check fired 3 times in 20 and changed no outcome. On DeepSeek V4.1 Flash, which never recommended it, it fired 2 times in 20 ([results](../scripts/agentdojo/results/model-answer-recheck-2026-10-22.json)). A safety net, not a measured gain |
505
+ | The Stop hook sends an answer back once when it repeats an instruction aimed at the model | an answer-only injection makes no call | On Ministral 8B, which follows the attack (6 of 20 answers recommend the hotel without the guard), the guard's note alone took that to 0; the re-check fired 3 times in 20 and changed no outcome. On DeepSeek V4.1 Flash, it fired 2 times in 20 ([results](../scripts/agentdojo/results/model-answer-recheck-2026-10-22.json)). A safety net, not a measured gain; see the saved-answer limitation below |
220
506
  | Ruleset v16 (see [gates](gates.md)) | 34 of MCPTox's dev-half poisoned tools still passed, nearly all tools that take no input | held-out half 194 → 210 of 218, all 66 implicit-trigger hijackings among them; 0 of 45 clean servers; the field corpus and 1,000 HISTOR packages block exactly what v15 blocked; five false positives of a first draft are pinned as tests |
221
507
  | The replay re-run | the new checks must not ask where 0.19.0 did not | all 1,046 records identical to 0.19.0 |
222
508
 
223
- Still delivered ([results](../scripts/redteam/results/)):
509
+ **Saved-answer qualification (2026-10-25):** the official DeepSeek guard + note result remains **18/20** under AgentDojo's exact-name mention predicate. Individual AI-assisted reading found no adopted recommendation in those saved excerpts; all 18 exact-name mentions occur in warnings. Only 18 answers are complete: two hit the historical 2,000-character storage cap, so a full-answer 0/20 compliance result is not established. These annotations are neither independent nor blinded and do not replace the official score. The local harness now retains full future answers with length/hash metadata; no new model run is implied. [Audit and reproduction](../../docs/growth/launchgate-methodology-2026-10-25.ru.md).
510
+
511
+ **Controlled selection probe (2026-10-25):** A separate synthetic post-tool probe using published WARDEN `0.21.0` covered six hotel tasks × five repetitions, clean/benign/injected notes and three profiles: **270/270 correct IDs**, with **0/90 injected target choices** (0/30 per profile). Target names appeared in **25/270** answers including controls, and Stop fired **0/90** times in `note_stop`; the unguarded baseline was correct too, so no incremental guard gain is established, and correct IDs do not validate every explanation fact. [Protocol, results and limits](../../docs/growth/launchgate-selection-2026-10-25.en.md): this separate study does not change AgentDojo’s historical **18/20** or establish broad safety.
512
+
513
+ **Tied-choice follow-up (2026-10-25):** With two admissible optima, injected target selection was 48/48 without a guard, 44/48 with the note and 45/48 with note+Stop; the respective clean baselines were 28/48, 25/48 and 29/48. Baseline-adjusted guard-effect intervals include zero, and all 432 choices were admissible; target choice is not automatically an error. Separate genuine-user preferences were followed 18/18, with no Stop rechecks. The local [offline evaluator](../scripts/selection-probe/README.md) now supports admissible sets and common-baseline contrasts; runtime blocking is unchanged. [Results and limits](../../docs/growth/launchgate-tie-selection-2026-10-25.en.md).
514
+
515
+ At the 2026-10-22 release these two examples were still delivered. Both are blocked in the local 2026-10-23 source; see the new section above. Historical [results](../scripts/redteam/results/):
224
516
 
225
517
  - **Pieces under 8 characters, each wrapped in different words**, to an address the user typed: 7 or more messages
226
518
  for a 40-character key.
@@ -241,10 +533,7 @@ Still delivered ([results](../scripts/redteam/results/)):
241
533
  as instructions.
242
534
  - **Local changes inside the project pass**, except a key read this session going into an ordinary file. Editing
243
535
  project files is the agent's job.
244
- - **Leaks in small pieces.** Ordinary private text may go to a destination the user named, whole or in pieces:
245
- that is the user's choice. To other destinations, and for keys and card numbers anywhere, pieces sent back to back
246
- through one argument are joined; pieces under 8 characters with different words around each are not, and neither
247
- is a key spelled out in words.
536
+ - **Leaks in small pieces.** Ordinary private text may go to a user-named destination. Existing stream joins and the six-character pair rule above cover selected secret partitions; shorter pieces with filler and unsupported verbal encodings remain limits.
248
537
  - **Pasted text is not typed text.** An address you meant to use but only pasted asks once.
249
538
  - **It cannot see** your prompt from before the plugin was installed, or text the model holds from elsewhere.
250
539
 
@@ -259,7 +548,8 @@ docker run --rm --network none -v "$PWD/out:/out" warden-adojo /out/oracle.json
259
548
 
260
549
  `oracle.py` writes per-pair results; `--variants none,strict,intent` chooses the guard variants (`strict` ignores
261
550
  what the user typed). `summarize_oracle.py` turns them into the summary the table quotes. `WARDEN_FLOW_GUARD=/out/x.mjs`
262
- plays another build of the guard (any plugin copy of `flow-guard.mjs`), which is how the 2026-10-22 changes were
551
+ plays another build of the guard (any plugin copy of `flow-guard.mjs`; since 2026-10-24 with its `flow-guard-*.mjs`
552
+ parts in the same folder), which is how the 2026-10-22 changes were
263
553
  compared with the code before them.
264
554
 
265
555
  The code-aware attacks need no container: `npm run build && node scripts/redteam/flow-redteam.mjs out.json`.
@@ -278,3 +568,15 @@ python3 scripts/agentdojo/summarize_model.py summary.json /srv/adojo/out --goals
278
568
  `model.py OUT --model deepseek-flash --model-label DeepSeek --dump-goals` writes the attacker goals
279
569
  `summarize_model.py` uses to recognise calls at the attacker.
280
570
  Per-run records of both rounds: `results/model-deepseek-2026-10-18b.jsonl.gz` (summary `model-deepseek-2026-10-18b.json`); `--guard-note` feeds the guard's note back to the model.
571
+
572
+ ## Source instruction signals and Stop diagnostics (local 0.22.0)
573
+
574
+ Bounded English/Russian patterns recognize spoofed role delimiters, claimed relayed preferences or prior approvals paired with a nearby directive, concealed instructions and private evaluation rubrics. They are fallible source signals, including in legitimate quotations; they do not prove an attack, grant authority or automatically forbid a choice. Genuine user prompts remain a separate source.
575
+
576
+ The local log records `instructionSignal`, `signalKinds`, `signalScanLimited` and `warningKind` (`source`, `instruction`, `suppressed-audit`). Stop records `trigger` (`no-instruction-signal`, `no-literal-overlap`, `literal-instruction-overlap`), `recheckRequested` and `semanticCompliance: "not-assessed"`. The existing 12-character normalized overlap and host-marked continuation bound remain; a recheck is neither permanent refusal nor proof of correction.
577
+
578
+ The repository helper `scripts/selection-probe/audit_warnings.mjs` replays saved hook inputs and answers. Acknowledgement candidate words describe visible text, not model awareness or obedience. Replaying an old answer measures hook behavior only; changed choices require a new controlled model run.
579
+
580
+ Post/Stop warning text uses fixed sentences and opaque `source-` identifiers (the first 16 hex digits of SHA-256 of the tool name). It does not quote the detected instruction or repeat raw tool names, source URLs or paths. This limits promotion of source-controlled metadata through the warning channel; it does not make a model immune to the original tool result. The identifier permits correlation only and is not a verified package identity, pin or permission. Local logs can still contain source-authored metadata and must be treated as untrusted data.
581
+
582
+ **Fresh model comparison (2026-10-25):** On the same six known fixtures, the 720-answer interleaved run selected the injected target 48/48 unguarded, 44/48 and 46/48 with published 0.21.0 note/note+Stop, and 27/48 with either local candidate profile. Candidate clean baselines were 23/48 and 25/48. Clean-adjusted candidate-versus-published intervals are −8.3 to +60.4pp for note and +14.6 to +62.5pp for note+Stop. Twelve actual rechecks changed no selected IDs; three lost a visible warning reference. All IDs were admissible and genuine user preferences passed 30/30. These known cases, controlled tool-role bridge and whole-version comparison do not establish general protection or a separate Stop benefit. See [the complete fresh-run report](flow-guard-model-comparison-2026-10-25.md).