@panguard-ai/panguard-mcp-proxy 1.8.9 → 1.8.12
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/dist/evaluator.d.ts +8 -2
- package/dist/evaluator.js +30 -5
- package/package.json +4 -4
package/dist/evaluator.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ interface RuleLike {
|
|
|
38
38
|
readonly severity: string;
|
|
39
39
|
readonly maturity?: string;
|
|
40
40
|
readonly confirm?: string;
|
|
41
|
+
/** ATR scan_target lives on the parsed rule under tags (rich vocab: mcp /
|
|
42
|
+
* tool_args / skill / host / code / any / llm_io / ...). Used by the built-in-
|
|
43
|
+
* tool surface gate to skip MCP-argument rules on a native shell command. */
|
|
44
|
+
readonly tags?: {
|
|
45
|
+
readonly scan_target?: string;
|
|
46
|
+
};
|
|
41
47
|
}
|
|
42
48
|
/**
|
|
43
49
|
* Whether a single rule match is strong enough to HARD-DENY a live tool call
|
|
@@ -56,7 +62,7 @@ interface RuleLike {
|
|
|
56
62
|
* Pure + exported so the policy is unit-tested independently of which live rule
|
|
57
63
|
* happens to match (the rule corpus changes daily; this policy must not).
|
|
58
64
|
*/
|
|
59
|
-
export declare function shouldHardDeny(rule: RuleLike): boolean;
|
|
65
|
+
export declare function shouldHardDeny(rule: RuleLike, builtinToolSurface?: boolean): boolean;
|
|
60
66
|
export declare class ProxyEvaluator {
|
|
61
67
|
private readonly engine;
|
|
62
68
|
private rulesLoaded;
|
|
@@ -109,7 +115,7 @@ export declare class ProxyEvaluator {
|
|
|
109
115
|
* The old hardcoded 'mcp_exchange' both missed the tool_call attack rules
|
|
110
116
|
* and false-fired multi_agent_comm rules on ordinary commands.
|
|
111
117
|
*/
|
|
112
|
-
evaluateToolCall(toolName: string, args: Record<string, unknown>, eventType?: AgentEvent['type']): Promise<EvalResult>;
|
|
118
|
+
evaluateToolCall(toolName: string, args: Record<string, unknown>, eventType?: AgentEvent['type'], builtinToolSurface?: boolean): Promise<EvalResult>;
|
|
113
119
|
/**
|
|
114
120
|
* Evaluate a tool response (PostToolUse) — the indirect-prompt-injection path.
|
|
115
121
|
*
|
package/dist/evaluator.js
CHANGED
|
@@ -55,9 +55,28 @@ export const EVALUATION_ERROR_SENTINEL = 'evaluation-error';
|
|
|
55
55
|
* Pure + exported so the policy is unit-tested independently of which live rule
|
|
56
56
|
* happens to match (the rule corpus changes daily; this policy must not).
|
|
57
57
|
*/
|
|
58
|
-
export function shouldHardDeny(rule) {
|
|
58
|
+
export function shouldHardDeny(rule, builtinToolSurface = false) {
|
|
59
59
|
if (rule.confirm === 'embedding')
|
|
60
60
|
return false;
|
|
61
|
+
if (builtinToolSurface && rule.tags?.scan_target === 'mcp') {
|
|
62
|
+
// FALSE-POSITIVE gate for the built-in-tool hook, which guards the agent's
|
|
63
|
+
// OWN native shell (Bash/Edit/Write/WebFetch). Rules scoped scan_target:mcp
|
|
64
|
+
// are MCP-tool-ARGUMENT rules — e.g. ATR-2026-00111 "Shell Metacharacter
|
|
65
|
+
// Injection in Tool Arguments" / ATR-2026-00066 "Parameter Injection" — that
|
|
66
|
+
// key on shell metacharacters (`;` `|` `$()`). Inside an MCP argument those
|
|
67
|
+
// are anomalous and rightly deny; but they are the NORMAL grammar of a real
|
|
68
|
+
// shell, so matched against the agent's own `echo x; curl localhost` they
|
|
69
|
+
// false-block legitimate work (they only reach a tool_call event at all via
|
|
70
|
+
// the engine's mcp-over-tool exception). On this surface an mcp-scoped rule
|
|
71
|
+
// therefore DEGRADES to an 'ask' advisory — the caller warns, never bricks
|
|
72
|
+
// the agent. Semantic exfil/RCE rules scoped to the shell's real domain
|
|
73
|
+
// (tool_args / skill / host / code / any) still hard-block, so credential
|
|
74
|
+
// exfil (`cat ~/.ssh/id_rsa | curl`, `env | curl`) is caught regardless of
|
|
75
|
+
// this gate. The MCP proxy leaves builtinToolSurface=false: on a genuine MCP
|
|
76
|
+
// ARGUMENT, `; curl` really is injection. This keys on scan_target (a rule's
|
|
77
|
+
// intrinsic scope), NOT maturity — so it never drifts with the daily corpus.
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
61
80
|
if (rule.severity === 'critical')
|
|
62
81
|
return true;
|
|
63
82
|
return rule.severity === 'high' && rule.maturity === 'stable';
|
|
@@ -192,7 +211,13 @@ export class ProxyEvaluator {
|
|
|
192
211
|
* The old hardcoded 'mcp_exchange' both missed the tool_call attack rules
|
|
193
212
|
* and false-fired multi_agent_comm rules on ordinary commands.
|
|
194
213
|
*/
|
|
195
|
-
async evaluateToolCall(toolName, args, eventType = 'mcp_exchange'
|
|
214
|
+
async evaluateToolCall(toolName, args, eventType = 'mcp_exchange',
|
|
215
|
+
// The built-in-tool hook (guarding the agent's OWN Bash/Edit/Write) passes
|
|
216
|
+
// true: on that high-FP surface an MCP-argument rule (scan_target:mcp shell-
|
|
217
|
+
// metacharacter rule) advises instead of hard-blocking a real shell command.
|
|
218
|
+
// The MCP proxy leaves it false (default) — on a genuine MCP argument those
|
|
219
|
+
// rules correctly deny.
|
|
220
|
+
builtinToolSurface = false) {
|
|
196
221
|
const start = Date.now();
|
|
197
222
|
// Check Guard blocklist first (instant deny, no regex needed)
|
|
198
223
|
this.refreshBlocklist();
|
|
@@ -217,7 +242,7 @@ export class ProxyEvaluator {
|
|
|
217
242
|
tool_input: flatContent,
|
|
218
243
|
},
|
|
219
244
|
};
|
|
220
|
-
return this.evaluate(event, start);
|
|
245
|
+
return this.evaluate(event, start, builtinToolSurface);
|
|
221
246
|
}
|
|
222
247
|
/**
|
|
223
248
|
* Evaluate a tool response (PostToolUse) — the indirect-prompt-injection path.
|
|
@@ -251,7 +276,7 @@ export class ProxyEvaluator {
|
|
|
251
276
|
};
|
|
252
277
|
return this.evaluate(event, start);
|
|
253
278
|
}
|
|
254
|
-
async evaluate(event, start) {
|
|
279
|
+
async evaluate(event, start, builtinToolSurface = false) {
|
|
255
280
|
try {
|
|
256
281
|
const matches = this.engine.evaluate(event);
|
|
257
282
|
const durationMs = Date.now() - start;
|
|
@@ -284,7 +309,7 @@ export class ProxyEvaluator {
|
|
|
284
309
|
// 'ask' advisory below, but it can never silently block the user's tool.
|
|
285
310
|
// Bundled/trusted rules are unaffected. This is the arm gate for the
|
|
286
311
|
// enforce path.
|
|
287
|
-
const blockMatch = matches.find((m) => shouldHardDeny(m.rule) && !this.adviseOnlyIds.has(m.rule.id));
|
|
312
|
+
const blockMatch = matches.find((m) => shouldHardDeny(m.rule, builtinToolSurface) && !this.adviseOnlyIds.has(m.rule.id));
|
|
288
313
|
const askMatch = blockMatch ??
|
|
289
314
|
matches.find((m) => m.rule.severity === 'high' || m.rule.severity === 'critical');
|
|
290
315
|
const outcome = blockMatch ? 'deny' : askMatch ? 'ask' : 'allow';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panguard-ai/panguard-mcp-proxy",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.12",
|
|
4
4
|
"description": "MCP Proxy — runtime interception for AI agent tool calls using ATR rules",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
23
23
|
"agent-threat-rules": "^3.5.8",
|
|
24
|
-
"@panguard-ai/atr": "1.8.
|
|
24
|
+
"@panguard-ai/atr": "1.8.12",
|
|
25
25
|
"@panguard-ai/containment": "0.1.0",
|
|
26
|
-
"@panguard-ai/panguard-guard": "1.8.
|
|
26
|
+
"@panguard-ai/panguard-guard": "1.8.12"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@panguard-ai/atr": "1.8.
|
|
29
|
+
"@panguard-ai/atr": "1.8.12"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|