@guardbee/mcp-security-proxy 0.3.1 → 1.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 +88 -11
- package/TR.md +88 -11
- package/dist/audit/logger.d.ts +14 -2
- package/dist/audit/logger.d.ts.map +1 -1
- package/dist/audit/logger.js +72 -1
- package/dist/audit/logger.js.map +1 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +65 -3
- package/dist/cli.js.map +1 -1
- package/dist/gateway/approval.d.ts +16 -0
- package/dist/gateway/approval.d.ts.map +1 -0
- package/dist/gateway/approval.js +36 -0
- package/dist/gateway/approval.js.map +1 -0
- package/dist/gateway/config.d.ts +36 -0
- package/dist/gateway/config.d.ts.map +1 -0
- package/dist/gateway/config.js +206 -0
- package/dist/gateway/config.js.map +1 -0
- package/dist/gateway/fields.d.ts +7 -0
- package/dist/gateway/fields.d.ts.map +1 -0
- package/dist/gateway/fields.js +43 -0
- package/dist/gateway/fields.js.map +1 -0
- package/dist/gateway/gateway.d.ts +59 -0
- package/dist/gateway/gateway.d.ts.map +1 -0
- package/dist/gateway/gateway.js +376 -0
- package/dist/gateway/gateway.js.map +1 -0
- package/dist/gateway/labels.d.ts +10 -0
- package/dist/gateway/labels.d.ts.map +1 -0
- package/dist/gateway/labels.js +17 -0
- package/dist/gateway/labels.js.map +1 -0
- package/dist/gateway/policy.d.ts +37 -0
- package/dist/gateway/policy.d.ts.map +1 -0
- package/dist/gateway/policy.js +57 -0
- package/dist/gateway/policy.js.map +1 -0
- package/dist/gateway/taint.d.ts +25 -0
- package/dist/gateway/taint.d.ts.map +1 -0
- package/dist/gateway/taint.js +43 -0
- package/dist/gateway/taint.js.map +1 -0
- package/dist/gateway/tokens.d.ts +18 -0
- package/dist/gateway/tokens.d.ts.map +1 -0
- package/dist/gateway/tokens.js +60 -0
- package/dist/gateway/tokens.js.map +1 -0
- package/dist/gateway/upstream.d.ts +20 -0
- package/dist/gateway/upstream.d.ts.map +1 -0
- package/dist/gateway/upstream.js +49 -0
- package/dist/gateway/upstream.js.map +1 -0
- package/dist/index.d.ts +21 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +34 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +140 -0
- package/dist/init.js.map +1 -0
- package/dist/interceptors/definition-drift.d.ts +1 -0
- package/dist/interceptors/definition-drift.d.ts.map +1 -1
- package/dist/interceptors/definition-drift.js +1 -0
- package/dist/interceptors/definition-drift.js.map +1 -1
- package/dist/interceptors/pii-masker.d.ts +1 -2
- package/dist/interceptors/pii-masker.d.ts.map +1 -1
- package/dist/interceptors/pii-masker.js +5 -68
- package/dist/interceptors/pii-masker.js.map +1 -1
- package/dist/interceptors/prompt-injection.d.ts +1 -2
- package/dist/interceptors/prompt-injection.d.ts.map +1 -1
- package/dist/interceptors/prompt-injection.js +4 -40
- package/dist/interceptors/prompt-injection.js.map +1 -1
- package/dist/interceptors/tool-result.d.ts +1 -3
- package/dist/interceptors/tool-result.d.ts.map +1 -1
- package/dist/interceptors/tool-result.js +4 -12
- package/dist/interceptors/tool-result.js.map +1 -1
- package/dist/proxy.d.ts +18 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +48 -226
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +36 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -1,43 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.scanForPromptInjection =
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/disregard\s+(all\s+)?(previous|prior|above)\s+instructions?/i,
|
|
8
|
-
/forget\s+(all\s+)?(previous|prior|above)\s+instructions?/i,
|
|
9
|
-
/you\s+are\s+now\s+(a\s+)?(?!an?\s+AI|a\s+language)/i,
|
|
10
|
-
/act\s+as\s+(if\s+you\s+are\s+)?(?!an?\s+AI|a\s+language)/i,
|
|
11
|
-
/pretend\s+(you\s+are|to\s+be)\s+(?!an?\s+AI|a\s+language)/i,
|
|
12
|
-
/jailbreak/i,
|
|
13
|
-
/DAN\s+mode/i,
|
|
14
|
-
/developer\s+mode/i,
|
|
15
|
-
/system\s+prompt\s*:/i,
|
|
16
|
-
/<\s*system\s*>/i,
|
|
17
|
-
/\[SYSTEM\]/i,
|
|
18
|
-
/bypass\s+(safety|filter|restriction|policy)/i,
|
|
19
|
-
/override\s+(safety|filter|restriction|policy)/i,
|
|
20
|
-
/reveal\s+(your\s+)?(system\s+prompt|instructions|prompt)/i,
|
|
21
|
-
/print\s+(your\s+)?(system\s+prompt|instructions)/i,
|
|
22
|
-
];
|
|
23
|
-
function flattenValue(value) {
|
|
24
|
-
if (typeof value === "string")
|
|
25
|
-
return value;
|
|
26
|
-
if (typeof value === "object" && value !== null) {
|
|
27
|
-
return JSON.stringify(value);
|
|
28
|
-
}
|
|
29
|
-
return String(value ?? "");
|
|
30
|
-
}
|
|
31
|
-
function scanForPromptInjection(input, mode = "block") {
|
|
32
|
-
const text = flattenValue(input);
|
|
33
|
-
for (const pattern of INJECTION_PATTERNS) {
|
|
34
|
-
if (pattern.test(text)) {
|
|
35
|
-
return {
|
|
36
|
-
action: mode,
|
|
37
|
-
reason: `Prompt injection pattern detected: ${pattern.toString()}`,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return { action: "allow" };
|
|
42
|
-
}
|
|
3
|
+
exports.scanForPromptInjection = void 0;
|
|
4
|
+
// Injection patterns live in @guardbee/guard-core, shared with the gateway.
|
|
5
|
+
var guard_core_1 = require("@guardbee/guard-core");
|
|
6
|
+
Object.defineProperty(exports, "scanForPromptInjection", { enumerable: true, get: function () { return guard_core_1.scanForPromptInjection; } });
|
|
43
7
|
//# sourceMappingURL=prompt-injection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-injection.js","sourceRoot":"","sources":["../../src/interceptors/prompt-injection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prompt-injection.js","sourceRoot":"","sources":["../../src/interceptors/prompt-injection.ts"],"names":[],"mappings":";;;AAAA,4EAA4E;AAC5E,mDAA8D;AAArD,oHAAA,sBAAsB,OAAA"}
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
/** Same patterns as request scanning, applied to a tool result the model will read. */
|
|
3
|
-
export declare function scanToolResult(content: unknown, mode?: "block" | "warn"): InterceptResult;
|
|
1
|
+
export { scanToolResult } from "@guardbee/guard-core";
|
|
4
2
|
//# sourceMappingURL=tool-result.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-result.d.ts","sourceRoot":"","sources":["../../src/interceptors/tool-result.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-result.d.ts","sourceRoot":"","sources":["../../src/interceptors/tool-result.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.scanToolResult =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const result = (0, prompt_injection_js_1.scanForPromptInjection)(content, mode);
|
|
8
|
-
if (result.action === "allow")
|
|
9
|
-
return result;
|
|
10
|
-
return {
|
|
11
|
-
action: result.action,
|
|
12
|
-
reason: `Indirect prompt injection in tool result (MCP06:2025): ${result.reason}`,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
3
|
+
exports.scanToolResult = void 0;
|
|
4
|
+
// Indirect injection scanning lives in @guardbee/guard-core, shared with the gateway.
|
|
5
|
+
var guard_core_1 = require("@guardbee/guard-core");
|
|
6
|
+
Object.defineProperty(exports, "scanToolResult", { enumerable: true, get: function () { return guard_core_1.scanToolResult; } });
|
|
15
7
|
//# sourceMappingURL=tool-result.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../../src/interceptors/tool-result.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-result.js","sourceRoot":"","sources":["../../src/interceptors/tool-result.ts"],"names":[],"mappings":";;;AAAA,sFAAsF;AACtF,mDAAsD;AAA7C,4GAAA,cAAc,OAAA"}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1
2
|
import type { ProxyConfig } from "./types.js";
|
|
3
|
+
import { AuditLogger } from "./audit/logger.js";
|
|
4
|
+
import { type GatewayConfig } from "./gateway/config.js";
|
|
5
|
+
import { Gateway } from "./gateway/gateway.js";
|
|
6
|
+
import { type Upstream } from "./gateway/upstream.js";
|
|
7
|
+
/** The MCP server the agent talks to; every request goes through the gateway. */
|
|
8
|
+
export declare function createProxyServer(gateway: Gateway): Server;
|
|
9
|
+
/**
|
|
10
|
+
* Gateway + the MCP server in front of it, with approvals asked through that
|
|
11
|
+
* server (MCP elicitation). The approver needs the server and the server needs
|
|
12
|
+
* the gateway, hence the late binding.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createGatewayServer(upstreams: Upstream[], config: GatewayConfig, audit: AuditLogger): {
|
|
15
|
+
gateway: Gateway;
|
|
16
|
+
server: Server;
|
|
17
|
+
};
|
|
18
|
+
export declare function startGateway(config: GatewayConfig): Promise<void>;
|
|
19
|
+
/** 0.x entry point: one server, unprefixed tool names, toxic flows only warn. */
|
|
2
20
|
export declare function startProxy(config: ProxyConfig): Promise<void>;
|
|
3
21
|
//# sourceMappingURL=proxy.d.ts.map
|
package/dist/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAUnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAwB,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE5E,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CA2B1D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,QAAQ,EAAE,EACrB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,WAAW,GACjB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAOtC;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAevE;AAED,iFAAiF;AACjF,wBAAsB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAEnE"}
|
package/dist/proxy.js
CHANGED
|
@@ -1,243 +1,65 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProxyServer = createProxyServer;
|
|
4
|
+
exports.createGatewayServer = createGatewayServer;
|
|
5
|
+
exports.startGateway = startGateway;
|
|
3
6
|
exports.startProxy = startProxy;
|
|
4
|
-
const index_js_1 = require("@modelcontextprotocol/sdk/
|
|
5
|
-
const stdio_js_1 = require("@modelcontextprotocol/sdk/
|
|
6
|
-
const index_js_2 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
7
|
-
const stdio_js_2 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
7
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
8
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
8
9
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
9
|
-
const prompt_injection_js_1 = require("./interceptors/prompt-injection.js");
|
|
10
|
-
const tool_result_js_1 = require("./interceptors/tool-result.js");
|
|
11
|
-
const definition_drift_js_1 = require("./interceptors/definition-drift.js");
|
|
12
|
-
const pii_masker_js_1 = require("./interceptors/pii-masker.js");
|
|
13
10
|
const logger_js_1 = require("./audit/logger.js");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
args: config.server.args ?? [],
|
|
22
|
-
env: { ...process.env, ...(config.server.env ?? {}) },
|
|
23
|
-
});
|
|
24
|
-
await downstream.connect(transport);
|
|
25
|
-
const serverInfo = downstream.getServerVersion();
|
|
26
|
-
const driftEnabled = config.interceptors?.definitionDrift?.enabled !== false;
|
|
27
|
-
const driftAction = config.interceptors?.definitionDrift?.action ?? config.interceptors?.promptInjection?.action ?? "block";
|
|
28
|
-
const resultEnabled = config.interceptors?.toolResultInjection?.enabled !== false;
|
|
29
|
-
const resultAction = config.interceptors?.toolResultInjection?.action ?? config.interceptors?.promptInjection?.action ?? "block";
|
|
30
|
-
const pin = new definition_drift_js_1.ToolDefinitionPin();
|
|
31
|
-
// --- Start proxy MCP server (upstream, for Claude) ---
|
|
32
|
-
const server = new index_js_2.Server({ name: "guardbee-security-proxy", version: "0.1.0" }, {
|
|
11
|
+
const approval_js_1 = require("./gateway/approval.js");
|
|
12
|
+
const config_js_1 = require("./gateway/config.js");
|
|
13
|
+
const gateway_js_1 = require("./gateway/gateway.js");
|
|
14
|
+
const upstream_js_1 = require("./gateway/upstream.js");
|
|
15
|
+
/** The MCP server the agent talks to; every request goes through the gateway. */
|
|
16
|
+
function createProxyServer(gateway) {
|
|
17
|
+
const server = new index_js_1.Server({ name: "guardbee-security-proxy", version: "1.0.0" }, {
|
|
33
18
|
capabilities: {
|
|
34
|
-
tools: {},
|
|
19
|
+
tools: { listChanged: true },
|
|
35
20
|
resources: {},
|
|
36
21
|
prompts: {},
|
|
37
22
|
},
|
|
38
23
|
});
|
|
39
|
-
|
|
40
|
-
server.setRequestHandler(types_js_1.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ts: new Date().toISOString(),
|
|
48
|
-
type: driftAction === "block" ? "blocked" : "warn",
|
|
49
|
-
tool: finding.toolName,
|
|
50
|
-
server: serverInfo?.name,
|
|
51
|
-
reason: finding.reason,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
return { ...result, tools: observed.tools };
|
|
55
|
-
});
|
|
56
|
-
// Call tool → intercept, scan, forward, mask response
|
|
57
|
-
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
58
|
-
const toolName = req.params.name;
|
|
59
|
-
const toolInput = req.params.arguments ?? {};
|
|
60
|
-
const started = Date.now();
|
|
61
|
-
// 1. Prompt injection scan on input
|
|
62
|
-
if (config.interceptors?.promptInjection?.enabled !== false) {
|
|
63
|
-
const mode = config.interceptors?.promptInjection?.action ?? "block";
|
|
64
|
-
const scan = (0, prompt_injection_js_1.scanForPromptInjection)(toolInput, mode);
|
|
65
|
-
if (scan.action === "block") {
|
|
66
|
-
audit.log({
|
|
67
|
-
ts: new Date().toISOString(),
|
|
68
|
-
type: "blocked",
|
|
69
|
-
tool: toolName,
|
|
70
|
-
server: serverInfo?.name,
|
|
71
|
-
input: toolInput,
|
|
72
|
-
reason: scan.reason,
|
|
73
|
-
});
|
|
74
|
-
void (0, mcp_telemetry_1.recordEvent)({
|
|
75
|
-
server: "security-proxy",
|
|
76
|
-
tool: toolName,
|
|
77
|
-
params: toolInput,
|
|
78
|
-
success: false,
|
|
79
|
-
durationMs: Date.now() - started,
|
|
80
|
-
error: `blocked: ${scan.reason}`,
|
|
81
|
-
});
|
|
82
|
-
return {
|
|
83
|
-
content: [
|
|
84
|
-
{
|
|
85
|
-
type: "text",
|
|
86
|
-
text: `[GuardBee Security Proxy] Tool call blocked: ${scan.reason}`,
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
isError: true,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
if (scan.action === "warn") {
|
|
93
|
-
audit.log({
|
|
94
|
-
ts: new Date().toISOString(),
|
|
95
|
-
type: "warn",
|
|
96
|
-
tool: toolName,
|
|
97
|
-
server: serverInfo?.name,
|
|
98
|
-
input: toolInput,
|
|
99
|
-
reason: scan.reason,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
// 2. Re-check the live tool definition against the session pin.
|
|
104
|
-
if (driftEnabled) {
|
|
105
|
-
try {
|
|
106
|
-
const listed = await downstream.listTools();
|
|
107
|
-
pin.ensurePinned(listed.tools);
|
|
108
|
-
const drift = pin.drifted(toolName, listed.tools);
|
|
109
|
-
if (drift && driftAction === "block") {
|
|
110
|
-
audit.log({
|
|
111
|
-
ts: new Date().toISOString(),
|
|
112
|
-
type: "blocked",
|
|
113
|
-
tool: toolName,
|
|
114
|
-
server: serverInfo?.name,
|
|
115
|
-
input: toolInput,
|
|
116
|
-
reason: drift.reason,
|
|
117
|
-
});
|
|
118
|
-
return {
|
|
119
|
-
content: [{ type: "text", text: `[GuardBee Security Proxy] Tool call blocked: ${drift.reason}` }],
|
|
120
|
-
isError: true,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
if (drift) {
|
|
124
|
-
audit.log({
|
|
125
|
-
ts: new Date().toISOString(),
|
|
126
|
-
type: "warn",
|
|
127
|
-
tool: toolName,
|
|
128
|
-
server: serverInfo?.name,
|
|
129
|
-
input: toolInput,
|
|
130
|
-
reason: drift.reason,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (err) {
|
|
135
|
-
audit.log({
|
|
136
|
-
ts: new Date().toISOString(),
|
|
137
|
-
type: "warn",
|
|
138
|
-
tool: toolName,
|
|
139
|
-
server: serverInfo?.name,
|
|
140
|
-
reason: `definition re-check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// 3. Log the call
|
|
145
|
-
audit.log({
|
|
146
|
-
ts: new Date().toISOString(),
|
|
147
|
-
type: "tool_call",
|
|
148
|
-
tool: toolName,
|
|
149
|
-
server: serverInfo?.name,
|
|
150
|
-
input: toolInput,
|
|
151
|
-
});
|
|
152
|
-
// 3. Forward to downstream
|
|
153
|
-
const result = await downstream.callTool({
|
|
154
|
-
name: toolName,
|
|
155
|
-
arguments: toolInput,
|
|
156
|
-
});
|
|
157
|
-
// 4. Indirect injection in the tool result, then PII masking.
|
|
158
|
-
let content = result.content;
|
|
159
|
-
if (resultEnabled) {
|
|
160
|
-
const scan = (0, tool_result_js_1.scanToolResult)(content, resultAction);
|
|
161
|
-
if (scan.action === "block") {
|
|
162
|
-
audit.log({
|
|
163
|
-
ts: new Date().toISOString(),
|
|
164
|
-
type: "blocked",
|
|
165
|
-
tool: toolName,
|
|
166
|
-
server: serverInfo?.name,
|
|
167
|
-
output: content,
|
|
168
|
-
reason: scan.reason,
|
|
169
|
-
});
|
|
170
|
-
void (0, mcp_telemetry_1.recordEvent)({
|
|
171
|
-
server: "security-proxy",
|
|
172
|
-
tool: toolName,
|
|
173
|
-
params: toolInput,
|
|
174
|
-
success: false,
|
|
175
|
-
durationMs: Date.now() - started,
|
|
176
|
-
error: `blocked: ${scan.reason}`,
|
|
177
|
-
});
|
|
178
|
-
return {
|
|
179
|
-
content: [{ type: "text", text: `[GuardBee Security Proxy] Tool result blocked: ${scan.reason}` }],
|
|
180
|
-
isError: true,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (scan.action === "warn") {
|
|
184
|
-
audit.log({
|
|
185
|
-
ts: new Date().toISOString(),
|
|
186
|
-
type: "warn",
|
|
187
|
-
tool: toolName,
|
|
188
|
-
server: serverInfo?.name,
|
|
189
|
-
output: content,
|
|
190
|
-
reason: scan.reason,
|
|
191
|
-
});
|
|
192
|
-
const warning = { type: "text", text: `[GuardBee Security Proxy] Warning: ${scan.reason}` };
|
|
193
|
-
content = [warning, ...(Array.isArray(content) ? content : [])];
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (config.interceptors?.piiMasking?.enabled !== false) {
|
|
197
|
-
content = (0, pii_masker_js_1.maskPiiInValue)(content);
|
|
198
|
-
}
|
|
199
|
-
// 5. Log response
|
|
200
|
-
audit.log({
|
|
201
|
-
ts: new Date().toISOString(),
|
|
202
|
-
type: "tool_response",
|
|
203
|
-
tool: toolName,
|
|
204
|
-
server: serverInfo?.name,
|
|
205
|
-
output: content,
|
|
206
|
-
});
|
|
207
|
-
void (0, mcp_telemetry_1.recordEvent)({
|
|
208
|
-
server: "security-proxy",
|
|
209
|
-
tool: toolName,
|
|
210
|
-
params: toolInput,
|
|
211
|
-
success: !result.isError,
|
|
212
|
-
durationMs: Date.now() - started,
|
|
213
|
-
});
|
|
214
|
-
return { ...result, content };
|
|
215
|
-
});
|
|
216
|
-
// Resources pass-through
|
|
217
|
-
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
|
|
218
|
-
return await downstream.listResources();
|
|
219
|
-
});
|
|
220
|
-
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (req) => {
|
|
221
|
-
return await downstream.readResource({ uri: req.params.uri });
|
|
222
|
-
});
|
|
223
|
-
// Prompts pass-through
|
|
224
|
-
server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => {
|
|
225
|
-
return await downstream.listPrompts();
|
|
24
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: await gateway.listTools() }));
|
|
25
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => gateway.callTool(req.params.name, req.params.arguments ?? {}));
|
|
26
|
+
server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({ resources: await gateway.listResources() }));
|
|
27
|
+
server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (req) => gateway.readResource(req.params.uri));
|
|
28
|
+
server.setRequestHandler(types_js_1.ListPromptsRequestSchema, async () => ({ prompts: await gateway.listPrompts() }));
|
|
29
|
+
server.setRequestHandler(types_js_1.GetPromptRequestSchema, async (req) => gateway.getPrompt(req.params.name, req.params.arguments));
|
|
30
|
+
gateway.onToolsChanged(() => {
|
|
31
|
+
void server.sendToolListChanged().catch(() => { });
|
|
226
32
|
});
|
|
227
|
-
server
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
33
|
+
return server;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Gateway + the MCP server in front of it, with approvals asked through that
|
|
37
|
+
* server (MCP elicitation). The approver needs the server and the server needs
|
|
38
|
+
* the gateway, hence the late binding.
|
|
39
|
+
*/
|
|
40
|
+
function createGatewayServer(upstreams, config, audit) {
|
|
41
|
+
let server;
|
|
42
|
+
const approver = (request) => server ? (0, approval_js_1.elicitationApprover)(server, config.approval.timeoutSeconds)(request) : Promise.resolve("unavailable");
|
|
43
|
+
const gateway = new gateway_js_1.Gateway(upstreams, config, audit, approver);
|
|
44
|
+
server = createProxyServer(gateway);
|
|
45
|
+
return { gateway, server };
|
|
46
|
+
}
|
|
47
|
+
async function startGateway(config) {
|
|
48
|
+
const audit = new logger_js_1.AuditLogger(config.audit);
|
|
49
|
+
const upstreams = [];
|
|
50
|
+
for (const [name, server] of Object.entries(config.upstreams)) {
|
|
51
|
+
upstreams.push(await (0, upstream_js_1.connectStdioUpstream)(name, server));
|
|
52
|
+
}
|
|
53
|
+
const { gateway, server } = createGatewayServer(upstreams, config, audit);
|
|
54
|
+
await server.connect(new stdio_js_1.StdioServerTransport());
|
|
236
55
|
process.on("SIGINT", async () => {
|
|
237
|
-
audit.close();
|
|
238
56
|
await server.close();
|
|
239
|
-
await
|
|
57
|
+
await gateway.close();
|
|
240
58
|
process.exit(0);
|
|
241
59
|
});
|
|
242
60
|
}
|
|
61
|
+
/** 0.x entry point: one server, unprefixed tool names, toxic flows only warn. */
|
|
62
|
+
async function startProxy(config) {
|
|
63
|
+
await startGateway((0, config_js_1.fromLegacyConfig)(config));
|
|
64
|
+
}
|
|
243
65
|
//# sourceMappingURL=proxy.js.map
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -3,13 +3,21 @@ export type McpServerConfig = {
|
|
|
3
3
|
args?: string[];
|
|
4
4
|
env?: Record<string, string>;
|
|
5
5
|
};
|
|
6
|
+
export type AuditConfig = {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
sink: "console" | "file";
|
|
9
|
+
filePath?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Write raw tool arguments and results into the log. Off → arguments are
|
|
12
|
+
* stored only as a SHA-256 hash and results are left out, so the audit log
|
|
13
|
+
* does not become another store of personal data. Defaults to true for the
|
|
14
|
+
* legacy JSON config, false for guardbee-proxy.yaml.
|
|
15
|
+
*/
|
|
16
|
+
includePayloads?: boolean;
|
|
17
|
+
};
|
|
6
18
|
export type ProxyConfig = {
|
|
7
19
|
server: McpServerConfig;
|
|
8
|
-
audit?:
|
|
9
|
-
enabled: boolean;
|
|
10
|
-
sink: "console" | "file";
|
|
11
|
-
filePath?: string;
|
|
12
|
-
};
|
|
20
|
+
audit?: AuditConfig;
|
|
13
21
|
interceptors?: {
|
|
14
22
|
promptInjection?: {
|
|
15
23
|
enabled: boolean;
|
|
@@ -22,10 +30,19 @@ export type ProxyConfig = {
|
|
|
22
30
|
definitionDrift?: {
|
|
23
31
|
enabled: boolean;
|
|
24
32
|
action: "block" | "warn";
|
|
33
|
+
/**
|
|
34
|
+
* `every-call` re-lists the upstream's tools before each call (default).
|
|
35
|
+
* `on-change` re-lists only after the upstream sent tools/list_changed.
|
|
36
|
+
*/
|
|
37
|
+
recheck?: "every-call" | "on-change";
|
|
25
38
|
};
|
|
26
39
|
piiMasking?: {
|
|
27
40
|
enabled: boolean;
|
|
28
41
|
patterns?: string[];
|
|
42
|
+
/** `redact` (default) replaces PII with a fixed placeholder; `tokenize` with a reversible session token. */
|
|
43
|
+
mode?: "redact" | "tokenize";
|
|
44
|
+
/** Put real values back even when the tool can send data out. Off by default. */
|
|
45
|
+
detokenizeForEgress?: boolean;
|
|
29
46
|
};
|
|
30
47
|
};
|
|
31
48
|
};
|
|
@@ -40,11 +57,24 @@ export type InterceptResult = {
|
|
|
40
57
|
};
|
|
41
58
|
export type AuditEvent = {
|
|
42
59
|
ts: string;
|
|
43
|
-
type: "tool_call" | "tool_response" | "blocked" | "warn";
|
|
60
|
+
type: "tool_call" | "tool_response" | "blocked" | "warn" | "toxic_flow" | "resource_read" | "approval";
|
|
44
61
|
tool?: string;
|
|
45
62
|
server?: string;
|
|
63
|
+
upstream?: string;
|
|
64
|
+
labels?: string[];
|
|
65
|
+
/** Matching policy rule (`id` or `rules[<index>]`). */
|
|
66
|
+
ruleId?: string;
|
|
67
|
+
taint?: {
|
|
68
|
+
sawUntrusted: boolean;
|
|
69
|
+
sawSensitive: boolean;
|
|
70
|
+
};
|
|
46
71
|
input?: unknown;
|
|
72
|
+
/** SHA-256 of the arguments, written instead of `input` when payloads are off. */
|
|
73
|
+
inputHash?: string;
|
|
47
74
|
output?: unknown;
|
|
48
75
|
reason?: string;
|
|
76
|
+
/** Hash chain: each event carries the previous event's hash. Set by AuditLogger. */
|
|
77
|
+
prevHash?: string;
|
|
78
|
+
hash?: string;
|
|
49
79
|
};
|
|
50
80
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,YAAY,CAAC,EAAE;QACb,eAAe,CAAC,EAAE;YAChB,OAAO,EAAE,OAAO,CAAC;YACjB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;SAC1B,CAAC;QACF,mBAAmB,CAAC,EAAE;YACpB,OAAO,EAAE,OAAO,CAAC;YACjB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;SAC1B,CAAC;QACF,eAAe,CAAC,EAAE;YAChB,OAAO,EAAE,OAAO,CAAC;YACjB,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;YACzB;;;eAGG;YACH,OAAO,CAAC,EAAE,YAAY,GAAG,WAAW,CAAC;SACtC,CAAC;QACF,UAAU,CAAC,EAAE;YACX,OAAO,EAAE,OAAO,CAAC;YACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,4GAA4G;YAC5G,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;YAC7B,iFAAiF;YACjF,mBAAmB,CAAC,EAAE,OAAO,CAAC;SAC/B,CAAC;KACH,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,eAAe,GAAG,UAAU,CAAC;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE;QAAE,YAAY,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;IACzD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guardbee/mcp-security-proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"mcpName": "io.github.4hmetuyar/security-proxy",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "MCP security gateway: several MCP servers behind one policy, with toxic-flow (lethal trifecta) blocking, approvals, PII masking and a hash-chained audit log",
|
|
6
6
|
"author": "GuardBee <hello@guardbee.ai>",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"homepage": "https://github.com/GuardBee/guardbee-mcp/tree/main/packages/security-proxy#readme",
|
|
@@ -41,7 +41,9 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
44
|
+
"yaml": "^2.9.1",
|
|
44
45
|
"zod": "^4.5.4",
|
|
46
|
+
"@guardbee/guard-core": "^0.1.0",
|
|
45
47
|
"@guardbee/mcp-telemetry": "^0.1.3"
|
|
46
48
|
},
|
|
47
49
|
"scripts": {
|