@intutic/mcp-governance-proxy 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/anomaly/detectors.d.ts +129 -0
- package/dist/anomaly/detectors.d.ts.map +1 -0
- package/dist/anomaly/detectors.js +368 -0
- package/dist/anomaly/detectors.js.map +1 -0
- package/dist/anomaly/index.d.ts +52 -0
- package/dist/anomaly/index.d.ts.map +1 -0
- package/dist/anomaly/index.js +71 -0
- package/dist/anomaly/index.js.map +1 -0
- package/dist/config.d.ts +107 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +129 -6
- package/dist/config.js.map +1 -1
- package/dist/daemon/healthMonitor.d.ts.map +1 -1
- package/dist/daemon/healthMonitor.js +45 -46
- package/dist/daemon/healthMonitor.js.map +1 -1
- package/dist/daemon/index.d.ts +15 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +78 -9
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/instrumentation.d.ts +20 -0
- package/dist/daemon/instrumentation.d.ts.map +1 -0
- package/dist/daemon/instrumentation.js +61 -0
- package/dist/daemon/instrumentation.js.map +1 -0
- package/dist/daemon/policyCache.d.ts +83 -0
- package/dist/daemon/policyCache.d.ts.map +1 -1
- package/dist/daemon/policyCache.js +280 -11
- package/dist/daemon/policyCache.js.map +1 -1
- package/dist/daemon/statusReporter.d.ts +12 -0
- package/dist/daemon/statusReporter.d.ts.map +1 -0
- package/dist/daemon/statusReporter.js +130 -0
- package/dist/daemon/statusReporter.js.map +1 -0
- package/dist/daemon/telemetryBatcher.d.ts +3 -1
- package/dist/daemon/telemetryBatcher.d.ts.map +1 -1
- package/dist/daemon/telemetryBatcher.js +56 -13
- package/dist/daemon/telemetryBatcher.js.map +1 -1
- package/dist/daemonClient.d.ts +1 -1
- package/dist/daemonClient.d.ts.map +1 -1
- package/dist/daemonClient.js +0 -2
- package/dist/daemonClient.js.map +1 -1
- package/dist/dlp.d.ts +19 -0
- package/dist/dlp.d.ts.map +1 -1
- package/dist/dlp.js +95 -18
- package/dist/dlp.js.map +1 -1
- package/dist/emitter.d.ts +40 -2
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +27 -5
- package/dist/emitter.js.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/injection.d.ts +46 -0
- package/dist/injection.d.ts.map +1 -0
- package/dist/injection.js +147 -0
- package/dist/injection.js.map +1 -0
- package/dist/interceptor.d.ts +131 -1
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +301 -5
- package/dist/interceptor.js.map +1 -1
- package/dist/policy.d.ts +110 -2
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +206 -5
- package/dist/policy.js.map +1 -1
- package/dist/proxy.d.ts +205 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +572 -65
- package/dist/proxy.js.map +1 -1
- package/dist/remoteBridge.d.ts +51 -0
- package/dist/remoteBridge.d.ts.map +1 -0
- package/dist/remoteBridge.js +182 -0
- package/dist/remoteBridge.js.map +1 -0
- package/dist/session.d.ts +158 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +206 -0
- package/dist/session.js.map +1 -0
- package/dist/sessionScope.d.ts +51 -0
- package/dist/sessionScope.d.ts.map +1 -0
- package/dist/sessionScope.js +94 -0
- package/dist/sessionScope.js.map +1 -0
- package/dist/sessionStore.d.ts +69 -0
- package/dist/sessionStore.d.ts.map +1 -0
- package/dist/sessionStore.js +143 -0
- package/dist/sessionStore.js.map +1 -0
- package/dist/tofu.d.ts +112 -0
- package/dist/tofu.d.ts.map +1 -0
- package/dist/tofu.js +180 -0
- package/dist/tofu.js.map +1 -0
- package/dist/toolPoison.d.ts +32 -0
- package/dist/toolPoison.d.ts.map +1 -0
- package/dist/toolPoison.js +81 -0
- package/dist/toolPoison.js.map +1 -0
- package/dist/valkeyErrors.d.ts +17 -0
- package/dist/valkeyErrors.d.ts.map +1 -0
- package/dist/valkeyErrors.js +24 -0
- package/dist/valkeyErrors.js.map +1 -0
- package/dist/wasm/context.d.ts +57 -0
- package/dist/wasm/context.d.ts.map +1 -0
- package/dist/wasm/context.js +59 -0
- package/dist/wasm/context.js.map +1 -0
- package/dist/wasm/hostImports.d.ts +43 -0
- package/dist/wasm/hostImports.d.ts.map +1 -0
- package/dist/wasm/hostImports.js +158 -0
- package/dist/wasm/hostImports.js.map +1 -0
- package/dist/wasm/loader.d.ts +95 -0
- package/dist/wasm/loader.d.ts.map +1 -0
- package/dist/wasm/loader.js +198 -0
- package/dist/wasm/loader.js.map +1 -0
- package/dist/wasm/referencedFiles.d.ts +115 -0
- package/dist/wasm/referencedFiles.d.ts.map +1 -0
- package/dist/wasm/referencedFiles.js +366 -0
- package/dist/wasm/referencedFiles.js.map +1 -0
- package/dist/wasm/runner.d.ts +91 -0
- package/dist/wasm/runner.d.ts.map +1 -0
- package/dist/wasm/runner.js +308 -0
- package/dist/wasm/runner.js.map +1 -0
- package/dist/wasm/watch.d.ts +5 -0
- package/dist/wasm/watch.d.ts.map +1 -0
- package/dist/wasm/watch.js +54 -0
- package/dist/wasm/watch.js.map +1 -0
- package/dist/wasm/worker.d.ts +39 -0
- package/dist/wasm/worker.d.ts.map +1 -0
- package/dist/wasm/worker.js +245 -0
- package/dist/wasm/worker.js.map +1 -0
- package/package.json +14 -4
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wasm/runner.ts — WasmRunner: owns the one dedicated `worker_threads`
|
|
3
|
+
* Worker (worker.ts), the local-rules loader (loader.ts), and the
|
|
4
|
+
* evaluate-all-rules union logic ported from
|
|
5
|
+
* `packages/proxy/src/wasm/registry.rs`'s `evaluate_inner` (most-restrictive-
|
|
6
|
+
* wins, short-circuit on a block, carry the first reask through).
|
|
7
|
+
*
|
|
8
|
+
* ## Divergences from the Rust runner (all recorded as TD entries)
|
|
9
|
+
*
|
|
10
|
+
* - **50ms per-rule deadline, not 5ms + fuel metering.** Wasmtime's `Store`
|
|
11
|
+
* can price guest instructions with fuel and enforce a hard wall-clock
|
|
12
|
+
* timeout independently; V8's WebAssembly has no fuel-equivalent
|
|
13
|
+
* instruction metering accessible from `worker_threads`, so a wall-clock
|
|
14
|
+
* race against a `postMessage` round trip (which also pays IPC overhead
|
|
15
|
+
* the in-process Wasmtime call never did) is the only backstop available
|
|
16
|
+
* here. 50ms, not 5ms, to keep that overhead from false-positiving a
|
|
17
|
+
* legitimate rule under normal load.
|
|
18
|
+
* - **Guest memory ceiling is checked, not enforced mid-call.** `runner.rs`
|
|
19
|
+
* sets a 16MB `StoreLimits` cap that stops a `memory.grow` as it happens;
|
|
20
|
+
* V8 exposes no such hook on a module-exported memory, so `worker.ts`
|
|
21
|
+
* refuses a module whose initial memory is over 16MB and discards the
|
|
22
|
+
* verdict of one that grew past it during the call (reported as a
|
|
23
|
+
* failure, fail-open like a trap). The growth itself is bounded only by
|
|
24
|
+
* V8 (TD-440's remainder).
|
|
25
|
+
* - **`read_referenced_file` is served from a pre-read table**
|
|
26
|
+
* (`referencedFiles.ts`, the port of `referenced_files.rs`), prefetched
|
|
27
|
+
* once per `evaluate()` only when a loaded rule imports the function and
|
|
28
|
+
* `INTUTIC_WASM_MANIFEST_ROOT` is set; otherwise every call refuses,
|
|
29
|
+
* exactly as the Rust proxy answers with no root configured.
|
|
30
|
+
*
|
|
31
|
+
* @module
|
|
32
|
+
*/
|
|
33
|
+
import { Worker } from 'node:worker_threads';
|
|
34
|
+
import { existsSync } from 'node:fs';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import { createStderrLogger as createLogger } from '../stderrLog.js';
|
|
37
|
+
import { WasmLoader, resolveWasmDir } from './loader.js';
|
|
38
|
+
import { buildWasmContext } from './context.js';
|
|
39
|
+
import { prefetch, resolveRoot, ReferencedFiles } from './referencedFiles.js';
|
|
40
|
+
const log = createLogger('mcp-proxy-wasm-runner');
|
|
41
|
+
/**
|
|
42
|
+
* Per-rule evaluation deadline. See this module's doc comment for why this
|
|
43
|
+
* is 50ms rather than `runner.rs`'s 5ms.
|
|
44
|
+
*/
|
|
45
|
+
const EVALUATE_TIMEOUT_MS = 50;
|
|
46
|
+
/** Generous — compilation is not guest-controlled per evaluation, but a
|
|
47
|
+
* pathological file must not hang a rescan forever. */
|
|
48
|
+
const COMPILE_TIMEOUT_MS = 3_000;
|
|
49
|
+
/** Consecutive per-rule timeouts before that rule is disabled until the next rescan. */
|
|
50
|
+
const MAX_CONSECUTIVE_TIMEOUTS = 3;
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the worker script to spawn.
|
|
53
|
+
*
|
|
54
|
+
* Always prefers the BUILT `dist/wasm/worker.js` when it exists on disk,
|
|
55
|
+
* even while this module itself is running as TypeScript source (a test
|
|
56
|
+
* runner, `tsx` dev mode) — plain compiled JS needs no loader help to
|
|
57
|
+
* resolve its own sibling `./hostImports.js` import, sidestepping
|
|
58
|
+
* `worker_threads` module-resolution entirely. `turbo.json` declares
|
|
59
|
+
* `test: { dependsOn: ['build'] }`, so `dist/` is guaranteed fresh by the
|
|
60
|
+
* time `pnpm turbo test` (CI's invocation) runs any package's suite.
|
|
61
|
+
*
|
|
62
|
+
* Only falls back to spawning the `.ts` source directly (via `tsx`'s ESM
|
|
63
|
+
* loader, registered on the worker's own `execArgv`) when no build has run
|
|
64
|
+
* yet — e.g. `vitest run` invoked standalone, bypassing turbo. That
|
|
65
|
+
* fallback path is NOT reliably portable: `execArgv: ['--import',
|
|
66
|
+
* 'tsx/esm']` (and the `NODE_OPTIONS`-delivered equivalent, also tried) both
|
|
67
|
+
* failed to propagate the loader hook to the worker's own sibling import on
|
|
68
|
+
* this repo's CI runner (Node 22, Linux) despite both working locally
|
|
69
|
+
* (Node 26, macOS) — a real, observed Node-version-dependent gap in how
|
|
70
|
+
* `worker_threads` applies ESM loader hooks, not a hypothetical one. Run
|
|
71
|
+
* `pnpm --filter @intutic/mcp-governance-proxy build` first if iterating on
|
|
72
|
+
* this file with bare `vitest` outside of `turbo test`.
|
|
73
|
+
*/
|
|
74
|
+
function workerScriptSpec() {
|
|
75
|
+
const srcUrl = new URL('./worker.ts', import.meta.url);
|
|
76
|
+
const distPath = fileURLToPath(srcUrl).replace(/\/src\/wasm\/worker\.ts$/, '/dist/wasm/worker.js');
|
|
77
|
+
if (existsSync(distPath)) {
|
|
78
|
+
return { url: new URL(`file://${distPath}`), execArgv: [] };
|
|
79
|
+
}
|
|
80
|
+
const isSource = import.meta.url.endsWith('.ts');
|
|
81
|
+
const url = new URL(isSource ? './worker.ts' : './worker.js', import.meta.url);
|
|
82
|
+
return { url, execArgv: isSource ? ['--import', 'tsx/esm'] : [] };
|
|
83
|
+
}
|
|
84
|
+
export class WasmRunner {
|
|
85
|
+
loader;
|
|
86
|
+
worker = null;
|
|
87
|
+
nextRequestId = 1;
|
|
88
|
+
pending = new Map();
|
|
89
|
+
consecutiveTimeouts = new Map();
|
|
90
|
+
disabledRuleIds = new Set();
|
|
91
|
+
constructor(dirOverride) {
|
|
92
|
+
this.loader = new WasmLoader(resolveWasmDir(dirOverride));
|
|
93
|
+
}
|
|
94
|
+
/** The resolved local rules directory, for logging. */
|
|
95
|
+
getDir() {
|
|
96
|
+
return this.loader.getDir();
|
|
97
|
+
}
|
|
98
|
+
/** Currently loaded rule ids (`local:<file name>`), priority order — observability for callers/tests. */
|
|
99
|
+
getLoadedRuleIds() {
|
|
100
|
+
return this.loader.getRules().map((r) => r.ruleId);
|
|
101
|
+
}
|
|
102
|
+
ensureWorker() {
|
|
103
|
+
if (this.worker)
|
|
104
|
+
return this.worker;
|
|
105
|
+
const { url, execArgv } = workerScriptSpec();
|
|
106
|
+
const worker = new Worker(url, execArgv.length > 0 ? { execArgv } : undefined);
|
|
107
|
+
worker.on('message', (msg) => {
|
|
108
|
+
if (typeof msg.id !== 'number')
|
|
109
|
+
return;
|
|
110
|
+
const entry = this.pending.get(msg.id);
|
|
111
|
+
if (!entry)
|
|
112
|
+
return; // already timed out and removed
|
|
113
|
+
clearTimeout(entry.timer);
|
|
114
|
+
this.pending.delete(msg.id);
|
|
115
|
+
entry.resolve(msg);
|
|
116
|
+
});
|
|
117
|
+
worker.on('error', (err) => {
|
|
118
|
+
log.error({ action: 'wasm_worker_error', err: err.message }, 'WASM worker thread error');
|
|
119
|
+
});
|
|
120
|
+
this.worker = worker;
|
|
121
|
+
return worker;
|
|
122
|
+
}
|
|
123
|
+
/** Send one message and wait for its correlated reply, or `null` on timeout. */
|
|
124
|
+
send(msg, timeoutMs) {
|
|
125
|
+
const worker = this.ensureWorker();
|
|
126
|
+
return new Promise((resolve) => {
|
|
127
|
+
const timer = setTimeout(() => {
|
|
128
|
+
this.pending.delete(msg['id']);
|
|
129
|
+
resolve(null);
|
|
130
|
+
}, timeoutMs);
|
|
131
|
+
this.pending.set(msg['id'], { resolve: (v) => resolve(v), timer });
|
|
132
|
+
worker.postMessage(msg);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
allocId() {
|
|
136
|
+
const id = this.nextRequestId;
|
|
137
|
+
this.nextRequestId += 1;
|
|
138
|
+
return id;
|
|
139
|
+
}
|
|
140
|
+
// ── CompileBridge, consumed by WasmLoader.rescan ──────────────────────
|
|
141
|
+
async compile(ruleId, bytes) {
|
|
142
|
+
const id = this.allocId();
|
|
143
|
+
const reply = await this.send({ type: 'compile', id, ruleId, bytes: toArrayBuffer(bytes) }, COMPILE_TIMEOUT_MS);
|
|
144
|
+
if (!reply)
|
|
145
|
+
return { ok: false, error: `compile timed out after ${COMPILE_TIMEOUT_MS}ms` };
|
|
146
|
+
if (!reply.ok) {
|
|
147
|
+
const fallback = reply.unsupportedImports && reply.unsupportedImports.length > 0
|
|
148
|
+
? `imports ${reply.unsupportedImports.join(', ')}, which this proxy does not provide`
|
|
149
|
+
: 'unknown compile error';
|
|
150
|
+
return { ok: false, error: reply.error ?? fallback, unsupportedImports: reply.unsupportedImports };
|
|
151
|
+
}
|
|
152
|
+
return { ok: true, readsReferencedFiles: reply.readsReferencedFiles ?? false };
|
|
153
|
+
}
|
|
154
|
+
remove(ruleId) {
|
|
155
|
+
this.ensureWorker().postMessage({ type: 'remove', ruleId });
|
|
156
|
+
this.consecutiveTimeouts.delete(ruleId);
|
|
157
|
+
this.disabledRuleIds.delete(ruleId);
|
|
158
|
+
}
|
|
159
|
+
// ── Rescan (driven by the existing policy-tick timer — see policy.ts) ──
|
|
160
|
+
/**
|
|
161
|
+
* Rescans `~/.intutic/wasm/` and (re)compiles anything changed. Also
|
|
162
|
+
* clears every rule's disabled-by-timeout status — "disable that rule
|
|
163
|
+
* until the next policy-driven rescan" is this call.
|
|
164
|
+
*/
|
|
165
|
+
async rescan() {
|
|
166
|
+
await this.loader.rescan(this);
|
|
167
|
+
this.disabledRuleIds.clear();
|
|
168
|
+
this.consecutiveTimeouts.clear();
|
|
169
|
+
}
|
|
170
|
+
/** Terminates the worker and respawns a fresh one, resending every currently-loaded rule's bytes so its module cache is rebuilt. */
|
|
171
|
+
async respawnWorker() {
|
|
172
|
+
const old = this.worker;
|
|
173
|
+
this.worker = null;
|
|
174
|
+
if (old) {
|
|
175
|
+
// Fail every request still waiting on the dead worker rather than
|
|
176
|
+
// leaving its promise unresolved forever.
|
|
177
|
+
for (const [id, entry] of this.pending) {
|
|
178
|
+
clearTimeout(entry.timer);
|
|
179
|
+
entry.resolve({ ok: false, error: 'worker terminated' });
|
|
180
|
+
this.pending.delete(id);
|
|
181
|
+
}
|
|
182
|
+
await old.terminate();
|
|
183
|
+
}
|
|
184
|
+
// Force-reload every currently-known file into the new worker — its
|
|
185
|
+
// module cache started empty. `force: true` mirrors this loader's own
|
|
186
|
+
// "worker cache was lost, nothing on disk moved" case.
|
|
187
|
+
await this.loader.rescan(this, true);
|
|
188
|
+
}
|
|
189
|
+
// ── Evaluation ───────────────────────────────────────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* Evaluates every currently loaded, non-disabled rule (priority order)
|
|
192
|
+
* against one context, mirroring `registry.rs`'s `evaluate_inner`:
|
|
193
|
+
* short-circuit on the first block, carry the first reask through in case
|
|
194
|
+
* a later, higher-priority rule still blocks, `allow` when nothing fired
|
|
195
|
+
* or every loaded rule was fail-open (timeout/trap/unmapped code).
|
|
196
|
+
*/
|
|
197
|
+
async evaluate(input) {
|
|
198
|
+
const rules = this.loader.getRules().filter((r) => !this.disabledRuleIds.has(r.ruleId));
|
|
199
|
+
if (rules.length === 0)
|
|
200
|
+
return { code: 'allow' };
|
|
201
|
+
const contextBytes = Buffer.from(JSON.stringify(buildWasmContext(input)));
|
|
202
|
+
let pendingReask = null;
|
|
203
|
+
// Read once per evaluation, before any rule runs, so the per-rule
|
|
204
|
+
// deadline covers guest execution only — and only when a rule will ask.
|
|
205
|
+
const files = rules.some((r) => r.readsReferencedFiles) ? await this.prefetchReferencedFiles(input) : undefined;
|
|
206
|
+
for (const rule of rules) {
|
|
207
|
+
const result = await this.evaluateOne(rule.ruleId, contextBytes, rule.readsReferencedFiles ? files : undefined);
|
|
208
|
+
if (result === null)
|
|
209
|
+
continue; // fail-open ALLOW for this rule (timeout, trap, or worker error)
|
|
210
|
+
switch (result.code) {
|
|
211
|
+
case 0:
|
|
212
|
+
break; // Bypass — try the next rule
|
|
213
|
+
case 1:
|
|
214
|
+
return { code: 'block', reason: result.reason ?? 'Blocked by custom WASM governance rule', ruleId: rule.ruleId };
|
|
215
|
+
case 2:
|
|
216
|
+
// Deprecated REDACT code — the guest never receives the request
|
|
217
|
+
// body (this context carries no raw payload), so redaction was
|
|
218
|
+
// never expressible. Treated as a block, loudly, mirroring
|
|
219
|
+
// runner.rs's own handling exactly.
|
|
220
|
+
log.warn({ action: 'wasm_deprecated_verdict', ruleId: rule.ruleId }, 'WASM rule returned deprecated verdict code 2 (REDACT); treating as a block. Return 1 to block, or 3 to reask.');
|
|
221
|
+
return {
|
|
222
|
+
code: 'block',
|
|
223
|
+
reason: result.reason ?? 'Blocked by custom WASM governance rule (legacy code 2)',
|
|
224
|
+
ruleId: rule.ruleId,
|
|
225
|
+
};
|
|
226
|
+
case 3:
|
|
227
|
+
if (!pendingReask) {
|
|
228
|
+
pendingReask = {
|
|
229
|
+
code: 'reask',
|
|
230
|
+
reason: result.reason ?? 'Refused by custom WASM governance rule — revise and retry',
|
|
231
|
+
ruleId: rule.ruleId,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
log.warn({ action: 'wasm_unmapped_verdict', ruleId: rule.ruleId, code: result.code }, 'WASM rule returned an unmapped verdict code; allowing. Valid codes are 0 (allow), 1 (block), 3 (reask).');
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return pendingReask ?? { code: 'allow' };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The referenced-files table for this evaluation (TD-441). With no
|
|
244
|
+
* `INTUTIC_WASM_MANIFEST_ROOT` the table is empty and every read refuses.
|
|
245
|
+
*/
|
|
246
|
+
async prefetchReferencedFiles(input) {
|
|
247
|
+
const root = resolveRoot();
|
|
248
|
+
if (!root)
|
|
249
|
+
return ReferencedFiles.empty().toTable();
|
|
250
|
+
const files = await prefetch([{ name: input.toolName, arguments: input.toolArguments ?? {} }], root);
|
|
251
|
+
if (!files.isEmpty()) {
|
|
252
|
+
log.debug({ action: 'wasm_referenced_files', readable: files.readableCount(), table: files.describe() }, 'referenced files resolved for WASM rules');
|
|
253
|
+
}
|
|
254
|
+
return files.toTable();
|
|
255
|
+
}
|
|
256
|
+
/** One rule's evaluation, raced against `EVALUATE_TIMEOUT_MS`. `null` means fail-open (timeout, worker error, or guest trap). */
|
|
257
|
+
async evaluateOne(ruleId, contextBytes, files) {
|
|
258
|
+
const id = this.allocId();
|
|
259
|
+
const reply = await this.send({ type: 'evaluate', id, ruleId, bytes: toArrayBuffer(contextBytes), ...(files ? { files } : {}) }, EVALUATE_TIMEOUT_MS);
|
|
260
|
+
if (reply === null) {
|
|
261
|
+
// Timed out. Mirrors `runner.rs`'s `Err(_) => Bypass` — this call
|
|
262
|
+
// fails open — plus the MCP-specific per-rule consecutive-timeout
|
|
263
|
+
// disable ladder the task calls for.
|
|
264
|
+
log.warn({ action: 'wasm_evaluate_timeout', ruleId, timeoutMs: EVALUATE_TIMEOUT_MS }, 'WASM rule evaluation timed out — failing open');
|
|
265
|
+
const attempts = (this.consecutiveTimeouts.get(ruleId) ?? 0) + 1;
|
|
266
|
+
this.consecutiveTimeouts.set(ruleId, attempts);
|
|
267
|
+
// terminate + lazily respawn: the NEXT evaluate() call pays the
|
|
268
|
+
// respawn cost via ensureWorker()/rescan(force); triggered here so a
|
|
269
|
+
// wedged worker does not keep timing out every rule behind it in this
|
|
270
|
+
// same evaluate() loop.
|
|
271
|
+
await this.respawnWorker();
|
|
272
|
+
if (attempts >= MAX_CONSECUTIVE_TIMEOUTS) {
|
|
273
|
+
this.disabledRuleIds.add(ruleId);
|
|
274
|
+
log.warn({ action: 'wasm_rule_disabled', ruleId, consecutiveTimeouts: attempts }, 'WASM rule disabled after consecutive timeouts — will retry on the next policy-driven rescan');
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
if (!reply.ok) {
|
|
279
|
+
// Worker-reported failure: a guest trap (including one following an
|
|
280
|
+
// `abort` call) or an internal error. Fail-open ALLOW for this call,
|
|
281
|
+
// never a crash — matches the task's explicit instruction.
|
|
282
|
+
log.warn({ action: 'wasm_evaluate_error', ruleId, err: reply.error }, 'WASM rule evaluation failed — failing open');
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
// A clean reply resets this rule's timeout streak.
|
|
286
|
+
this.consecutiveTimeouts.delete(ruleId);
|
|
287
|
+
return { code: reply.code ?? -1, reason: reply.reason };
|
|
288
|
+
}
|
|
289
|
+
/** Terminates the worker. Safe to call even if one was never spawned. */
|
|
290
|
+
async shutdown() {
|
|
291
|
+
const worker = this.worker;
|
|
292
|
+
this.worker = null;
|
|
293
|
+
for (const [id, entry] of this.pending) {
|
|
294
|
+
clearTimeout(entry.timer);
|
|
295
|
+
entry.resolve({ ok: false, error: 'runner shut down' });
|
|
296
|
+
this.pending.delete(id);
|
|
297
|
+
}
|
|
298
|
+
if (worker)
|
|
299
|
+
await worker.terminate();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function toArrayBuffer(bytes) {
|
|
303
|
+
// Structured-clone (postMessage) needs an ArrayBuffer, not a Node Buffer
|
|
304
|
+
// view that may share a larger underlying pool — slice guarantees the
|
|
305
|
+
// bytes sent are exactly, and only, this rule's/context's own.
|
|
306
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/wasm/runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/wasm/watch.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,cAAc;IAC7B,KAAK,IAAI,IAAI,CAAA;CACd;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACpC,UAAU,SAAM,GACf,cAAc,GAAG,IAAI,CA6BvB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch the local rules directory and call back, debounced, when a `.wasm`
|
|
3
|
+
* file appears, changes or goes away (TD-442).
|
|
4
|
+
*
|
|
5
|
+
* The rescan rides the 60-second policy tick (`policy.ts` `start(onTick)`)
|
|
6
|
+
* and no second timer was wanted. A filesystem watcher is not a timer: it
|
|
7
|
+
* costs nothing while nothing changes and fires within a second when
|
|
8
|
+
* something does, which is the ~5 s hot-reload the Rust proxy has
|
|
9
|
+
* (`registry.rs` `ensure_local_up_to_date`). The tick stays as the fallback
|
|
10
|
+
* for platforms where `fs.watch` misses events.
|
|
11
|
+
*
|
|
12
|
+
* A missing directory is not an error — the operator may never create it.
|
|
13
|
+
* The watcher is retried on every policy tick through `ensureWasmWatch`, so
|
|
14
|
+
* a directory created later is picked up without a restart.
|
|
15
|
+
*/
|
|
16
|
+
import * as node_fs from 'node:fs';
|
|
17
|
+
import { createStderrLogger as createLogger } from '../stderrLog.js';
|
|
18
|
+
const log = createLogger('mcp-proxy-wasm-watch');
|
|
19
|
+
export function watchWasmDir(dir, onChange, debounceMs = 500) {
|
|
20
|
+
if (!node_fs.existsSync(dir))
|
|
21
|
+
return null;
|
|
22
|
+
let timer = null;
|
|
23
|
+
let watcher;
|
|
24
|
+
try {
|
|
25
|
+
watcher = node_fs.watch(dir, { persistent: false }, (_event, filename) => {
|
|
26
|
+
if (filename && !String(filename).endsWith('.wasm'))
|
|
27
|
+
return;
|
|
28
|
+
if (timer)
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
timer = setTimeout(() => {
|
|
31
|
+
timer = null;
|
|
32
|
+
Promise.resolve(onChange()).catch((err) => {
|
|
33
|
+
log.warn({ action: 'wasm_watch_rescan_failed', err: err.message }, 'WASM rescan after a directory change failed');
|
|
34
|
+
});
|
|
35
|
+
}, debounceMs);
|
|
36
|
+
timer.unref();
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
log.warn({ action: 'wasm_watch_unavailable', dir, err: err.message }, 'fs.watch unavailable for the WASM rules directory; the policy tick still rescans');
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
watcher.on('error', (err) => {
|
|
44
|
+
log.warn({ action: 'wasm_watch_error', dir, err: err.message }, 'WASM directory watcher errored; the policy tick still rescans');
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
close() {
|
|
48
|
+
if (timer)
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
watcher.close();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/wasm/watch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,kBAAkB,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAEpE,MAAM,GAAG,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAA;AAOhD,MAAM,UAAU,YAAY,CAC1B,GAAW,EACX,QAAoC,EACpC,UAAU,GAAG,GAAG;IAEhB,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACzC,IAAI,KAAK,GAA0B,IAAI,CAAA;IACvC,IAAI,OAA0B,CAAA;IAC9B,IAAI,CAAC;QACH,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;YACvE,IAAI,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,OAAM;YAC3D,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;YAC9B,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBACtB,KAAK,GAAG,IAAI,CAAA;gBACZ,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBACxC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,0BAA0B,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,6CAA6C,CAAC,CAAA;gBAC9H,CAAC,CAAC,CAAA;YACJ,CAAC,EAAE,UAAU,CAAC,CAAA;YACd,KAAK,CAAC,KAAK,EAAE,CAAA;QACf,CAAC,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,wBAAwB,EAAE,GAAG,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,kFAAkF,CAAC,CAAA;QACpK,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC1B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,+DAA+D,CAAC,CAAA;IAClI,CAAC,CAAC,CAAA;IACF,OAAO;QACL,KAAK;YACH,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAA;YAC9B,OAAO,CAAC,KAAK,EAAE,CAAA;QACjB,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wasm/worker.ts — The ONE dedicated `worker_threads` worker hosting every
|
|
3
|
+
* compiled WASM governance rule.
|
|
4
|
+
*
|
|
5
|
+
* Runs in its own thread (spawned by `wasm/runner.ts`). Owns a
|
|
6
|
+
* `Map<ruleId, WebAssembly.Module>` — compiled ONCE per file and cached here
|
|
7
|
+
* — but instantiates a FRESH `WebAssembly.Instance` (fresh guest memory) for
|
|
8
|
+
* every `evaluate` message, mirroring the Rust runner's actual per-evaluation
|
|
9
|
+
* `Store` and preventing any cross-request state leakage between calls that
|
|
10
|
+
* happen to share a rule.
|
|
11
|
+
*
|
|
12
|
+
* Protocol (see `runner.ts` for the main-thread side):
|
|
13
|
+
* compile { type:'compile', id, ruleId, bytes } -> { type:'compile-result', id, ruleId, ok, unsupportedImports?, readsReferencedFiles?, error? }
|
|
14
|
+
* remove { type:'remove', ruleId } (no reply)
|
|
15
|
+
* evaluate { type:'evaluate', id, ruleId, bytes, files? } -> { type:'evaluate-result', id, ruleId, ok, code?, reason?, error? }
|
|
16
|
+
*
|
|
17
|
+
* `files` is the per-evaluation referenced-files table (TD-441), posted only
|
|
18
|
+
* to a rule that imports `read_referenced_file`; it is rebuilt here into the
|
|
19
|
+
* host-import state, so the main thread never shares memory with the guest.
|
|
20
|
+
*
|
|
21
|
+
* A guest `abort` call is inert (hostImports.ts logs and returns); an
|
|
22
|
+
* uncaught exception or trap during instantiation/evaluation is caught here
|
|
23
|
+
* and reported as `ok:false` — the caller (runner.ts) treats that as
|
|
24
|
+
* fail-open ALLOW, exactly like a timeout, never a crash.
|
|
25
|
+
*
|
|
26
|
+
* @module
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Guest memory ceiling, `runner.rs`'s 16MB `StoreLimits` (TD-440). V8 offers
|
|
30
|
+
* no grow hook on a memory the module itself exports and `unsupportedWasmImports`
|
|
31
|
+
* refuses an imported one, so this is enforced AFTER the call: a rule whose
|
|
32
|
+
* memory grew past the ceiling has its verdict discarded and reported as a
|
|
33
|
+
* failure (fail-open, like a trap), rather than being stopped mid-growth.
|
|
34
|
+
* What it cannot do is bound the growth itself; that remainder stays in
|
|
35
|
+
* TD-440. Checked before the call too, so a module that declares more than
|
|
36
|
+
* the ceiling as its initial size never runs.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MAX_GUEST_MEMORY_BYTES: number;
|
|
39
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/wasm/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAmB,CAAA"}
|