@aimarket/warden 0.8.2 → 0.13.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 (117) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README-es.md +45 -10
  3. package/README-fr.md +45 -10
  4. package/README-ru.md +47 -10
  5. package/README-zh.md +45 -10
  6. package/README.md +47 -10
  7. package/dist/classifier.d.ts +79 -0
  8. package/dist/classifier.d.ts.map +1 -0
  9. package/dist/classifier.js +360 -0
  10. package/dist/classifier.js.map +1 -0
  11. package/dist/claude-hook.d.ts +53 -0
  12. package/dist/claude-hook.d.ts.map +1 -0
  13. package/dist/claude-hook.js +192 -0
  14. package/dist/claude-hook.js.map +1 -0
  15. package/dist/encoded-text.d.ts +7 -0
  16. package/dist/encoded-text.d.ts.map +1 -0
  17. package/dist/encoded-text.js +63 -0
  18. package/dist/encoded-text.js.map +1 -0
  19. package/dist/fold.d.ts +1 -2
  20. package/dist/fold.d.ts.map +1 -1
  21. package/dist/fold.js +3 -2
  22. package/dist/fold.js.map +1 -1
  23. package/dist/histor.d.ts +104 -0
  24. package/dist/histor.d.ts.map +1 -0
  25. package/dist/histor.js +86 -0
  26. package/dist/histor.js.map +1 -0
  27. package/dist/index.d.ts +6 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +8 -4
  30. package/dist/index.js.map +1 -1
  31. package/dist/mcp-client.d.ts +58 -0
  32. package/dist/mcp-client.d.ts.map +1 -0
  33. package/dist/mcp-client.js +451 -0
  34. package/dist/mcp-client.js.map +1 -0
  35. package/dist/mcp-rpc.js +1 -1
  36. package/dist/mcp-server.js +8 -0
  37. package/dist/mcp-server.js.map +1 -1
  38. package/dist/mcp-tools.d.ts.map +1 -1
  39. package/dist/mcp-tools.js +29 -4
  40. package/dist/mcp-tools.js.map +1 -1
  41. package/dist/mtl.d.ts +28 -0
  42. package/dist/mtl.d.ts.map +1 -0
  43. package/dist/mtl.js +77 -0
  44. package/dist/mtl.js.map +1 -0
  45. package/dist/pin-store.d.ts.map +1 -1
  46. package/dist/pin-store.js +1 -0
  47. package/dist/pin-store.js.map +1 -1
  48. package/dist/pinning.d.ts +1 -1
  49. package/dist/pinning.d.ts.map +1 -1
  50. package/dist/pinning.js +12 -4
  51. package/dist/pinning.js.map +1 -1
  52. package/dist/result-screen.d.ts +19 -0
  53. package/dist/result-screen.d.ts.map +1 -0
  54. package/dist/result-screen.js +86 -0
  55. package/dist/result-screen.js.map +1 -0
  56. package/dist/scan-cli.d.ts +3 -0
  57. package/dist/scan-cli.d.ts.map +1 -0
  58. package/dist/scan-cli.js +36 -0
  59. package/dist/scan-cli.js.map +1 -0
  60. package/dist/scan-config.d.ts +72 -0
  61. package/dist/scan-config.d.ts.map +1 -0
  62. package/dist/scan-config.js +239 -0
  63. package/dist/scan-config.js.map +1 -0
  64. package/dist/scan-lock.d.ts +46 -0
  65. package/dist/scan-lock.d.ts.map +1 -0
  66. package/dist/scan-lock.js +113 -0
  67. package/dist/scan-lock.js.map +1 -0
  68. package/dist/scan-report.d.ts +21 -0
  69. package/dist/scan-report.d.ts.map +1 -0
  70. package/dist/scan-report.js +288 -0
  71. package/dist/scan-report.js.map +1 -0
  72. package/dist/scan.d.ts +177 -0
  73. package/dist/scan.d.ts.map +1 -0
  74. package/dist/scan.js +754 -0
  75. package/dist/scan.js.map +1 -0
  76. package/dist/static-scan.d.ts +4 -3
  77. package/dist/static-scan.d.ts.map +1 -1
  78. package/dist/static-scan.js +227 -11
  79. package/dist/static-scan.js.map +1 -1
  80. package/dist/tool-list.d.ts +34 -0
  81. package/dist/tool-list.d.ts.map +1 -0
  82. package/dist/tool-list.js +57 -0
  83. package/dist/tool-list.js.map +1 -0
  84. package/dist/toxic-flow.d.ts +33 -0
  85. package/dist/toxic-flow.d.ts.map +1 -0
  86. package/dist/toxic-flow.js +117 -0
  87. package/dist/toxic-flow.js.map +1 -0
  88. package/dist/types.d.ts +12 -0
  89. package/dist/types.d.ts.map +1 -1
  90. package/dist/wrap-cli.d.ts +3 -0
  91. package/dist/wrap-cli.d.ts.map +1 -1
  92. package/dist/wrap-cli.js +17 -6
  93. package/dist/wrap-cli.js.map +1 -1
  94. package/dist/wrap-state.js +2 -2
  95. package/dist/wrap-state.js.map +1 -1
  96. package/dist/wrap.d.ts.map +1 -1
  97. package/dist/wrap.js +22 -35
  98. package/dist/wrap.js.map +1 -1
  99. package/docs/gates.es.md +40 -7
  100. package/docs/gates.fr.md +39 -6
  101. package/docs/gates.md +38 -7
  102. package/docs/gates.ru.md +40 -7
  103. package/docs/gates.zh.md +10 -5
  104. package/docs/scan.es.md +199 -0
  105. package/docs/scan.fr.md +199 -0
  106. package/docs/scan.md +199 -0
  107. package/docs/scan.ru.md +199 -0
  108. package/docs/scan.zh.md +199 -0
  109. package/docs/scanner-comparison.es.md +209 -0
  110. package/docs/scanner-comparison.fr.md +213 -0
  111. package/docs/scanner-comparison.md +200 -0
  112. package/docs/scanner-comparison.ru.md +205 -0
  113. package/docs/scanner-comparison.zh.md +131 -0
  114. package/docs/security-hardening-language-boundary.md +306 -0
  115. package/package.json +10 -5
  116. package/scripts/check-quality-gate.mjs +64 -0
  117. package/scripts/quality-public-key.pem +3 -0
@@ -0,0 +1,306 @@
1
+ # Language-independent admission, WARDEN 0.12.0
2
+
3
+ No finite keyword list understands every language. Normalizing an encoding does
4
+ not translate its meaning, and an LLM's language coverage is also finite.
5
+ WARDEN therefore separates **recognition** from **permission to expose a tool
6
+ definition**.
7
+
8
+ ## Revision 0.12.0
9
+
10
+ A confirmed semantic authority-boundary violation now always has `high` severity
11
+ at the host. The provider's original level is retained only as `modelSeverity`.
12
+ This closes the threshold mismatch behind the 16 known allowed attacks in 0.11.0;
13
+ uncertainty remains a separate failure of inspection.
14
+
15
+ With `scan --classifier-blocks`, a complete clean review can resolve exactly two
16
+ ambiguous static cases: a readable base64 blob, and quotation inside a structured
17
+ JSON field. The existing rules still inspect raw and decoded content. Unquoted
18
+ instructions and the other threat, origin, drift and approval gates remain in
19
+ force. This is model-dependent disambiguation, not a language-independent proof
20
+ of safety. The model is explicitly prompted to distinguish protective negation
21
+ and detection examples from directives to perform the quoted attack.
22
+
23
+ A review is bound to a pre-request snapshot of **all** advertised fields. Its
24
+ clearance lives in a private WeakMap, not in provider JSON or a serializable
25
+ policy flag. Mutating the input, copying an inspection object, inventing a clean
26
+ result, an advisory-only call, truncation, uncertainty or a failed request cannot
27
+ satisfy the guard. The same review is used by scan and its lock-update recheck.
28
+ No broad static allowlist is introduced. The offline gate chain and wrap do not
29
+ obtain a clean semantic review and retain conservative detection.
30
+
31
+ An early candidate that exempted every decodable base64 string was rejected:
32
+ it lost 900 existing offline detections. A second candidate relaxed structured
33
+ quotes offline; a mixed-language imperative exposed an unsafe exception. Both
34
+ partial experiments were stopped, and the final comparison keeps offline
35
+ blocking unchanged on the frozen attack and control sets. The final semantic run
36
+ uses a separate build identity and output directory; partial runs are not counted
37
+ as completed measurements.
38
+
39
+ Historical 0.11.0 measurements below are retained for comparison. The final
40
+ 0.12.0 measurements are recorded separately in the companion report.
41
+
42
+ ## Enforce the trust boundary
43
+
44
+ ```sh
45
+ node dist/mcp-server.js wrap --require-approval --id notes -- node /path/to/server.mjs
46
+ ```
47
+
48
+ First contact records the observed definitions for review but withholds them from
49
+ the MCP client and refuses their calls. In an operator terminal:
50
+
51
+ ```sh
52
+ node dist/mcp-server.js pins status --id notes
53
+ node dist/mcp-server.js pins approve --id notes
54
+ ```
55
+
56
+ Use the same `--state-dir` on both commands if overriding the default. Approval
57
+ requires the explicit TTY confirmation and rechecks non-pinning security gates.
58
+ The observed snapshot must still match what the operator reviewed. Relist the
59
+ tools after approval.
60
+
61
+ The policy equivalent is:
62
+
63
+ ```json
64
+ {
65
+ "requireApproval": true,
66
+ "pinToolDefs": true,
67
+ "blockAtSeverity": "high",
68
+ "allowUnknownServers": true,
69
+ "sensitiveToolPatterns": []
70
+ }
71
+ ```
72
+
73
+ The pin records `approvalMode: "operator"`, v2 hashing of every advertised tool
74
+ field, and the launch identity. Automatic first-contact pins, old pins, and
75
+ incomplete pins do not qualify. Any changed field or launch identity is refused,
76
+ even if `pinToolDefs` is separately disabled or a high severity threshold would
77
+ otherwise ignore a finding. No language identification is involved.
78
+
79
+ `scan --require-approval --lock warden.lock.json` applies the same admission
80
+ requirement. `--update-lock` is the explicit operator action that records a new
81
+ snapshot for repository review. A model verdict alone never changes trust.
82
+ `--no-launch` cannot verify the current definitions and refuses strict admission.
83
+
84
+ Compatibility: strict approval is **opt-in**. Ordinary wrap still automatically
85
+ pins a clean first contact, now marked `automatic`. `--audit-only` observes and
86
+ does not enforce. A host calling the library's `approve()` is a trusted authority;
87
+ it must not expose that operation to an untrusted model. MCP pin mutation tools
88
+ remain disabled unless the operator explicitly enables them.
89
+
90
+ ## Semantic inspection assists review
91
+
92
+ ```sh
93
+ WARDEN_CLASSIFIER_API_KEY=… node dist/mcp-server.js scan --project \
94
+ --classifier-url https://your-approved-provider.example/v1 \
95
+ --classifier-model YOUR_MODEL --classifier-blocks
96
+ ```
97
+
98
+ The classifier reads names, titles, descriptions, input/output schemas,
99
+ annotations and all extension metadata, including normalized readings of common
100
+ encodings. Its prompt asks about overriding user authority, credentials,
101
+ exfiltration, concealment and control of other tools. It has no language keyword
102
+ table. Random fences delimit untrusted input; they are not a security guarantee.
103
+
104
+ The field-reference protocol requires exactly one `clean`, `attack` or `uncertain`
105
+ decision per trusted tool index. Attack evidence refers to host-assigned field
106
+ IDs. WARDEN validates those IDs and extracts the source previews itself: admission
107
+ no longer depends on the model copying Unicode quotations exactly. Missing,
108
+ duplicate, contradictory and invalid decisions never become clean results.
109
+ Valid decisions survive a malformed decision about a different tool. Each
110
+ incomplete tool gets at most one isolated retry, also for length-limited responses.
111
+ An entire malformed batch can therefore use at most nine requests (one plus eight
112
+ single-tool retries). Authentication errors, rate limits, network/provider errors
113
+ and content filtering do not trigger this fan-out. Inputs exceeding the inspection
114
+ budget are rejected before transmission.
115
+
116
+ Enforcing mode rejects remaining incomplete coverage, uncertainty, timeouts and
117
+ provider errors with `CLASSIFIER_INCOMPLETE`. These are inspection failures, not
118
+ attack detections; they also prevent `--update-lock` from recording an approval.
119
+ Advisory mode reports failures without enforcing them.
120
+
121
+ `--classifier-reasoning-effort none|low|medium|high` is optional and requires provider
122
+ support; omitting it preserves the provider default. For the DeepSeek experiment
123
+ we explicitly use `none`: its default thinking mode otherwise shares the 4,000-token
124
+ output budget with the final JSON ([provider documentation](https://api-docs.deepseek.com/guides/thinking_mode/)).
125
+ Responses and optional diagnostics are size-bounded and never contain API headers
126
+ or keys. Diagnostic source text is recorded only through an explicit callback.
127
+
128
+ The classifier is part of `scan`, not the synchronous offline `vet()` gate chain
129
+ or the wrap call path. It does not silently send tool text from wrap to a vendor.
130
+ Choose and configure the endpoint explicitly. A clean model verdict is still not
131
+ an operator approval.
132
+
133
+ ## Reproducible measurements
134
+
135
+ The companion MOMUS comparison freezes 8,244 synthetic attack definitions and
136
+ 362 benign controls in 11 languages before evaluating both builds. The original
137
+ 4,194 attack inputs are preserved byte-for-byte. Extra languages: Arabic, Hindi,
138
+ Korean, Swahili and Turkish. Extra encodings: percent, hexadecimal entities and
139
+ nested base64/percent encoding. These fixtures are author-labelled synthetic
140
+ data, not independent human-reviewed natural traffic or successful agent attacks.
141
+
142
+ Initial static comparison, old ruleset 10 versus ruleset 11:
143
+
144
+ | Set | Before | After |
145
+ |---|---:|---:|
146
+ | Original attacks detected / 4,194 | 1,620 | 1,692 |
147
+ | Expanded attacks detected / 8,244 | 2,664 | 2,808 |
148
+ | Benign controls blocked / 362 | 28 | 31 |
149
+ | Real public snapshots blocked / 986 | 3 | 3 |
150
+
151
+ No previously detected synthetic attack became allowed. Exact replay produced
152
+ identical decisions. The three added false blocks are encoded quotations of an
153
+ attack in extension metadata: decoding exposes the quote to a pre-existing
154
+ lexical rule. Existing false blocks include the base64 entropy heuristic on
155
+ legitimate encoded examples. These controls deliberately test such weaknesses;
156
+ they are not removed to improve the score. The public snapshots are unlabelled:
157
+ their block count is not a measured false-positive rate.
158
+
159
+ Strict admission refuses all 8,244 unapproved attack definitions and every changed
160
+ definition after approval, but also refuses all 362 unapproved benign controls.
161
+ Once explicitly approved, 331/362 benign controls pass; 31 still trip static
162
+ rules. These are admission measurements, **not 100% semantic recall**.
163
+
164
+ MOMUS reproduces the comparison and preserves per-case decisions:
165
+
166
+ ```sh
167
+ python -m momus.engine.coverage_compare --before /path/to/warden-v10 \
168
+ --after /path/to/warden-v11 --out data/warden-v11-comparison
169
+ ```
170
+
171
+ The optional live runner uses the actual compiled WARDEN classifier, with eight
172
+ independent fixtures per request and four concurrent requests. It never sends
173
+ fixture labels to the model. Keys come from environment variables; reports record
174
+ model, endpoint, corpus hash, build hash, prompt hash, findings and incomplete
175
+ inspections. A pilot is possible with a final numeric argument. Partial runs can
176
+ resume only with the identical corpus/model/build.
177
+
178
+ ```sh
179
+ node momus/engine/coverage_semantic.mjs /path/to/warden \
180
+ data/warden-v11-comparison/cases.jsonl.gz data/semantic-pilot 80
181
+ ```
182
+
183
+ ## Protocol pilot
184
+
185
+ After replacing transcribed quotes with field IDs, fixing the uncertainty schema
186
+ and explicitly configuring the output budget, the same frozen, balanced 80-case
187
+ pilot was run through both providers:
188
+
189
+ | Provider / model | Attack high findings | Benign high findings | Incomplete |
190
+ |---|---:|---:|---:|
191
+ | DeepSeek / `deepseek-flash`, effort `none` | 40/40 | 0/40 | 0/80 |
192
+ | OpenRouter / `minimax/minimax-m3`, provider default | 40/40 | 0/40 | 0/80 |
193
+
194
+ Each used ten requests, all with `finish_reason=stop`; no retry was needed in
195
+ these pilots. Earlier iterations had incomplete inspections (24/80 and 40/80 in
196
+ the initial quote protocol, 16/80 and 3/80 in the first field protocol). Those
197
+ failed inspections were never counted as detected attacks. These are successive
198
+ engineering pilots, not a controlled causal comparison: the model is
199
+ nondeterministic, the protocol was tuned using pilot results, and the final pilot
200
+ is not evidence of universal reliability.
201
+
202
+ ## Historical 0.11.0 full semantic run — 2026-10-10
203
+
204
+ The completed run used `deepseek-flash` with explicit reasoning effort `none`,
205
+ the frozen 8,606 fixtures and the same build digest as the static comparison.
206
+ At the unchanged blocking threshold `high`:
207
+
208
+ | Set | Ruleset 10 alone | Ruleset 11 alone | Ruleset 11 + DeepSeek |
209
+ |---|---:|---:|---:|
210
+ | Original attacks blocked / 4,194 | 1,620 | 1,692 | 4,184 |
211
+ | Expanded attacks blocked / 8,244 | 2,664 | 2,808 | 8,228 |
212
+ | Benign controls blocked / 362 | 28 | 31 | 32 |
213
+
214
+ Combined blocking is **99.76%** on the original set and **99.81%** on the expanded
215
+ set. The semantic layer alone produced 8,223 high findings on attacks, one high
216
+ finding on a benign control, and **zero incomplete inspections out of 8,606**.
217
+ All 1,076 requests finished with `stop`; no isolated retry was needed in this run.
218
+ Fault-injection unit tests separately verify bounded retries and closed admission
219
+ when retries fail.
220
+
221
+ All attacks received some semantic finding; 21 were below `high`. Static rules
222
+ blocked five of those, leaving **16 allowed attacks at the configured threshold**.
223
+ That is a remaining policy/model-severity limitation, not a protocol failure.
224
+ The added semantic false block is a German HTML-encoded tool title that says never
225
+ to send credentials or conversation history externally. Together with 31 static
226
+ false blocks, the combined false-positive rate on these stress controls is
227
+ **32/362 (8.84%)**. In that 0.11.0 run, static decisions were never disambiguated by a clean model verdict.
228
+
229
+ The recorded semantic result requires explicitly enabling the classifier; ordinary
230
+ offline `vet()` and wrap retain the static numbers. Neither these results nor
231
+ operator approval demonstrate safety in every language or on future model versions.
232
+ The inputs are related synthetic templates, including the pilot used for protocol
233
+ tuning. This is a reproducible stress evaluation, not a held-out generalization claim.
234
+
235
+ The companion [machine-readable report](../../momus/docs/coverage-v11.json) contains
236
+ build/corpus/prompt identities, per-language counts, missed IDs, false-block IDs,
237
+ provider pilots and validation results. Exact inputs and individual decisions are
238
+ in `momus/data/warden-v11-comparison/` and
239
+ `momus/data/warden-semantic-full-deepseek-v11/` in the working checkout.
240
+
241
+ ## Final 0.12.0 measurement — 2026-10-10
242
+
243
+ The same frozen corpus (SHA-256
244
+ `f3333cd9243fc7b9e62cd760609124d1fefc97d103b706cbd67750be291b3add`)
245
+ was rerun with `deepseek-flash`, effort `none`, `scan --classifier-blocks`
246
+ semantics and threshold `high`. The live runner uses the production WARDEN
247
+ gates with the bound inspection; this is not a benchmark-only allowlist.
248
+
249
+ | Outcome | 0.11.0 combined | 0.12.0 combined |
250
+ |---|---:|---:|
251
+ | Original attacks blocked / 4,194 | 4,184 | 4,194 |
252
+ | Expanded attacks blocked / 8,244 | 8,228 | 8,244 |
253
+ | Allowed attacks | 16 | 0 |
254
+ | Benign controls blocked / 362 | 32 (8.84%) | 1 (0.28%) |
255
+ | Incomplete inspections / 8,606 | 0 | 0 |
256
+
257
+ All 16 previously missed attacks are blocked, and all 32 previously false-blocked
258
+ controls are allowed. **One new false block remains**:
259
+ `control.es.0.description.base64`, an encoded Spanish instruction to summarize
260
+ user-provided text. The model incorrectly treated this ordinary task description
261
+ as an authority violation. It remains in the denominator and saved report.
262
+ The number of false blocks fell by 96.875%; this is not zero-error protection.
263
+
264
+ All 1,076 provider answers completed with `stop`, no singleton retries. The model
265
+ received no fixture labels. Provider usage for the final full run was 3,694,186
266
+ prompt tokens and 466,642 completion tokens; stopped development runs are separate.
267
+ The balanced final pilot passed 40 attacks and 40 controls without incompletes.
268
+
269
+ Offline outcomes are deliberately unchanged: 2,808/8,244 attacks blocked,
270
+ 31/362 controls blocked, zero attack regressions, zero replay differences.
271
+ The 986 unlabelled public snapshots keep the same three blocks. Their block count
272
+ is not a false-positive estimate. Ordinary wrap is not part of the live semantic
273
+ measurement.
274
+
275
+ [Full report](../../momus/docs/coverage-v12.json),
276
+ [implementation audit of the nine-part plan](../../momus/docs/mcp-quality-review.md),
277
+ and [safe local reproductions](../../momus/docs/mcp-quality-reproductions.json).
278
+ Final decisions are in `momus/data/warden-semantic-full-v12-bound/`; frozen
279
+ comparison inputs and offline decisions are in `momus/data/warden-v12-comparison/`.
280
+ The report binds corpus, prompt and build hashes and records previous-error
281
+ transitions and the abandoned experiments. This corpus influenced the changes;
282
+ its related synthetic translations are not an independent held-out distribution
283
+ or evidence of understanding every possible language.
284
+
285
+ ## Scope of the guarantee
286
+
287
+ Strict approval prevents an unreviewed or changed **tool definition** from reaching
288
+ the client. It does not prove that approved prose is safe, that the implementation
289
+ matches its description, or that a call is authorized by the user's actual intent.
290
+ It does not pin executable bytes or inherited environment variables.
291
+
292
+ Strict wrap strips `initialize.instructions`, which is outside the approved tool
293
+ snapshot. Other MCP surfaces—resources, prompts, server metadata, sampling and
294
+ elicitation—are outside this definition-approval guarantee. Tool results are
295
+ separate untrusted data; the existing result screen is lexical, and withholding a
296
+ result cannot undo a tool call. A complete host also needs scoped filesystem and
297
+ network access, per-call authorization outside the model, and process isolation.
298
+
299
+
300
+ ## Revision 0.13.0 — ordinary imperatives and honest coverage
301
+
302
+ The generic classifier criterion now requires a concrete authority violation. An imperative describing an operation on user-provided input remains ordinary documentation after decoding. There is no Spanish allowlist. On the same frozen 8,606 fixtures, DeepSeek (`deepseek-flash`, effort `none`) blocked all 8,244 attacks and allowed all 362 controls, including `control.es.0.description.base64`; no inspection was incomplete. [Full report](../../momus/docs/coverage-v13.json) includes prompt/build/corpus hashes and token counts. Offline recognition remains 2,808/8,244 with 31/362 false blocks; the semantic result must not be attributed to offline mode.
303
+
304
+ `wrap` reports oversized results as incomplete rather than scanning head/tail and clearing the unseen middle. This result layer remains static; no claim of universal multilingual result recognition is made. `block` withholds the result after the tool ran. `warn` annotates it.
305
+
306
+ Composition analysis accepts `policy.capabilityBindings`: one object per server with `serverId`, `identityHash`, `toolsHash` and a `tools` map from exact tool names to arrays containing `private`, `untrusted`, `outbound`. The hashes must match the reviewed launch identity and every definition field; identity/definition changes invalidate the binding. Empty arrays are explicit operator declarations. Names and server-provided capability annotations cannot grant trust. Unknown tools produce a possible-flow advisory. Known combinations produce a confirmed-capability advisory; neither proves a particular data leak.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aimarket/warden",
3
- "version": "0.8.2",
3
+ "version": "0.13.0",
4
4
  "description": "WARDEN — MCP security firewall. Stdio wrap proxy, inspection tools and library. Vets definitions before model exposure. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,10 @@
33
33
  },
34
34
  "files": [
35
35
  "dist",
36
+ "scripts/check-quality-gate.mjs",
37
+ "scripts/quality-public-key.pem",
36
38
  "README.md",
39
+ "docs/scan*.md",
37
40
  "README-ru.md",
38
41
  "README-es.md",
39
42
  "README-fr.md",
@@ -50,11 +53,13 @@
50
53
  "scripts": {
51
54
  "mcp": "node dist/mcp-server.js",
52
55
  "build": "tsc -p tsconfig.json",
56
+ "prepare": "npm run build",
53
57
  "typecheck": "tsc -p tsconfig.json --noEmit",
54
58
  "test": "vitest run",
55
59
  "test:coverage": "vitest run --coverage",
56
60
  "test:watch": "vitest",
57
- "prepublishOnly": "npm run build && npm test && npm run check:ruleset",
61
+ "prepublishOnly": "npm run build && npm test && npm run check:ruleset && npm run check:quality",
62
+ "check:quality": "node scripts/check-quality-gate.mjs",
58
63
  "check:ruleset": "node scripts/check-published-ruleset.mjs",
59
64
  "bench:wrap": "npm run build && node scripts/benchmark-wrap.mjs"
60
65
  },
@@ -77,9 +82,9 @@
77
82
  "access": "public"
78
83
  },
79
84
  "devDependencies": {
80
- "@types/node": "^22.10.2",
81
- "@vitest/coverage-v8": "^2.1.9",
85
+ "@types/node": "^26.6.4",
86
+ "@vitest/coverage-v8": "^4.1.11",
82
87
  "typescript": "^5.7.2",
83
- "vitest": "^2.1.8"
88
+ "vitest": "^4.1.11"
84
89
  }
85
90
  }
@@ -0,0 +1,64 @@
1
+ // Release evidence is signed by the isolated production evaluator, never by a PR runner.
2
+ import { createHash, verify } from 'node:crypto';
3
+ import { readFileSync, readdirSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { join, resolve } from 'node:path';
6
+
7
+ export function buildDigest(build) {
8
+ const hash = createHash('sha256');
9
+ const files = readdirSync(join(build, 'dist')).filter(n => n.endsWith('.js')).sort();
10
+ if (!files.length) throw Error('WARDEN build missing');
11
+ if (files.some(name => name.startsWith('._'))) throw Error('Build contains AppleDouble transfer metadata');
12
+ for (const name of files) hash.update(name + '\0').update(readFileSync(join(build, 'dist', name))).update('\0');
13
+ return hash.digest('hex');
14
+ }
15
+
16
+ export function checkEvidence(envelope, publicKey, expectedBuild, now = Date.now() / 1000) {
17
+ const payload = Buffer.from(envelope.payload, 'base64');
18
+ if (!verify(null, payload, publicKey, Buffer.from(envelope.signature, 'base64'))) throw Error('Invalid quality signature');
19
+ const r = JSON.parse(payload);
20
+ if (r.type !== 'momus.release-quality/v1' || r.policy !== 'zero-regressions-v1' || r.status !== 'passed')
21
+ throw Error('Quality cycle has not passed');
22
+ if (r.buildDigest !== expectedBuild) throw Error('This exact WARDEN build has not passed');
23
+ if (!Number.isFinite(r.evaluatedAt) || !Number.isFinite(r.expiresAt) || r.evaluatedAt > now + 60
24
+ || now - r.evaluatedAt > 48 * 3600 || r.expiresAt < now) throw Error('Quality evidence expired');
25
+ const full = r.fullEvidence;
26
+ if (!full || full.status !== 'passed' || full.buildDigest !== expectedBuild
27
+ || full.holdoutSha256 !== r.holdoutSha256 || !Number.isFinite(full.evaluatedAt)
28
+ || full.evaluatedAt > now + 60 || now - full.evaluatedAt > 35 * 86400) throw Error('Fresh full evaluation required');
29
+ for (const name of ['development', 'holdout', 'candidates']) {
30
+ const suite = r.suites?.[name];
31
+ if (!suite?.passed || !suite.counts || ['missed', 'falsePositives', 'incomplete'].some(k => suite.counts[k] !== 0))
32
+ throw Error(`Quality failure in ${name}`);
33
+ }
34
+ if (r.suites.holdout.counts.attacks < 20 || r.suites.holdout.counts.benign < 20) throw Error('Holdout coverage missing');
35
+ if (Object.values(r.suites).some(s => !s.passed)) throw Error('Regression suite failed');
36
+ const b = r.behaviour;
37
+ if (!b?.passed || b.cases !== 72 || b.executed !== 72 || !Number.isFinite(b.evaluatedAt)
38
+ || now - b.evaluatedAt > 35 * 86400 || b.evaluatedAt > now + 60
39
+ || ['missed', 'falsePositives', 'incomplete'].some(k => b[k] !== 0)) throw Error('Behaviour campaign missing or failed');
40
+ if (r.discovery?.status !== 'complete' || r.discovery.generation?.status !== 'ok') throw Error('Adversarial discovery unavailable');
41
+ const smoke = r.behaviourSmoke;
42
+ if (!smoke?.passed || smoke.executed < 3 || !Number.isFinite(smoke.evaluatedAt)
43
+ || smoke.evaluatedAt > now + 60 || now - smoke.evaluatedAt > 48 * 3600
44
+ || smoke.observerSha256 !== b.observerSha256 || smoke.fixturesSha256 !== b.fixturesSha256
45
+ || ['missed', 'falsePositives', 'incomplete'].some(k => smoke[k] !== 0)) throw Error('Daily live sandbox check missing or failed');
46
+ return r;
47
+ }
48
+
49
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
50
+ try {
51
+ const build = resolve(process.argv[2] || fileURLToPath(new URL('..', import.meta.url)));
52
+ const response = await fetch('https://histor.modelmarket.dev/security-quality/latest.json',
53
+ { redirect: 'error', signal: AbortSignal.timeout(15000), cache: 'no-store' });
54
+ if (!response.ok) throw Error('Quality service unavailable');
55
+ const bytes = await response.arrayBuffer();
56
+ if (bytes.byteLength > 128 * 1024) throw Error('Oversized quality receipt');
57
+ const envelope = JSON.parse(Buffer.from(bytes).toString());
58
+ const report = checkEvidence(envelope, readFileSync(new URL('./quality-public-key.pem', import.meta.url)), buildDigest(build));
59
+ console.log(`MOMUS release gate passed: ${report.run}, ${report.buildDigest}`);
60
+ } catch (error) {
61
+ console.error(`Release blocked: ${error.message}`);
62
+ process.exitCode = 1;
63
+ }
64
+ }
@@ -0,0 +1,3 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MCowBQYDK2VwAyEAmc0nzrgzWIxs5Pk8PFmc70ci/PhEBPoGp1+RQJAC7vM=
3
+ -----END PUBLIC KEY-----