@intutic/mcp-governance-proxy 2.2.0 → 2.4.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 +8 -7
- package/dist/agentTools.d.ts +83 -0
- package/dist/agentTools.d.ts.map +1 -0
- package/dist/agentTools.js +170 -0
- package/dist/agentTools.js.map +1 -0
- package/dist/anomaly/detectors.js +1 -1
- package/dist/anomaly/detectors.js.map +1 -1
- package/dist/approvalHold.d.ts +8 -7
- package/dist/approvalHold.d.ts.map +1 -1
- package/dist/approvalHold.js +7 -6
- package/dist/approvalHold.js.map +1 -1
- package/dist/budget.d.ts +133 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +191 -0
- package/dist/budget.js.map +1 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/daemon/healthMonitor.d.ts.map +1 -1
- package/dist/daemon/healthMonitor.js +0 -1
- package/dist/daemon/healthMonitor.js.map +1 -1
- package/dist/daemon/index.d.ts +0 -2
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +0 -2
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/policyCache.d.ts +22 -3
- package/dist/daemon/policyCache.d.ts.map +1 -1
- package/dist/daemon/policyCache.js +38 -15
- package/dist/daemon/policyCache.js.map +1 -1
- package/dist/daemon/socketServer.d.ts +0 -1
- package/dist/daemon/socketServer.d.ts.map +1 -1
- package/dist/daemon/socketServer.js +0 -1
- package/dist/daemon/socketServer.js.map +1 -1
- package/dist/daemon/statusReporter.d.ts.map +1 -1
- package/dist/daemon/statusReporter.js +0 -1
- package/dist/daemon/statusReporter.js.map +1 -1
- package/dist/daemon/telemetryBatcher.d.ts.map +1 -1
- package/dist/daemon/telemetryBatcher.js +0 -1
- package/dist/daemon/telemetryBatcher.js.map +1 -1
- package/dist/daemonClient.d.ts.map +1 -1
- package/dist/daemonClient.js +0 -1
- package/dist/daemonClient.js.map +1 -1
- package/dist/dlp.d.ts +32 -3
- package/dist/dlp.d.ts.map +1 -1
- package/dist/dlp.js +116 -18
- package/dist/dlp.js.map +1 -1
- package/dist/dlpPii.d.ts +66 -0
- package/dist/dlpPii.d.ts.map +1 -0
- package/dist/dlpPii.js +235 -0
- package/dist/dlpPii.js.map +1 -0
- package/dist/emitter.d.ts +65 -3
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +13 -1
- package/dist/emitter.js.map +1 -1
- package/dist/guardedValkey.d.ts +40 -0
- package/dist/guardedValkey.d.ts.map +1 -0
- package/dist/guardedValkey.js +84 -0
- package/dist/guardedValkey.js.map +1 -0
- package/dist/httpJson.d.ts +7 -0
- package/dist/httpJson.d.ts.map +1 -1
- package/dist/httpJson.js +39 -0
- package/dist/httpJson.js.map +1 -1
- package/dist/injection.js +1 -1
- package/dist/interceptor.d.ts +65 -10
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +190 -69
- package/dist/interceptor.js.map +1 -1
- package/dist/policy.d.ts +37 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +62 -3
- package/dist/policy.js.map +1 -1
- package/dist/proxy.d.ts +30 -9
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +127 -57
- package/dist/proxy.js.map +1 -1
- package/dist/refusals.d.ts +26 -0
- package/dist/refusals.d.ts.map +1 -0
- package/dist/refusals.js +43 -0
- package/dist/refusals.js.map +1 -0
- package/dist/registryObserver.d.ts +9 -5
- package/dist/registryObserver.d.ts.map +1 -1
- package/dist/registryObserver.js +19 -8
- package/dist/registryObserver.js.map +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/session.js +1 -1
- package/dist/sessionScope.d.ts +1 -1
- package/dist/sessionScope.js +1 -1
- package/dist/sessionStore.d.ts +10 -23
- package/dist/sessionStore.d.ts.map +1 -1
- package/dist/sessionStore.js +17 -76
- package/dist/sessionStore.js.map +1 -1
- package/dist/wasm/cloudRules.d.ts +138 -0
- package/dist/wasm/cloudRules.d.ts.map +1 -0
- package/dist/wasm/cloudRules.js +242 -0
- package/dist/wasm/cloudRules.js.map +1 -0
- package/dist/wasm/context.d.ts +10 -0
- package/dist/wasm/context.d.ts.map +1 -1
- package/dist/wasm/context.js +27 -0
- package/dist/wasm/context.js.map +1 -1
- package/dist/wasm/hostImports.d.ts +3 -3
- package/dist/wasm/hostImports.d.ts.map +1 -1
- package/dist/wasm/hostImports.js +8 -7
- package/dist/wasm/hostImports.js.map +1 -1
- package/dist/wasm/loader.d.ts +10 -1
- package/dist/wasm/loader.d.ts.map +1 -1
- package/dist/wasm/loader.js +2 -0
- package/dist/wasm/loader.js.map +1 -1
- package/dist/wasm/referencedFiles.d.ts +29 -4
- package/dist/wasm/referencedFiles.d.ts.map +1 -1
- package/dist/wasm/referencedFiles.js +48 -21
- package/dist/wasm/referencedFiles.js.map +1 -1
- package/dist/wasm/runner.d.ts +169 -31
- package/dist/wasm/runner.d.ts.map +1 -1
- package/dist/wasm/runner.js +298 -83
- package/dist/wasm/runner.js.map +1 -1
- package/dist/wasm/watch.js +1 -1
- package/dist/wasm/worker.d.ts +10 -6
- package/dist/wasm/worker.d.ts.map +1 -1
- package/dist/wasm/worker.js +93 -9
- package/dist/wasm/worker.js.map +1 -1
- package/package.json +5 -4
- package/dist/toolPoison.d.ts +0 -32
- package/dist/toolPoison.d.ts.map +0 -1
- package/dist/toolPoison.js +0 -81
- package/dist/toolPoison.js.map +0 -1
- package/dist/wasm/fuel.d.ts +0 -35
- package/dist/wasm/fuel.d.ts.map +0 -1
- package/dist/wasm/fuel.js +0 -299
- package/dist/wasm/fuel.js.map +0 -1
- package/dist/wasm/memoryCap.d.ts +0 -21
- package/dist/wasm/memoryCap.d.ts.map +0 -1
- package/dist/wasm/memoryCap.js +0 -54
- package/dist/wasm/memoryCap.js.map +0 -1
- package/dist/wasm/wasmBinary.d.ts +0 -25
- package/dist/wasm/wasmBinary.d.ts.map +0 -1
- package/dist/wasm/wasmBinary.js +0 -100
- package/dist/wasm/wasmBinary.js.map +0 -1
package/dist/httpJson.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"httpJson.js","sourceRoot":"","sources":["../src/httpJson.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"httpJson.js","sourceRoot":"","sources":["../src/httpJson.ts"],"names":[],"mappings":"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"}
|
package/dist/injection.js
CHANGED
|
@@ -65,7 +65,7 @@ const PATTERNS = [
|
|
|
65
65
|
*/
|
|
66
66
|
const KILL_THRESHOLD = 2;
|
|
67
67
|
/**
|
|
68
|
-
* Workspace-supplied patterns, compiled
|
|
68
|
+
* Workspace-supplied patterns, compiled. Same contract as
|
|
69
69
|
* `dlp.ts`'s `setDynamicPatterns`: sources come from the control plane's
|
|
70
70
|
* `mcpInjectionPatterns`, an invalid one is dropped and counted, and the
|
|
71
71
|
* hardcoded floor above applies regardless — an unreachable control plane
|
package/dist/interceptor.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* 0. The MCP server registry, server/tool allowlists and SSO group clearance
|
|
6
6
|
* 1. DLP scan (credential / destructive pattern detection)
|
|
7
7
|
* 2. SOP policy rules (fetched from control plane via PolicyClient)
|
|
8
|
+
* 3–5. Prompt injection, anomaly detectors, WASM rules
|
|
9
|
+
* 6. MCP call budgets — last, so only a call that would otherwise run is counted
|
|
8
10
|
*
|
|
9
11
|
* Returns an allow / block / redact decision.
|
|
10
12
|
*
|
|
@@ -16,25 +18,38 @@ import type { WasmRunner } from './wasm/runner.js';
|
|
|
16
18
|
import type { PolicyClient } from './policy.js';
|
|
17
19
|
import { type GovernanceEmitter } from './emitter.js';
|
|
18
20
|
import type { ApprovalHolds } from './approvalHold.js';
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
import { type McpBudgetEnforcer } from './budget.js';
|
|
22
|
+
import type { McpRefusalCode } from './refusals.js';
|
|
23
|
+
/**
|
|
24
|
+
* A refusal: what the agent reads (`reason`), and what a client reads — the
|
|
25
|
+
* stable `code`, the `ruleId` that decided, and any `detail` the code carries
|
|
26
|
+
* (refusals.ts). All three reach the JSON-RPC frame's `error.data`.
|
|
27
|
+
*/
|
|
28
|
+
export interface Block {
|
|
22
29
|
action: 'block';
|
|
23
30
|
reason: string;
|
|
24
|
-
|
|
31
|
+
code: Exclude<McpRefusalCode, 'HELD'>;
|
|
32
|
+
ruleId: string;
|
|
33
|
+
detail?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export type Decision = {
|
|
36
|
+
action: 'allow';
|
|
37
|
+
} | Block | {
|
|
25
38
|
action: 'redact';
|
|
26
39
|
reason: string;
|
|
27
40
|
redactedInput: unknown;
|
|
28
41
|
}
|
|
29
42
|
/**
|
|
30
43
|
* Refused for now, pending a person's approval (`require_approval`). Not a
|
|
31
|
-
* block: the agent is told the hold id and
|
|
32
|
-
*
|
|
44
|
+
* block: the agent is told the hold id and to retry once it is approved,
|
|
45
|
+
* which passes only while the workspace's review-hold bypass is on — see
|
|
46
|
+
* approvalHold.ts. `holdId` is empty when the hold could not be recorded.
|
|
33
47
|
*/
|
|
34
48
|
| {
|
|
35
49
|
action: 'hold';
|
|
36
50
|
reason: string;
|
|
37
51
|
holdId: string;
|
|
52
|
+
ruleId: string;
|
|
38
53
|
};
|
|
39
54
|
export declare class ToolCallInterceptor {
|
|
40
55
|
private readonly policy;
|
|
@@ -111,6 +126,13 @@ export declare class ToolCallInterceptor {
|
|
|
111
126
|
* refusal — never an allow.
|
|
112
127
|
*/
|
|
113
128
|
private readonly holds;
|
|
129
|
+
/**
|
|
130
|
+
* Counts each call against the workspace's MCP call budgets (budget.ts).
|
|
131
|
+
* `undefined` (construction sites that predate budgets) skips them; the
|
|
132
|
+
* proxy always passes one, with no store when it has no Valkey, so a
|
|
133
|
+
* covered call then resolves through the fail setting.
|
|
134
|
+
*/
|
|
135
|
+
private readonly budgets;
|
|
114
136
|
constructor(policy: PolicyClient, emitter: GovernanceEmitter,
|
|
115
137
|
/**
|
|
116
138
|
* The local fail setting (`INTUTIC_MCP_FAIL_OPEN`, config.ts). The
|
|
@@ -183,7 +205,16 @@ export declare class ToolCallInterceptor {
|
|
|
183
205
|
* holds) leaves such a call held with nothing recorded, which is still a
|
|
184
206
|
* refusal — never an allow.
|
|
185
207
|
*/
|
|
186
|
-
holds?: ApprovalHolds | undefined
|
|
208
|
+
holds?: ApprovalHolds | undefined,
|
|
209
|
+
/**
|
|
210
|
+
* Counts each call against the workspace's MCP call budgets (budget.ts).
|
|
211
|
+
* `undefined` (construction sites that predate budgets) skips them; the
|
|
212
|
+
* proxy always passes one, with no store when it has no Valkey, so a
|
|
213
|
+
* covered call then resolves through the fail setting.
|
|
214
|
+
*/
|
|
215
|
+
budgets?: McpBudgetEnforcer | undefined);
|
|
216
|
+
/** Set once this process has said that budgets went unchecked under fail-open, so the log says it once. */
|
|
217
|
+
private budgetsUncheckedWarned;
|
|
187
218
|
/**
|
|
188
219
|
* Whether a governance check that cannot complete lets the call through:
|
|
189
220
|
* the workspace's `mcpProxyFailBehavior` when the control plane has sent
|
|
@@ -203,9 +234,9 @@ export declare class ToolCallInterceptor {
|
|
|
203
234
|
/**
|
|
204
235
|
* A `require_approval` rule matched. Returns the hold decision, or `null`
|
|
205
236
|
* when an approved bypass for this exact call lets it continue. Every hold
|
|
206
|
-
* reason names the hold id and
|
|
207
|
-
*
|
|
208
|
-
* knows what to
|
|
237
|
+
* reason names the hold id, who may approve it and when a retry passes, in
|
|
238
|
+
* the words the hook gates print (`holdApprovalHint`), so a person reading
|
|
239
|
+
* the agent's transcript knows what to do.
|
|
209
240
|
*/
|
|
210
241
|
private hold;
|
|
211
242
|
/**
|
|
@@ -226,6 +257,18 @@ export declare class ToolCallInterceptor {
|
|
|
226
257
|
* from an `allow` one without the registry.
|
|
227
258
|
*/
|
|
228
259
|
private checkRegistry;
|
|
260
|
+
/**
|
|
261
|
+
* Brings the workspace's control-plane rules up to date with the policy, and
|
|
262
|
+
* refuses the call when they have never loaded and the proxy fails closed.
|
|
263
|
+
*
|
|
264
|
+
* Never loaded means no policy from the control plane yet, or one whose rule
|
|
265
|
+
* binaries could not be fetched: whether one of those rules refuses this
|
|
266
|
+
* call is unknown. Fail-open judges the call by the rules that are loaded —
|
|
267
|
+
* this machine's — as a workspace with no uploaded rules would be;
|
|
268
|
+
* fail-closed refuses it, as it does an unknown registry. Once loaded, the
|
|
269
|
+
* set is kept for as long as the process runs, through any later outage.
|
|
270
|
+
*/
|
|
271
|
+
private checkCloudRulesLoaded;
|
|
229
272
|
/**
|
|
230
273
|
* The workspace's SSO group policy, applied to the member this proxy's API
|
|
231
274
|
* key resolves to, by `evaluateSsoGroupClearance` — the function the
|
|
@@ -248,5 +291,17 @@ export declare class ToolCallInterceptor {
|
|
|
248
291
|
* @returns Decision: allow, block, or redact
|
|
249
292
|
*/
|
|
250
293
|
decide(toolName: string, toolInput: unknown): Promise<Decision>;
|
|
294
|
+
/**
|
|
295
|
+
* Counts the call against every budget that covers it, or refuses it when
|
|
296
|
+
* one is used up. The threshold and exceeded events go out once per budget
|
|
297
|
+
* per period (the store claims them), each as a `budget_breach` finding; a
|
|
298
|
+
* refusal also sends `tool_blocked`, as every refusal does, carrying the
|
|
299
|
+
* budget — the control plane files one incident per budget per period from
|
|
300
|
+
* those and counts the rest on it.
|
|
301
|
+
*
|
|
302
|
+
* When the count cannot be checked, the fail setting decides, as for every
|
|
303
|
+
* other governance check that cannot complete.
|
|
304
|
+
*/
|
|
305
|
+
private checkBudgets;
|
|
251
306
|
}
|
|
252
307
|
//# sourceMappingURL=interceptor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
|
package/dist/interceptor.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* 0. The MCP server registry, server/tool allowlists and SSO group clearance
|
|
6
6
|
* 1. DLP scan (credential / destructive pattern detection)
|
|
7
7
|
* 2. SOP policy rules (fetched from control plane via PolicyClient)
|
|
8
|
+
* 3–5. Prompt injection, anomaly detectors, WASM rules
|
|
9
|
+
* 6. MCP call budgets — last, so only a call that would otherwise run is counted
|
|
8
10
|
*
|
|
9
11
|
* Returns an allow / block / redact decision.
|
|
10
12
|
*
|
|
@@ -12,13 +14,22 @@
|
|
|
12
14
|
*/
|
|
13
15
|
import * as node_crypto from 'node:crypto';
|
|
14
16
|
import { createStderrLogger as createLogger } from './stderrLog.js';
|
|
15
|
-
import { evaluateSsoGroupClearance } from '@intutic/shared-types';
|
|
16
|
-
import { scanToolInput, formatDlpBlockReason, setDynamicPatterns } from './dlp.js';
|
|
17
|
+
import { evaluateMcpRegistry, evaluateSsoGroupClearance, holdApprovalHint } from '@intutic/shared-types';
|
|
18
|
+
import { scanToolInput, formatDlpBlockReason, setDynamicPatterns, setWorkspacePii } from './dlp.js';
|
|
17
19
|
import { scanText, injectionSeverity, setDynamicInjectionPatterns } from './injection.js';
|
|
18
20
|
import { evaluateSequenceDetectors, resolveEffectiveDisposition, REASK_MAX_ATTEMPTS } from './anomaly/index.js';
|
|
19
21
|
import { SessionState } from './session.js';
|
|
20
22
|
import { detectionFinding } from './emitter.js';
|
|
23
|
+
import { budgetEventDetail, exceededReason, unavailableReason, warningReason } from './budget.js';
|
|
21
24
|
const log = createLogger('mcp-proxy-interceptor');
|
|
25
|
+
/** The decision for a refusal. */
|
|
26
|
+
function block(code, ruleId, reason, detail) {
|
|
27
|
+
return detail ? { action: 'block', reason, code, ruleId, detail } : { action: 'block', reason, code, ruleId };
|
|
28
|
+
}
|
|
29
|
+
/** Every governance check that cannot complete refuses with this, when the proxy fails closed. */
|
|
30
|
+
const FAIL_CLOSED_REASON = 'Governance check failed — Intutic control plane unreachable. ' +
|
|
31
|
+
'Tool call blocked by workspace policy (fail-closed mode). ' +
|
|
32
|
+
'Contact your administrator or update mcpProxyFailBehavior to open.';
|
|
22
33
|
export class ToolCallInterceptor {
|
|
23
34
|
policy;
|
|
24
35
|
emitter;
|
|
@@ -31,6 +42,7 @@ export class ToolCallInterceptor {
|
|
|
31
42
|
wasmRunner;
|
|
32
43
|
workspaceId;
|
|
33
44
|
holds;
|
|
45
|
+
budgets;
|
|
34
46
|
constructor(policy, emitter,
|
|
35
47
|
/**
|
|
36
48
|
* The local fail setting (`INTUTIC_MCP_FAIL_OPEN`, config.ts). The
|
|
@@ -103,7 +115,14 @@ export class ToolCallInterceptor {
|
|
|
103
115
|
* holds) leaves such a call held with nothing recorded, which is still a
|
|
104
116
|
* refusal — never an allow.
|
|
105
117
|
*/
|
|
106
|
-
holds = undefined
|
|
118
|
+
holds = undefined,
|
|
119
|
+
/**
|
|
120
|
+
* Counts each call against the workspace's MCP call budgets (budget.ts).
|
|
121
|
+
* `undefined` (construction sites that predate budgets) skips them; the
|
|
122
|
+
* proxy always passes one, with no store when it has no Valkey, so a
|
|
123
|
+
* covered call then resolves through the fail setting.
|
|
124
|
+
*/
|
|
125
|
+
budgets = undefined) {
|
|
107
126
|
this.policy = policy;
|
|
108
127
|
this.emitter = emitter;
|
|
109
128
|
this.localFailOpen = localFailOpen;
|
|
@@ -115,7 +134,10 @@ export class ToolCallInterceptor {
|
|
|
115
134
|
this.wasmRunner = wasmRunner;
|
|
116
135
|
this.workspaceId = workspaceId;
|
|
117
136
|
this.holds = holds;
|
|
137
|
+
this.budgets = budgets;
|
|
118
138
|
}
|
|
139
|
+
/** Set once this process has said that budgets went unchecked under fail-open, so the log says it once. */
|
|
140
|
+
budgetsUncheckedWarned = false;
|
|
119
141
|
/**
|
|
120
142
|
* Whether a governance check that cannot complete lets the call through:
|
|
121
143
|
* the workspace's `mcpProxyFailBehavior` when the control plane has sent
|
|
@@ -142,19 +164,19 @@ export class ToolCallInterceptor {
|
|
|
142
164
|
`attempts with no correction.`;
|
|
143
165
|
log.warn({ action: 'reask_hardened', toolName, key, attempts }, 'Reask attempts exhausted — hardening to a hard block');
|
|
144
166
|
this.emitter.emit('tool_blocked', toolName, toolInput, hardenedReason);
|
|
145
|
-
return
|
|
167
|
+
return block('REASK_EXHAUSTED', key, hardenedReason);
|
|
146
168
|
}
|
|
147
169
|
const reaskReason = `${baseReason} (attempt ${attempts}/${REASK_MAX_ATTEMPTS} — will become an unconditional ` +
|
|
148
170
|
`block if this keeps tripping)`;
|
|
149
171
|
this.emitter.emit('tool_blocked', toolName, toolInput, reaskReason);
|
|
150
|
-
return
|
|
172
|
+
return block('REASK', key, reaskReason);
|
|
151
173
|
}
|
|
152
174
|
/**
|
|
153
175
|
* A `require_approval` rule matched. Returns the hold decision, or `null`
|
|
154
176
|
* when an approved bypass for this exact call lets it continue. Every hold
|
|
155
|
-
* reason names the hold id and
|
|
156
|
-
*
|
|
157
|
-
* knows what to
|
|
177
|
+
* reason names the hold id, who may approve it and when a retry passes, in
|
|
178
|
+
* the words the hook gates print (`holdApprovalHint`), so a person reading
|
|
179
|
+
* the agent's transcript knows what to do.
|
|
158
180
|
*/
|
|
159
181
|
async hold(rule, toolName, toolInput) {
|
|
160
182
|
const outcome = this.holds
|
|
@@ -168,14 +190,13 @@ export class ToolCallInterceptor {
|
|
|
168
190
|
}
|
|
169
191
|
const reason = outcome.recorded
|
|
170
192
|
? `HELD for approval: ${rule.reason} [${rule.id}]. Hold id: ${outcome.holdId}. ` +
|
|
171
|
-
|
|
172
|
-
`Retry this exact call after it is approved.`
|
|
193
|
+
holdApprovalHint(outcome.holdId)
|
|
173
194
|
: `HELD for approval: ${rule.reason} [${rule.id}], but the hold could not be recorded ` +
|
|
174
195
|
`(Intutic control plane unreachable), so there is nothing to approve yet. Retry once the ` +
|
|
175
196
|
`control plane is reachable to request approval.`;
|
|
176
197
|
log.warn({ action: 'tool_held', toolName, ruleId: rule.id, holdId: outcome.holdId, recorded: outcome.recorded }, reason);
|
|
177
198
|
this.emitter.emit('tool_held', toolName, toolInput, `${rule.reason} [${rule.id}]`);
|
|
178
|
-
return { action: 'hold', reason, holdId: outcome.holdId };
|
|
199
|
+
return { action: 'hold', reason, holdId: outcome.recorded ? outcome.holdId : '', ruleId: rule.id };
|
|
179
200
|
}
|
|
180
201
|
/**
|
|
181
202
|
* Refuses the call when the registry says this server — or this tool on it —
|
|
@@ -205,30 +226,40 @@ export class ToolCallInterceptor {
|
|
|
205
226
|
`Tool call blocked (fail-closed mode: mcpProxyFailBehavior or INTUTIC_MCP_FAIL_OPEN=false).`;
|
|
206
227
|
log.warn({ action: 'registry_unknown_block', serverName: this.serverName, toolName }, reason);
|
|
207
228
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
let reason = null;
|
|
211
|
-
if (registry.blockedServers.includes(this.serverName)) {
|
|
212
|
-
reason =
|
|
213
|
-
`MCP server "${this.serverName}" is blocked in this workspace's MCP server registry. ` +
|
|
214
|
-
`An owner or admin can change that on the MCP Servers page.`;
|
|
215
|
-
}
|
|
216
|
-
else if (registry.defaultPolicy === 'deny' && !registry.approvedServers.includes(this.serverName)) {
|
|
217
|
-
reason =
|
|
218
|
-
`MCP server "${this.serverName}" is not approved in this workspace's MCP server registry, ` +
|
|
219
|
-
`and the workspace refuses unapproved servers (mcpDefaultPolicy: deny). It is waiting in ` +
|
|
220
|
-
`the approval queue on the MCP Servers page for an owner or admin.`;
|
|
229
|
+
return block('REGISTRY_UNAVAILABLE', 'mcpProxyFailBehavior', reason);
|
|
221
230
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (!
|
|
231
|
+
// The decision every gate makes (`@intutic/shared-types` mcpRegistryRecord.ts):
|
|
232
|
+
// the harness hook gates apply the same function, from the policy
|
|
233
|
+
// snapshot, to the servers no proxy fronts.
|
|
234
|
+
const decision = evaluateMcpRegistry(registry, this.serverName, toolName);
|
|
235
|
+
const refusal = decision ? block(decision.code, decision.ruleId, decision.reason) : null;
|
|
236
|
+
if (!refusal)
|
|
228
237
|
return null;
|
|
229
|
-
log.warn({ action: 'registry_block', serverName: this.serverName, toolName }, reason);
|
|
238
|
+
log.warn({ action: 'registry_block', serverName: this.serverName, toolName }, refusal.reason);
|
|
239
|
+
this.emitter.emit('tool_blocked', toolName, toolInput, refusal.reason);
|
|
240
|
+
return refusal;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Brings the workspace's control-plane rules up to date with the policy, and
|
|
244
|
+
* refuses the call when they have never loaded and the proxy fails closed.
|
|
245
|
+
*
|
|
246
|
+
* Never loaded means no policy from the control plane yet, or one whose rule
|
|
247
|
+
* binaries could not be fetched: whether one of those rules refuses this
|
|
248
|
+
* call is unknown. Fail-open judges the call by the rules that are loaded —
|
|
249
|
+
* this machine's — as a workspace with no uploaded rules would be;
|
|
250
|
+
* fail-closed refuses it, as it does an unknown registry. Once loaded, the
|
|
251
|
+
* set is kept for as long as the process runs, through any later outage.
|
|
252
|
+
*/
|
|
253
|
+
async checkCloudRulesLoaded(runner, toolName, toolInput) {
|
|
254
|
+
await runner.syncCloudRules(this.policy.getWasmRules());
|
|
255
|
+
if (runner.cloudRulesLoaded() || this.failOpen)
|
|
256
|
+
return null;
|
|
257
|
+
const reason = `This workspace's custom rules have not loaded (Intutic control plane unreachable since this ` +
|
|
258
|
+
`proxy started), so whether one of them refuses this call is unknown. Tool call blocked ` +
|
|
259
|
+
`(fail-closed mode: mcpProxyFailBehavior or INTUTIC_MCP_FAIL_OPEN=false).`;
|
|
260
|
+
log.warn({ action: 'wasm_cloud_unknown_block', toolName }, reason);
|
|
230
261
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
231
|
-
return
|
|
262
|
+
return block('GOVERNANCE_UNAVAILABLE', 'mcpProxyFailBehavior', reason);
|
|
232
263
|
}
|
|
233
264
|
/**
|
|
234
265
|
* The workspace's SSO group policy, applied to the member this proxy's API
|
|
@@ -256,7 +287,7 @@ export class ToolCallInterceptor {
|
|
|
256
287
|
const reason = `${decision.reason} [${decision.ruleId}]`;
|
|
257
288
|
log.warn({ action: 'sso_group_block', toolName, memberId: principal?.memberId ?? null, ruleId: decision.ruleId }, reason);
|
|
258
289
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
259
|
-
return
|
|
290
|
+
return block('SSO_GROUP', decision.ruleId ?? 'sso_group', reason);
|
|
260
291
|
}
|
|
261
292
|
/**
|
|
262
293
|
* Evaluate a tools/call request and return a governance decision.
|
|
@@ -299,7 +330,7 @@ export class ToolCallInterceptor {
|
|
|
299
330
|
`allowlist in workspace settings (mcpAllowedServers).`;
|
|
300
331
|
log.warn({ action: 'server_allowlist_block', serverName: this.serverName, toolName }, reason);
|
|
301
332
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
302
|
-
return
|
|
333
|
+
return block('SERVER_NOT_ALLOWED', 'mcpAllowedServers', reason);
|
|
303
334
|
}
|
|
304
335
|
// 0. Additive tool scoping. When the workspace declares an allowlist,
|
|
305
336
|
// ONLY those tools may be called — the inverse of every other rule here,
|
|
@@ -314,7 +345,7 @@ export class ToolCallInterceptor {
|
|
|
314
345
|
`allowlist in workspace settings (mcpAllowedTools).`;
|
|
315
346
|
log.warn({ action: 'allowlist_block', toolName }, reason);
|
|
316
347
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
317
|
-
return
|
|
348
|
+
return block('TOOL_NOT_ALLOWED', 'mcpAllowedTools', reason);
|
|
318
349
|
}
|
|
319
350
|
// 0.5. SSO group clearance, after the scoping checks and before DLP.
|
|
320
351
|
const clearance = this.checkSsoGroupClearance(toolName, toolInput);
|
|
@@ -328,6 +359,21 @@ export class ToolCallInterceptor {
|
|
|
328
359
|
catch {
|
|
329
360
|
// Pattern delivery must never take the scanner down; the floor stands.
|
|
330
361
|
}
|
|
362
|
+
// The workspace's PII detector actions, the baseline this machine's
|
|
363
|
+
// INTUTIC_MCP_DLP_DETECTORS may only tighten. When the control plane
|
|
364
|
+
// could not read them, which detectors the workspace requires is
|
|
365
|
+
// unknown: fail-closed refuses the call, as the LLM proxy refuses the
|
|
366
|
+
// request; fail-open scans with the local config alone.
|
|
367
|
+
const piiDetectors = this.policy.getPiiDetectors();
|
|
368
|
+
if (piiDetectors.kind === 'unreadable' && !this.failOpen) {
|
|
369
|
+
const reason = `This workspace's PII detector actions could not be read (${piiDetectors.reason}), so which ` +
|
|
370
|
+
`detectors apply is unknown. Tool call blocked (fail-closed mode: mcpProxyFailBehavior or ` +
|
|
371
|
+
`INTUTIC_MCP_FAIL_OPEN=false).`;
|
|
372
|
+
log.warn({ action: 'pii_detectors_unreadable_block', toolName }, reason);
|
|
373
|
+
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
374
|
+
return block('GOVERNANCE_UNAVAILABLE', 'piiDetectors', reason);
|
|
375
|
+
}
|
|
376
|
+
setWorkspacePii(piiDetectors);
|
|
331
377
|
try {
|
|
332
378
|
const dlp = scanToolInput(toolInput);
|
|
333
379
|
// Captured regardless of outcome — by pipeline position, a non-empty
|
|
@@ -339,19 +385,13 @@ export class ToolCallInterceptor {
|
|
|
339
385
|
const reason = formatDlpBlockReason(dlp.findings);
|
|
340
386
|
log.warn({ action: 'dlp_block', toolName, findings: dlp.findings }, 'DLP block');
|
|
341
387
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
342
|
-
return {
|
|
388
|
+
return block('DLP', `dlp.${dlp.findings[0]?.pattern ?? 'input'}`, reason);
|
|
343
389
|
}
|
|
344
390
|
}
|
|
345
391
|
catch (err) {
|
|
346
392
|
log.error({ action: 'dlp_error', err: err.message }, 'DLP scan error — skipping');
|
|
347
|
-
if (!this.failOpen)
|
|
348
|
-
return
|
|
349
|
-
action: 'block',
|
|
350
|
-
reason: 'Governance check failed — Intutic control plane unreachable. ' +
|
|
351
|
-
'Tool call blocked by workspace policy (fail-closed mode). ' +
|
|
352
|
-
'Contact your administrator or update mcpProxyFailBehavior to open.',
|
|
353
|
-
};
|
|
354
|
-
}
|
|
393
|
+
if (!this.failOpen)
|
|
394
|
+
return block('GOVERNANCE_UNAVAILABLE', 'mcpProxyFailBehavior', FAIL_CLOSED_REASON);
|
|
355
395
|
}
|
|
356
396
|
// 2. SOP policy rule match
|
|
357
397
|
try {
|
|
@@ -361,13 +401,13 @@ export class ToolCallInterceptor {
|
|
|
361
401
|
if (rule.action === 'block') {
|
|
362
402
|
log.warn({ action: 'policy_block', toolName, ruleId: rule.id, reason: rule.reason }, 'Policy block');
|
|
363
403
|
this.emitter.emit('tool_blocked', toolName, toolInput, rule.reason);
|
|
364
|
-
return
|
|
404
|
+
return block('SOP_RULE', rule.id, rule.reason);
|
|
365
405
|
}
|
|
366
406
|
if (rule.action === 'warn') {
|
|
367
407
|
log.warn({ action: 'policy_warn', toolName, ruleId: rule.id, reason: rule.reason }, 'Policy warning (allowing)');
|
|
368
408
|
// Fall through to allow. Reported as `tool_flagged` with the rule id
|
|
369
409
|
// in the reason — the same shape the harness gates use — so a
|
|
370
|
-
// SHADOW guardrail's evidence
|
|
410
|
+
// SHADOW guardrail's evidence counts this proxy's traffic.
|
|
371
411
|
this.emitter.emit('tool_flagged', toolName, toolInput, `${rule.reason} [${rule.id}]`);
|
|
372
412
|
}
|
|
373
413
|
if (rule.action === 'require_approval') {
|
|
@@ -380,14 +420,8 @@ export class ToolCallInterceptor {
|
|
|
380
420
|
}
|
|
381
421
|
catch (err) {
|
|
382
422
|
log.error({ action: 'policy_error', err: err.message }, 'Policy evaluation error');
|
|
383
|
-
if (!this.failOpen)
|
|
384
|
-
return
|
|
385
|
-
action: 'block',
|
|
386
|
-
reason: 'Governance check failed — Intutic control plane unreachable. ' +
|
|
387
|
-
'Tool call blocked by workspace policy (fail-closed mode). ' +
|
|
388
|
-
'Contact your administrator or update mcpProxyFailBehavior to open.',
|
|
389
|
-
};
|
|
390
|
-
}
|
|
423
|
+
if (!this.failOpen)
|
|
424
|
+
return block('GOVERNANCE_UNAVAILABLE', 'mcpProxyFailBehavior', FAIL_CLOSED_REASON);
|
|
391
425
|
}
|
|
392
426
|
// 3. Prompt-injection scan (request direction) — the `toolInput` a
|
|
393
427
|
// MISBEHAVING or COMPROMISED calling agent sends, e.g. an agent that
|
|
@@ -416,7 +450,7 @@ export class ToolCallInterceptor {
|
|
|
416
450
|
});
|
|
417
451
|
if (injectionAction === 'block') {
|
|
418
452
|
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
419
|
-
return
|
|
453
|
+
return block('INJECTION', 'injection.tool_input', reason);
|
|
420
454
|
}
|
|
421
455
|
// 'warn' (default): report only, fall through to allow.
|
|
422
456
|
}
|
|
@@ -441,7 +475,7 @@ export class ToolCallInterceptor {
|
|
|
441
475
|
// built) and allow.
|
|
442
476
|
const anomalyMode = this.policy.getAnomalyMode() ?? this.anomalyModeDefault;
|
|
443
477
|
// The session window — the shared one across this session's sibling
|
|
444
|
-
// proxy processes when a store is configured (Wave 5.3
|
|
478
|
+
// proxy processes when a store is configured (Wave 5.3), the
|
|
445
479
|
// per-process one otherwise. One round trip at most per tools/call, and
|
|
446
480
|
// none when nothing below would read it.
|
|
447
481
|
const window = anomalyMode !== 'off' || this.wasmRunner ? await this.session.loadWindow(toolName) : undefined;
|
|
@@ -467,7 +501,7 @@ export class ToolCallInterceptor {
|
|
|
467
501
|
this.emitter.emit('anomaly_detected', toolName, toolInput, finding.reason, detectionFinding(finding, effective, severity));
|
|
468
502
|
if (effective === 'kill') {
|
|
469
503
|
this.emitter.emit('tool_blocked', toolName, toolInput, finding.reason);
|
|
470
|
-
return
|
|
504
|
+
return block('ANOMALY', finding.detectorId, finding.reason);
|
|
471
505
|
}
|
|
472
506
|
if (effective === 'reask') {
|
|
473
507
|
// Keyed per-detector-id, independent of every other detector's
|
|
@@ -488,14 +522,19 @@ export class ToolCallInterceptor {
|
|
|
488
522
|
log.error({ action: 'anomaly_detection_error', err: err.message }, 'Anomaly detection error — skipping');
|
|
489
523
|
}
|
|
490
524
|
}
|
|
491
|
-
// 5. WASM custom rules (Phase 3) — operator-authored,
|
|
492
|
-
//
|
|
493
|
-
//
|
|
494
|
-
//
|
|
525
|
+
// 5. WASM custom rules (Phase 3) — operator-authored rules, native or
|
|
526
|
+
// Rego: the workspace's, uploaded to the control plane, and this
|
|
527
|
+
// machine's, dropped into `~/.intutic/wasm/`. Position: after every
|
|
528
|
+
// built-in check, immediately before allow — this is deliberately the
|
|
529
|
+
// LAST gate, so a WASM rule's `RequestContext` carries
|
|
495
530
|
// `injection_findings`/`corroborating_detectors`/etc. already populated
|
|
496
531
|
// by the steps above it (see `wasm/context.ts`'s module doc).
|
|
497
532
|
if (this.wasmRunner) {
|
|
533
|
+
const unloaded = await this.checkCloudRulesLoaded(this.wasmRunner, toolName, toolInput);
|
|
534
|
+
if (unloaded)
|
|
535
|
+
return unloaded;
|
|
498
536
|
try {
|
|
537
|
+
const shadow = [];
|
|
499
538
|
const verdict = await this.wasmRunner.evaluate({
|
|
500
539
|
sessionId: this.session.sessionId,
|
|
501
540
|
workspaceId: this.workspaceId,
|
|
@@ -510,30 +549,112 @@ export class ToolCallInterceptor {
|
|
|
510
549
|
injectionSources: injectionSourcesForContext,
|
|
511
550
|
corroboratingDetectors: corroboratingDetectorsForContext,
|
|
512
551
|
toolContractChanged: this.session.getToolContractChanged(),
|
|
513
|
-
|
|
552
|
+
serverName: this.serverName,
|
|
553
|
+
}, shadow);
|
|
554
|
+
// Whatever the call's outcome: the shadowed rules' evaluations are
|
|
555
|
+
// promotion evidence whether or not another rule refused the call.
|
|
556
|
+
if (shadow.length > 0) {
|
|
557
|
+
this.emitter.emit('wasm_shadow_evaluated', toolName, toolInput, undefined, undefined, undefined, { wasmShadowReports: shadow });
|
|
558
|
+
}
|
|
559
|
+
if (verdict.code === 'unavailable') {
|
|
560
|
+
// A rule reached no verdict: refused whatever `failOpen` says (see
|
|
561
|
+
// `WasmRunner.evaluate`). Reported like any refusal, except a
|
|
562
|
+
// quarantined rule's: the call that quarantined it was reported
|
|
563
|
+
// already, and one event per refused call after it would file an
|
|
564
|
+
// incident per retry.
|
|
565
|
+
log.warn({ action: 'wasm_unavailable_block', toolName, ruleId: verdict.ruleId, stop: verdict.stop }, verdict.reason);
|
|
566
|
+
if (verdict.stop !== 'quarantined')
|
|
567
|
+
this.emitter.emit('tool_blocked', toolName, toolInput, verdict.reason);
|
|
568
|
+
return block('GOVERNANCE_UNAVAILABLE', `wasm:${verdict.ruleId}`, verdict.reason);
|
|
569
|
+
}
|
|
514
570
|
if (verdict.code === 'block') {
|
|
515
571
|
log.warn({ action: 'wasm_block', toolName, ruleId: verdict.ruleId }, 'Tool call blocked by WASM governance rule');
|
|
516
572
|
this.emitter.emit('tool_blocked', toolName, toolInput, verdict.reason);
|
|
517
|
-
return
|
|
573
|
+
return block('WASM_RULE', `wasm:${verdict.ruleId}`, verdict.reason);
|
|
518
574
|
}
|
|
519
575
|
if (verdict.code === 'reask') {
|
|
520
576
|
// Keyed per-rule-id, independent of every anomaly detector's own
|
|
521
577
|
// counter — see applyReaskLadder's doc comment.
|
|
522
578
|
return await this.applyReaskLadder(`wasm:${verdict.ruleId}`, verdict.reason, toolName, toolInput);
|
|
523
579
|
}
|
|
580
|
+
if (verdict.code === 'hold') {
|
|
581
|
+
// A Rego rule's hold takes the `require_approval` path: the same
|
|
582
|
+
// approved-bypass lookup and hold record, keyed on the rule id.
|
|
583
|
+
const held = await this.hold({ id: verdict.ruleId, reason: verdict.reason }, toolName, toolInput);
|
|
584
|
+
if (held)
|
|
585
|
+
return held;
|
|
586
|
+
// An approved bypass: the remaining checks still apply.
|
|
587
|
+
}
|
|
524
588
|
// 'allow': fall through.
|
|
525
589
|
}
|
|
526
590
|
catch (err) {
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
// rule
|
|
530
|
-
//
|
|
531
|
-
log.error({ action: 'wasm_evaluate_error', err: err.message }, 'WASM rule evaluation error
|
|
591
|
+
// The runner itself failed (not one rule's timeout or trap, which
|
|
592
|
+
// `WasmRunner.evaluate` turns into a verdict): no rule judged the
|
|
593
|
+
// call, so it is refused like a rule that reached no verdict, whatever
|
|
594
|
+
// the fail setting.
|
|
595
|
+
log.error({ action: 'wasm_evaluate_error', err: err.message }, 'WASM rule evaluation error');
|
|
596
|
+
return block('GOVERNANCE_UNAVAILABLE', 'wasm', `Custom rules could not be evaluated: ${err.message}. Tool call blocked: a rule ` +
|
|
597
|
+
`that cannot decide never allows.`);
|
|
532
598
|
}
|
|
533
599
|
}
|
|
534
|
-
// 6.
|
|
600
|
+
// 6. MCP call budgets — after every other check, so a call another check
|
|
601
|
+
// refuses never spends allowance.
|
|
602
|
+
const budgetDecision = await this.checkBudgets(toolName, toolInput);
|
|
603
|
+
if (budgetDecision)
|
|
604
|
+
return budgetDecision;
|
|
605
|
+
// 7. Allow — emit telemetry event
|
|
535
606
|
this.emitter.emit('tool_allowed', toolName, toolInput);
|
|
536
607
|
return { action: 'allow' };
|
|
537
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Counts the call against every budget that covers it, or refuses it when
|
|
611
|
+
* one is used up. The threshold and exceeded events go out once per budget
|
|
612
|
+
* per period (the store claims them), each as a `budget_breach` finding; a
|
|
613
|
+
* refusal also sends `tool_blocked`, as every refusal does, carrying the
|
|
614
|
+
* budget — the control plane files one incident per budget per period from
|
|
615
|
+
* those and counts the rest on it.
|
|
616
|
+
*
|
|
617
|
+
* When the count cannot be checked, the fail setting decides, as for every
|
|
618
|
+
* other governance check that cannot complete.
|
|
619
|
+
*/
|
|
620
|
+
async checkBudgets(toolName, toolInput) {
|
|
621
|
+
if (!this.budgets)
|
|
622
|
+
return null;
|
|
623
|
+
const policy = this.policy.getMcpBudgets();
|
|
624
|
+
const verdict = await this.budgets.check(policy, toolName, this.policy.getPrincipal()?.memberId ?? null);
|
|
625
|
+
if (verdict.kind === 'unlimited')
|
|
626
|
+
return null;
|
|
627
|
+
if (verdict.kind === 'allowed') {
|
|
628
|
+
for (const standing of verdict.warnings) {
|
|
629
|
+
const reason = warningReason(standing, policy.warnAtPct);
|
|
630
|
+
log.warn({ action: 'mcp_budget_threshold', toolName, budgetId: standing.budget.id, used: standing.used }, reason);
|
|
631
|
+
this.emitter.emit('mcp_budget_threshold', toolName, undefined, reason, { detectorId: 'budget', kind: 'budget_breach', disposition: 'steer', severity: 'low', confidence: 1 }, budgetEventDetail(standing));
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
if (verdict.kind === 'exceeded') {
|
|
636
|
+
const reason = exceededReason(verdict.standing);
|
|
637
|
+
log.warn({ action: 'mcp_budget_exceeded', toolName, budgetId: verdict.standing.budget.id, notify: verdict.notify }, reason);
|
|
638
|
+
if (verdict.notify) {
|
|
639
|
+
this.emitter.emit('mcp_budget_exceeded', toolName, undefined, reason, { detectorId: 'budget', kind: 'budget_breach', disposition: 'kill', severity: 'medium', confidence: 1 }, budgetEventDetail(verdict.standing));
|
|
640
|
+
}
|
|
641
|
+
const detail = budgetEventDetail(verdict.standing);
|
|
642
|
+
this.emitter.emit('tool_blocked', toolName, toolInput, reason, undefined, detail);
|
|
643
|
+
return block('BUDGET_EXCEEDED', detail.budgetId, reason, { ...detail });
|
|
644
|
+
}
|
|
645
|
+
// Unavailable: no Valkey configured, or it did not answer.
|
|
646
|
+
if (this.failOpen) {
|
|
647
|
+
if (!this.budgetsUncheckedWarned) {
|
|
648
|
+
this.budgetsUncheckedWarned = true;
|
|
649
|
+
log.warn({ action: 'mcp_budget_unchecked', budgets: verdict.budgets.map((b) => b.id) }, 'MCP call budgets cover calls through this proxy but could not be checked (no Valkey, or unreachable); ' +
|
|
650
|
+
'allowing them uncounted (fail-open)');
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
const reason = unavailableReason(verdict.budgets);
|
|
655
|
+
log.warn({ action: 'mcp_budget_unchecked_block', toolName }, reason);
|
|
656
|
+
this.emitter.emit('tool_blocked', toolName, toolInput, reason);
|
|
657
|
+
return block('BUDGET_UNAVAILABLE', 'mcpProxyFailBehavior', reason, { budgetIds: verdict.budgets.map((b) => b.id) });
|
|
658
|
+
}
|
|
538
659
|
}
|
|
539
660
|
//# sourceMappingURL=interceptor.js.map
|