@intutic/mcp-governance-proxy 1.9.0 → 1.10.1

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 (123) hide show
  1. package/README.md +28 -0
  2. package/dist/anomaly/detectors.d.ts +129 -0
  3. package/dist/anomaly/detectors.d.ts.map +1 -0
  4. package/dist/anomaly/detectors.js +368 -0
  5. package/dist/anomaly/detectors.js.map +1 -0
  6. package/dist/anomaly/index.d.ts +52 -0
  7. package/dist/anomaly/index.d.ts.map +1 -0
  8. package/dist/anomaly/index.js +71 -0
  9. package/dist/anomaly/index.js.map +1 -0
  10. package/dist/config.d.ts +107 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +129 -6
  13. package/dist/config.js.map +1 -1
  14. package/dist/daemon/healthMonitor.d.ts.map +1 -1
  15. package/dist/daemon/healthMonitor.js +45 -46
  16. package/dist/daemon/healthMonitor.js.map +1 -1
  17. package/dist/daemon/index.d.ts +15 -1
  18. package/dist/daemon/index.d.ts.map +1 -1
  19. package/dist/daemon/index.js +78 -9
  20. package/dist/daemon/index.js.map +1 -1
  21. package/dist/daemon/instrumentation.d.ts +20 -0
  22. package/dist/daemon/instrumentation.d.ts.map +1 -0
  23. package/dist/daemon/instrumentation.js +61 -0
  24. package/dist/daemon/instrumentation.js.map +1 -0
  25. package/dist/daemon/policyCache.d.ts +83 -0
  26. package/dist/daemon/policyCache.d.ts.map +1 -1
  27. package/dist/daemon/policyCache.js +280 -11
  28. package/dist/daemon/policyCache.js.map +1 -1
  29. package/dist/daemon/statusReporter.d.ts +12 -0
  30. package/dist/daemon/statusReporter.d.ts.map +1 -0
  31. package/dist/daemon/statusReporter.js +130 -0
  32. package/dist/daemon/statusReporter.js.map +1 -0
  33. package/dist/daemon/telemetryBatcher.d.ts +3 -1
  34. package/dist/daemon/telemetryBatcher.d.ts.map +1 -1
  35. package/dist/daemon/telemetryBatcher.js +56 -13
  36. package/dist/daemon/telemetryBatcher.js.map +1 -1
  37. package/dist/daemonClient.d.ts +1 -1
  38. package/dist/daemonClient.d.ts.map +1 -1
  39. package/dist/daemonClient.js +0 -2
  40. package/dist/daemonClient.js.map +1 -1
  41. package/dist/dlp.d.ts +19 -0
  42. package/dist/dlp.d.ts.map +1 -1
  43. package/dist/dlp.js +95 -18
  44. package/dist/dlp.js.map +1 -1
  45. package/dist/emitter.d.ts +40 -2
  46. package/dist/emitter.d.ts.map +1 -1
  47. package/dist/emitter.js +27 -5
  48. package/dist/emitter.js.map +1 -1
  49. package/dist/index.js +13 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/injection.d.ts +46 -0
  52. package/dist/injection.d.ts.map +1 -0
  53. package/dist/injection.js +147 -0
  54. package/dist/injection.js.map +1 -0
  55. package/dist/interceptor.d.ts +131 -1
  56. package/dist/interceptor.d.ts.map +1 -1
  57. package/dist/interceptor.js +301 -5
  58. package/dist/interceptor.js.map +1 -1
  59. package/dist/policy.d.ts +110 -2
  60. package/dist/policy.d.ts.map +1 -1
  61. package/dist/policy.js +206 -5
  62. package/dist/policy.js.map +1 -1
  63. package/dist/proxy.d.ts +205 -0
  64. package/dist/proxy.d.ts.map +1 -1
  65. package/dist/proxy.js +572 -65
  66. package/dist/proxy.js.map +1 -1
  67. package/dist/remoteBridge.d.ts +51 -0
  68. package/dist/remoteBridge.d.ts.map +1 -0
  69. package/dist/remoteBridge.js +182 -0
  70. package/dist/remoteBridge.js.map +1 -0
  71. package/dist/session.d.ts +158 -0
  72. package/dist/session.d.ts.map +1 -0
  73. package/dist/session.js +206 -0
  74. package/dist/session.js.map +1 -0
  75. package/dist/sessionScope.d.ts +51 -0
  76. package/dist/sessionScope.d.ts.map +1 -0
  77. package/dist/sessionScope.js +94 -0
  78. package/dist/sessionScope.js.map +1 -0
  79. package/dist/sessionStore.d.ts +69 -0
  80. package/dist/sessionStore.d.ts.map +1 -0
  81. package/dist/sessionStore.js +143 -0
  82. package/dist/sessionStore.js.map +1 -0
  83. package/dist/tofu.d.ts +112 -0
  84. package/dist/tofu.d.ts.map +1 -0
  85. package/dist/tofu.js +180 -0
  86. package/dist/tofu.js.map +1 -0
  87. package/dist/toolPoison.d.ts +32 -0
  88. package/dist/toolPoison.d.ts.map +1 -0
  89. package/dist/toolPoison.js +81 -0
  90. package/dist/toolPoison.js.map +1 -0
  91. package/dist/valkeyErrors.d.ts +17 -0
  92. package/dist/valkeyErrors.d.ts.map +1 -0
  93. package/dist/valkeyErrors.js +24 -0
  94. package/dist/valkeyErrors.js.map +1 -0
  95. package/dist/wasm/context.d.ts +57 -0
  96. package/dist/wasm/context.d.ts.map +1 -0
  97. package/dist/wasm/context.js +59 -0
  98. package/dist/wasm/context.js.map +1 -0
  99. package/dist/wasm/hostImports.d.ts +43 -0
  100. package/dist/wasm/hostImports.d.ts.map +1 -0
  101. package/dist/wasm/hostImports.js +158 -0
  102. package/dist/wasm/hostImports.js.map +1 -0
  103. package/dist/wasm/loader.d.ts +95 -0
  104. package/dist/wasm/loader.d.ts.map +1 -0
  105. package/dist/wasm/loader.js +198 -0
  106. package/dist/wasm/loader.js.map +1 -0
  107. package/dist/wasm/referencedFiles.d.ts +115 -0
  108. package/dist/wasm/referencedFiles.d.ts.map +1 -0
  109. package/dist/wasm/referencedFiles.js +366 -0
  110. package/dist/wasm/referencedFiles.js.map +1 -0
  111. package/dist/wasm/runner.d.ts +91 -0
  112. package/dist/wasm/runner.d.ts.map +1 -0
  113. package/dist/wasm/runner.js +308 -0
  114. package/dist/wasm/runner.js.map +1 -0
  115. package/dist/wasm/watch.d.ts +5 -0
  116. package/dist/wasm/watch.d.ts.map +1 -0
  117. package/dist/wasm/watch.js +54 -0
  118. package/dist/wasm/watch.js.map +1 -0
  119. package/dist/wasm/worker.d.ts +39 -0
  120. package/dist/wasm/worker.d.ts.map +1 -0
  121. package/dist/wasm/worker.js +245 -0
  122. package/dist/wasm/worker.js.map +1 -0
  123. package/package.json +14 -4
@@ -9,17 +9,118 @@
9
9
  *
10
10
  * @module
11
11
  */
12
+ import * as node_crypto from 'node:crypto';
12
13
  import { createStderrLogger as createLogger } from './stderrLog.js';
13
- import { scanToolInput, formatDlpBlockReason } from './dlp.js';
14
+ import { scanToolInput, formatDlpBlockReason, setDynamicPatterns } from './dlp.js';
15
+ import { scanText, injectionSeverity, setDynamicInjectionPatterns } from './injection.js';
16
+ import { evaluateSequenceDetectors, resolveEffectiveDisposition, REASK_MAX_ATTEMPTS } from './anomaly/index.js';
17
+ import { SessionState } from './session.js';
14
18
  const log = createLogger('mcp-proxy-interceptor');
15
19
  export class ToolCallInterceptor {
16
20
  policy;
17
21
  emitter;
18
22
  failOpen;
19
- constructor(policy, emitter, failOpen = true) {
23
+ serverName;
24
+ injectionActionDefault;
25
+ session;
26
+ anomalyModeDefault;
27
+ anomalyOverridesDefault;
28
+ wasmRunner;
29
+ workspaceId;
30
+ constructor(policy, emitter, failOpen = true,
31
+ /**
32
+ * The real MCP server this proxy process fronts, from `--server-name`
33
+ * (config.ts, threaded since Phase D's `wrapWithProxy` but unconsumed
34
+ * until now). `'unknown'` when unset, matching config.ts's own default —
35
+ * see the server-scoping check below for why an allowlist naming
36
+ * anything else then refuses every call from this process.
37
+ */
38
+ serverName = 'unknown',
39
+ /**
40
+ * Env-derived default prompt-injection disposition (`config.ts`'s
41
+ * `mcpInjectionAction`), used whenever `policy.getInjectionAction()`
42
+ * reports no control-plane override. Defaults to `'warn'` here too, so
43
+ * every existing construction site/test that doesn't pass this
44
+ * parameter keeps the same steer-not-kill posture the Rust detector this
45
+ * was ported from uses by default.
46
+ */
47
+ injectionActionDefault = 'warn',
48
+ /**
49
+ * Shared per-process session state (Phase 2) — the SAME instance
50
+ * `McpGovernanceProxy` (proxy.ts) owns and hands to `handleHarnessLine`
51
+ * for post-decision recording, so the sequence this method reads for
52
+ * detection and the sequence `handleHarnessLine` appends to after an
53
+ * `allow` are never two different objects that could drift. Defaults to
54
+ * a fresh instance so every existing construction site/test that
55
+ * predates Phase 2 keeps working unchanged.
56
+ */
57
+ session = new SessionState(),
58
+ /**
59
+ * Env-derived default anomaly mode (`config.ts`'s `mcpAnomalyMode`),
60
+ * used whenever `policy.getAnomalyMode()` reports no control-plane
61
+ * override. Defaults to `'enforce'` — see `config.ts`'s doc comment on
62
+ * why "enforce" here does not mean "block on suspicion": every
63
+ * detector's own Rust-declared disposition ceiling still applies via
64
+ * `resolveEffectiveDisposition`.
65
+ */
66
+ anomalyModeDefault = 'enforce',
67
+ /**
68
+ * Env-derived default per-detector override map (`config.ts`'s
69
+ * `mcpAnomalyOverrides`). Merged with `policy.getAnomalyOverrides()` per
70
+ * key, control-plane value winning — the same "policy overrides the
71
+ * env-derived default, per field" shape `injectionActionDefault` uses,
72
+ * just applied per-map-entry instead of to one scalar.
73
+ */
74
+ anomalyOverridesDefault = {},
75
+ /**
76
+ * Phase 3's WASM custom-rule runner, or `undefined` to skip WASM
77
+ * evaluation entirely (every construction site/test that predates Phase
78
+ * 3, and any deployment with no `~/.intutic/wasm/` rules). Owned by
79
+ * `McpGovernanceProxy` (proxy.ts), same sharing pattern as `session`.
80
+ */
81
+ wasmRunner = undefined,
82
+ /**
83
+ * This proxy's workspace id, from `config.ts` — needed for Phase 3's
84
+ * `RequestContext.workspace_id` field. Stored separately from `policy`
85
+ * (which has no getter for it) and from `serverName` (a different
86
+ * identity: the MCP SERVER this process fronts, not the WORKSPACE it
87
+ * belongs to).
88
+ */
89
+ workspaceId = 'unknown') {
20
90
  this.policy = policy;
21
91
  this.emitter = emitter;
22
92
  this.failOpen = failOpen;
93
+ this.serverName = serverName;
94
+ this.injectionActionDefault = injectionActionDefault;
95
+ this.session = session;
96
+ this.anomalyModeDefault = anomalyModeDefault;
97
+ this.anomalyOverridesDefault = anomalyOverridesDefault;
98
+ this.wasmRunner = wasmRunner;
99
+ this.workspaceId = workspaceId;
100
+ }
101
+ /**
102
+ * Applies the shared reask ladder (Phase 2 anomaly detectors AND Phase 3
103
+ * WASM rules both use this): a session-lifetime attempt counter keyed by
104
+ * `key`, `REASK_MAX_ATTEMPTS` (3) tries before hardening into an
105
+ * unconditional block. Always emits `tool_blocked` — a reask blocks the
106
+ * CURRENT attempt, so existing consumers keyed on `tool_blocked` must see
107
+ * it, exactly like every other new block reason in this package.
108
+ */
109
+ async applyReaskLadder(key, baseReason, toolName, toolInput) {
110
+ // Shared across the session's sibling proxy processes when a session
111
+ // store is configured (Wave 5.3); the per-process counter otherwise.
112
+ const attempts = await this.session.incrReaskAttemptShared(key);
113
+ if (attempts > REASK_MAX_ATTEMPTS) {
114
+ const hardenedReason = `${baseReason} — hardened to an unconditional block after ${REASK_MAX_ATTEMPTS} reask ` +
115
+ `attempts with no correction.`;
116
+ log.warn({ action: 'reask_hardened', toolName, key, attempts }, 'Reask attempts exhausted — hardening to a hard block');
117
+ this.emitter.emit('tool_blocked', toolName, toolInput, hardenedReason);
118
+ return { action: 'block', reason: hardenedReason };
119
+ }
120
+ const reaskReason = `${baseReason} (attempt ${attempts}/${REASK_MAX_ATTEMPTS} — will become an unconditional ` +
121
+ `block if this keeps tripping)`;
122
+ this.emitter.emit('tool_blocked', toolName, toolInput, reaskReason);
123
+ return { action: 'block', reason: reaskReason };
23
124
  }
24
125
  /**
25
126
  * Evaluate a tools/call request and return a governance decision.
@@ -30,9 +131,65 @@ export class ToolCallInterceptor {
30
131
  */
31
132
  async decide(toolName, toolInput) {
32
133
  log.debug({ action: 'interceptor_decide', toolName }, 'Evaluating tool call');
33
- // 1. DLP scan
134
+ // Captured across pipeline steps for Phase 3's WASM context (built only
135
+ // if this call reaches step 5) — each stays honestly empty/undefined
136
+ // when its step never ran or found nothing, never fabricated.
137
+ let dlpFindingsForContext = [];
138
+ let injectionFindingsForContext = [];
139
+ let injectionSourcesForContext = [];
140
+ let corroboratingDetectorsForContext = 0;
141
+ // -1. Additive SERVER scoping. When the workspace declares a server
142
+ // allowlist (mcpAllowedServers), ONLY calls proxied to those servers may
143
+ // proceed — checked ahead of the per-tool allowlist below since a
144
+ // disallowed server should never even reach tool-name evaluation. Same
145
+ // empty-means-unrestricted convention as every allowlist in this file,
146
+ // and — deliberately, like the tool-scoping check right below it — NOT
147
+ // gated on `failOpen`: an explicit, non-empty allowlist that excludes
148
+ // this server is a definite policy decision the proxy already has the
149
+ // data to make, not a "control plane unreachable" failure mode. A
150
+ // control-plane outage naturally fails open here too, for the same
151
+ // reason it does for tool scoping: an unreachable policy fetch leaves
152
+ // `allowedServers` empty, which reads as unrestricted rather than as a
153
+ // block.
154
+ const allowedServers = this.policy.getAllowedServers();
155
+ if (allowedServers.length > 0 && !allowedServers.includes(this.serverName)) {
156
+ const reason = `MCP server "${this.serverName}" is not in this workspace's MCP server allowlist ` +
157
+ `(${allowedServers.length} server(s) permitted). An operator can widen the ` +
158
+ `allowlist in workspace settings (mcpAllowedServers).`;
159
+ log.warn({ action: 'server_allowlist_block', serverName: this.serverName, toolName }, reason);
160
+ this.emitter.emit('tool_blocked', toolName, toolInput, reason);
161
+ return { action: 'block', reason };
162
+ }
163
+ // 0. Additive tool scoping. When the workspace declares an allowlist,
164
+ // ONLY those tools may be called — the inverse of every other rule here,
165
+ // which names what is forbidden. An EMPTY allowlist means unrestricted,
166
+ // never "permit nothing": read the other way it would block every
167
+ // workspace that never declared one, which is the exact inversion the
168
+ // starter rules' harness-allowlist near-miss exists to catch.
169
+ const allowedTools = this.policy.getAllowedTools();
170
+ if (allowedTools.length > 0 && !allowedTools.includes(toolName)) {
171
+ const reason = `Tool "${toolName}" is not in this workspace's MCP tool allowlist ` +
172
+ `(${allowedTools.length} tool(s) permitted). An operator can widen the ` +
173
+ `allowlist in workspace settings (mcpAllowedTools).`;
174
+ log.warn({ action: 'allowlist_block', toolName }, reason);
175
+ this.emitter.emit('tool_blocked', toolName, toolInput, reason);
176
+ return { action: 'block', reason };
177
+ }
178
+ // 1. DLP scan — with the workspace's own patterns loaded first, so a
179
+ // control-plane-defined pattern reaches the same scanner as the floor.
180
+ try {
181
+ setDynamicPatterns(this.policy.getDlpPatterns());
182
+ }
183
+ catch {
184
+ // Pattern delivery must never take the scanner down; the floor stands.
185
+ }
34
186
  try {
35
187
  const dlp = scanToolInput(toolInput);
188
+ // Captured regardless of outcome — by pipeline position, a non-empty
189
+ // result here always blocks below, so `dlpFindingsForContext` is
190
+ // honestly almost always `[]` by the time Phase 3 reads it (a
191
+ // property of the pipeline order, not a bug in this capture).
192
+ dlpFindingsForContext = dlp.findings;
36
193
  if (dlp.hasFinding) {
37
194
  const reason = formatDlpBlockReason(dlp.findings);
38
195
  log.warn({ action: 'dlp_block', toolName, findings: dlp.findings }, 'DLP block');
@@ -63,7 +220,10 @@ export class ToolCallInterceptor {
63
220
  }
64
221
  if (rule.action === 'warn') {
65
222
  log.warn({ action: 'policy_warn', toolName, ruleId: rule.id, reason: rule.reason }, 'Policy warning (allowing)');
66
- // Fall through to allow — warnings are logged only
223
+ // Fall through to allow. Reported as `tool_flagged` with the rule id
224
+ // in the reason — the same shape the harness gates use — so a
225
+ // SHADOW guardrail's evidence (LLD #71) counts this proxy's traffic.
226
+ this.emitter.emit('tool_flagged', toolName, toolInput, `${rule.reason} [${rule.id}]`);
67
227
  }
68
228
  // 'require_approval' treated as block in headless proxy (no interactive UI)
69
229
  if (rule.action === 'require_approval') {
@@ -85,7 +245,143 @@ export class ToolCallInterceptor {
85
245
  };
86
246
  }
87
247
  }
88
- // 3. Allow — emit telemetry event
248
+ // 3. Prompt-injection scan (request direction) — the `toolInput` a
249
+ // MISBEHAVING or COMPROMISED calling agent sends, e.g. an agent that
250
+ // itself already ingested injected content from an earlier step and is
251
+ // now echoing instructions into its own tool call. Pipeline position
252
+ // mirrors the Rust proxy's own ordering: after DLP/SOP, before anomaly
253
+ // detection and WASM rules (neither exists yet in this package — Phase
254
+ // 2/3 land after this).
255
+ try {
256
+ const toolInputText = JSON.stringify(toolInput ?? {});
257
+ setDynamicInjectionPatterns(this.policy.getInjectionPatterns());
258
+ const findings = scanText(toolInputText);
259
+ if (findings.length > 0) {
260
+ injectionFindingsForContext = findings;
261
+ injectionSourcesForContext = ['tool_input'];
262
+ const severity = injectionSeverity(findings, 'tool_input');
263
+ const reason = `Prompt-injection pattern(s) detected in tool input: ${findings.join(', ')}`;
264
+ log.warn({ action: 'injection_detected', toolName, patterns: findings, source: 'tool_input', severity }, 'Prompt-injection pattern matched in tool call input');
265
+ const injectionAction = this.policy.getInjectionAction() ?? this.injectionActionDefault;
266
+ this.emitter.emit('injection_detected', toolName, toolInput, reason, severity);
267
+ if (injectionAction === 'block') {
268
+ this.emitter.emit('tool_blocked', toolName, toolInput, reason);
269
+ return { action: 'block', reason };
270
+ }
271
+ // 'warn' (default): report only, fall through to allow.
272
+ }
273
+ }
274
+ catch (err) {
275
+ // Never let a scanner failure take the proxy down — same posture as
276
+ // the DLP/SOP try/catches above, and unconditionally non-fatal (unlike
277
+ // those) because injection scanning's own default disposition is
278
+ // report-only, so a scanner error degrading to "scan skipped" is no
279
+ // more permissive than the feature's own warn default.
280
+ log.error({ action: 'injection_scan_error', err: err.message }, 'Injection scan error — skipping');
281
+ }
282
+ // 4. Anomaly detection (Phase 2) — the 7 detectors ported from
283
+ // `packages/proxy/src/plugins/anomaly/detectors.rs`; see
284
+ // `anomaly/detectors.ts`. Five (consecutive_repeat, ping_pong_cycle,
285
+ // landmark_cycle, tool_diversity_collapse, code_as_action) are
286
+ // sequence/current-call detectors evaluated here, on every request; the
287
+ // other two (tool_poisoning, dlp_escalation) read data this pipeline
288
+ // stage does not have (a cached tools/list, response-direction DLP
289
+ // findings) and run from `proxy.ts`'s response-direction handling
290
+ // instead. Position: after injection, before WASM (Phase 3, not yet
291
+ // built) and allow.
292
+ const anomalyMode = this.policy.getAnomalyMode() ?? this.anomalyModeDefault;
293
+ // The session window — the shared one across this session's sibling
294
+ // proxy processes when a store is configured (Wave 5.3, TD-437), the
295
+ // per-process one otherwise. One round trip at most per tools/call, and
296
+ // none when nothing below would read it.
297
+ const window = anomalyMode !== 'off' || this.wasmRunner ? await this.session.loadWindow(toolName) : undefined;
298
+ if (anomalyMode !== 'off' && window) {
299
+ try {
300
+ const prospective = window.prospective;
301
+ const findings = evaluateSequenceDetectors(prospective, toolName, toolInput);
302
+ corroboratingDetectorsForContext = findings.length;
303
+ const overrides = { ...this.anomalyOverridesDefault, ...this.policy.getAnomalyOverrides() };
304
+ for (const finding of findings) {
305
+ const effective = resolveEffectiveDisposition(finding, anomalyMode, overrides);
306
+ if (effective === 'off')
307
+ continue; // demoted below reporting entirely — skip silently
308
+ const severity = effective === 'kill' ? 'high' : effective === 'reask' ? 'medium' : 'low';
309
+ log.warn({
310
+ action: 'anomaly_detected',
311
+ toolName,
312
+ detectorId: finding.detectorId,
313
+ kind: finding.kind,
314
+ disposition: effective,
315
+ confidence: finding.confidence,
316
+ }, 'Anomaly detector fired');
317
+ this.emitter.emit('anomaly_detected', toolName, toolInput, finding.reason, severity);
318
+ if (effective === 'kill') {
319
+ this.emitter.emit('tool_blocked', toolName, toolInput, finding.reason);
320
+ return { action: 'block', reason: finding.reason };
321
+ }
322
+ if (effective === 'reask') {
323
+ // Keyed per-detector-id, independent of every other detector's
324
+ // (and every WASM rule's — see applyReaskLadder) own counter.
325
+ return await this.applyReaskLadder(finding.detectorId, finding.reason, toolName, toolInput);
326
+ }
327
+ // 'steer': report only. Findings are sorted most-severe-first
328
+ // (evaluateSequenceDetectors), so once we reach a 'steer' finding
329
+ // every remaining one is 'steer' too — keep logging/emitting them,
330
+ // then fall through to allow.
331
+ }
332
+ }
333
+ catch (err) {
334
+ // Same non-fatal posture as the injection scanner above: an anomaly
335
+ // detector is a pure function of in-memory state, but a defensive
336
+ // catch here means a bug in one detector degrades to "this call's
337
+ // anomaly check was skipped," never "the proxy crashed."
338
+ log.error({ action: 'anomaly_detection_error', err: err.message }, 'Anomaly detection error — skipping');
339
+ }
340
+ }
341
+ // 5. WASM custom rules (Phase 3) — operator-authored, compiled
342
+ // AssemblyScript rules dropped into `~/.intutic/wasm/`. Position: after
343
+ // every built-in check, immediately before allow — this is deliberately
344
+ // the LAST gate, so a WASM rule's `RequestContext` carries
345
+ // `injection_findings`/`corroborating_detectors`/etc. already populated
346
+ // by the steps above it (see `wasm/context.ts`'s module doc).
347
+ if (this.wasmRunner) {
348
+ try {
349
+ const verdict = await this.wasmRunner.evaluate({
350
+ sessionId: this.session.sessionId,
351
+ workspaceId: this.workspaceId,
352
+ tools: this.session.getToolsList(),
353
+ toolCallId: node_crypto.randomUUID(),
354
+ toolName,
355
+ toolArguments: toolInput,
356
+ toolSequence: window?.prospective ?? this.session.prospectiveSequence(toolName),
357
+ callsLast60s: window?.callsLast60s ?? this.session.callsInLastMs(),
358
+ dlpFindingDescriptions: dlpFindingsForContext.map((f) => f.description),
359
+ injectionFindings: injectionFindingsForContext,
360
+ injectionSources: injectionSourcesForContext,
361
+ corroboratingDetectors: corroboratingDetectorsForContext,
362
+ toolContractChanged: this.session.getToolContractChanged(),
363
+ });
364
+ if (verdict.code === 'block') {
365
+ log.warn({ action: 'wasm_block', toolName, ruleId: verdict.ruleId }, 'Tool call blocked by WASM governance rule');
366
+ this.emitter.emit('tool_blocked', toolName, toolInput, verdict.reason);
367
+ return { action: 'block', reason: verdict.reason };
368
+ }
369
+ if (verdict.code === 'reask') {
370
+ // Keyed per-rule-id, independent of every anomaly detector's own
371
+ // counter — see applyReaskLadder's doc comment.
372
+ return await this.applyReaskLadder(`wasm:${verdict.ruleId}`, verdict.reason, toolName, toolInput);
373
+ }
374
+ // 'allow': fall through.
375
+ }
376
+ catch (err) {
377
+ // Fail-open, matching every other governance-check catch in this
378
+ // method — a WASM runner failure (not to be confused with a single
379
+ // rule's own timeout/trap, which `WasmRunner.evaluate` already
380
+ // absorbs internally) must not take the whole proxy down.
381
+ log.error({ action: 'wasm_evaluate_error', err: err.message }, 'WASM rule evaluation error — skipping');
382
+ }
383
+ }
384
+ // 6. Allow — emit telemetry event
89
385
  this.emitter.emit('tool_allowed', toolName, toolInput);
90
386
  return { action: 'allow' };
91
387
  }
@@ -1 +1 @@
1
- {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
package/dist/policy.d.ts CHANGED
@@ -17,6 +17,28 @@ export interface SopRule {
17
17
  /** Human-readable reason reported back to harness */
18
18
  reason: string;
19
19
  }
20
+ /**
21
+ * Is this unvalidated object actually a policy rule?
22
+ *
23
+ * The daemon caches `sopRules` as `Record<string, unknown>[]` — it filters the
24
+ * control plane's response to plain objects and validates nothing further. The
25
+ * client then used `as SopRule[]`, behind a `Promise<any>` that hid the
26
+ * mismatch entirely.
27
+ *
28
+ * That matters because these drive blocking decisions: a rule missing
29
+ * `toolPattern` reaches `new RegExp(undefined)` and matches everything, and a
30
+ * rule with an unrecognised `action` falls through every branch and enforces
31
+ * nothing. Neither is a shape the control plane intends to send — which is the
32
+ * argument for dropping the malformed ones loudly rather than trusting a cast.
33
+ */
34
+ /**
35
+ * Exported because it is the contract between this package and whatever
36
+ * produces rules for it, not merely an internal helper. `policyCache`'s snapshot
37
+ * seed has to satisfy it, and a test that asserted against a *copy* of this
38
+ * predicate would prove nothing about the real one — which is precisely the
39
+ * two-copies-of-one-rule failure the harness gates were just rebuilt to remove.
40
+ */
41
+ export declare function isSopRule(value: unknown): value is SopRule;
20
42
  export declare class PolicyClient {
21
43
  private readonly controlPlaneUrl;
22
44
  private readonly apiKey;
@@ -24,15 +46,101 @@ export declare class PolicyClient {
24
46
  private readonly ttlMs;
25
47
  private readonly mcpProxyMode;
26
48
  private rules;
49
+ /**
50
+ * Workspace DLP regex sources, delivered to the scanner on every refresh.
51
+ * The daemon has cached these since the policy cache existed; this client
52
+ * received and dropped them — a workspace's custom patterns were a setting
53
+ * wired to nothing.
54
+ */
55
+ private dlpPatterns;
56
+ /** Workspace injection-pattern sources (`mcpInjectionPatterns`, TD-436); empty = floor only. */
57
+ private injectionPatterns;
58
+ /**
59
+ * Additive tool scoping: when non-empty, ONLY these tools may be called
60
+ * through this proxy. Empty means unrestricted — the same convention as
61
+ * `allow_harnesses` and the starter rules' allowlist inversion warnings:
62
+ * an empty allowlist read as "permit nothing" would block every workspace
63
+ * that never declared one.
64
+ */
65
+ private allowedTools;
66
+ /** Operator-curated tool descriptions, applied to tools/list responses. */
67
+ private toolDescriptionOverrides;
68
+ /**
69
+ * Additive SERVER scoping: when non-empty, ONLY these MCP server names may
70
+ * be proxied by this process. Empty means unrestricted — same convention
71
+ * and same source as `allowedTools` above (mirrors `mcpAllowedTools` /
72
+ * `mcpAllowedServers` in workspace settings, delivered via the same
73
+ * `GET /api/v1/sop/rules` response this class already polls).
74
+ */
75
+ private allowedServers;
76
+ /**
77
+ * Control-plane-delivered prompt-injection disposition override. `undefined`
78
+ * (never `'warn'`/`'block'` by default) means "the control plane did not
79
+ * send this field" — distinct from the field being explicitly set to
80
+ * `'warn'` — so the caller (interceptor.ts) knows to fall back to
81
+ * `config.ts`'s `mcpInjectionAction` env-derived default rather than
82
+ * silently treating an unset policy as an explicit warn.
83
+ */
84
+ private injectionAction;
85
+ /**
86
+ * Control-plane-delivered anomaly-detection mode override (Phase 2).
87
+ * `undefined` when absent — same "absent means no override, fall back to
88
+ * config.ts's env-derived default" convention as `injectionAction` above.
89
+ */
90
+ private anomalyMode;
91
+ /**
92
+ * Control-plane-delivered per-detector disposition overrides. Always an
93
+ * object (never `undefined`) — an absent policy field reads as "no
94
+ * overrides," which `resolveEffectiveDisposition` (anomaly/index.ts)
95
+ * already treats as a no-op via `overrides[detectorId] === undefined`, the
96
+ * same empty-object-means-unrestricted convention `toolDescriptionOverrides`
97
+ * above uses.
98
+ */
99
+ private anomalyOverrides;
27
100
  private lastFetchAt;
28
101
  private refreshTimer;
29
102
  constructor(controlPlaneUrl: string, apiKey: string, workspaceId: string, ttlMs?: number, mcpProxyMode?: string);
30
- /** Start background refresh timer. */
31
- start(): void;
103
+ /**
104
+ * Start background refresh timer.
105
+ *
106
+ * @param onTick - Optional callback invoked on the SAME interval as the
107
+ * policy refresh (including the initial kick below) — Phase 3's
108
+ * `~/.intutic/wasm/` rescan (`WasmRunner.rescan`, wired from proxy.ts)
109
+ * hooks this rather than starting a second timer, per the task's own
110
+ * "don't add a second timer" instruction. Errors are swallowed the same
111
+ * way `refresh()`'s own are — a rescan failure must not take down the
112
+ * policy refresh it rides alongside.
113
+ */
114
+ start(onTick?: () => void | Promise<void>): void;
32
115
  /** Stop the background refresh timer. */
33
116
  stop(): void;
34
117
  /** Return the current cached rule set. */
35
118
  getRules(): readonly SopRule[];
119
+ /** Workspace DLP regex sources from the last successful refresh. */
120
+ /** Workspace-supplied prompt-injection regex sources; empty means the hardcoded floor alone. */
121
+ getInjectionPatterns(): readonly string[];
122
+ getDlpPatterns(): readonly string[];
123
+ /** The additive tool allowlist; empty means unrestricted. */
124
+ getAllowedTools(): readonly string[];
125
+ /** The additive MCP server allowlist; empty means unrestricted. */
126
+ getAllowedServers(): readonly string[];
127
+ /** Operator description overrides for tools/list curation. */
128
+ getToolDescriptionOverrides(): Readonly<Record<string, string>>;
129
+ /**
130
+ * The control-plane-delivered prompt-injection disposition, or `undefined`
131
+ * when the policy source never sent `mcpInjectionAction` — absent means
132
+ * "no override", not "warn". Callers (interceptor.ts) fall back to
133
+ * `config.ts`'s env-derived default in that case, the same "absent means
134
+ * default" convention every other optional policy field in this class
135
+ * follows.
136
+ */
137
+ getInjectionAction(): 'warn' | 'block' | undefined;
138
+ /** The control-plane-delivered anomaly mode, or `undefined` if unset (see field doc). */
139
+ getAnomalyMode(): 'enforce' | 'warn' | 'off' | undefined;
140
+ /** Per-detector disposition overrides; always an object, empty means none. */
141
+ getAnomalyOverrides(): Readonly<Record<string, 'steer' | 'reask' | 'kill' | 'off'>>;
142
+ /** Parse the optional curation fields shared by both refresh paths. */
143
+ private absorbCuration;
36
144
  /** Find the first matching rule for a given tool name + serialized args. */
37
145
  matchRule(toolName: string, toolInputJson: string): SopRule | null;
38
146
  /** Fetch fresh rules from the control plane. */
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,kBAAkB,CAAA;IAC7C,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;CACf;AAMD,qBAAa,YAAY;IAMrB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAT/B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAA8B;gBAG/B,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAe,EACtB,YAAY,GAAE,MAAsB;IAGvD,sCAAsC;IACtC,KAAK,IAAI,IAAI;IAab,yCAAyC;IACzC,IAAI,IAAI,IAAI;IAOZ,0CAA0C;IAC1C,QAAQ,IAAI,SAAS,OAAO,EAAE;IAI9B,4EAA4E;IAC5E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAiBlE,gDAAgD;IAC1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAyB/B"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,kBAAkB,CAAA;IAC7C,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;GAaG;AACH;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAU1D;AAMD,qBAAa,YAAY;IAyDrB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY;IA5D/B,OAAO,CAAC,KAAK,CAAgB;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,WAAW,CAAe;IAClC,gGAAgG;IAChG,OAAO,CAAC,iBAAiB,CAAe;IACxC;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,CAAe;IACnC,2EAA2E;IAC3E,OAAO,CAAC,wBAAwB,CAA6B;IAC7D;;;;;;OAMG;IACH,OAAO,CAAC,cAAc,CAAe;IACrC;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAA8B;IACrD;;;;OAIG;IACH,OAAO,CAAC,WAAW,CAAwC;IAC3D;;;;;;;OAOG;IACH,OAAO,CAAC,gBAAgB,CAAyD;IACjF,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAA8B;gBAG/B,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAe,EACtB,YAAY,GAAE,MAAsB;IAGvD;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAehD,yCAAyC;IACzC,IAAI,IAAI,IAAI;IAOZ,0CAA0C;IAC1C,QAAQ,IAAI,SAAS,OAAO,EAAE;IAI9B,oEAAoE;IACpE,gGAAgG;IAChG,oBAAoB,IAAI,SAAS,MAAM,EAAE;IAIzC,cAAc,IAAI,SAAS,MAAM,EAAE;IAInC,6DAA6D;IAC7D,eAAe,IAAI,SAAS,MAAM,EAAE;IAIpC,mEAAmE;IACnE,iBAAiB,IAAI,SAAS,MAAM,EAAE;IAItC,8DAA8D;IAC9D,2BAA2B,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI/D;;;;;;;OAOG;IACH,kBAAkB,IAAI,MAAM,GAAG,OAAO,GAAG,SAAS;IAIlD,yFAAyF;IACzF,cAAc,IAAI,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS;IAIxD,8EAA8E;IAC9E,mBAAmB,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC,CAAC;IAInF,uEAAuE;IACvE,OAAO,CAAC,cAAc;IAmCtB,4EAA4E;IAC5E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAiBlE,gDAAgD;IAC1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA6D/B"}