@quint-security/proxy 0.3.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.
@@ -0,0 +1,14 @@
1
+ import { type PolicyConfig } from "@quint-security/core";
2
+ export interface HttpProxyOptions {
3
+ serverName: string;
4
+ port: number;
5
+ targetUrl: string;
6
+ policy: PolicyConfig;
7
+ requireAuth?: boolean;
8
+ }
9
+ /**
10
+ * Start the HTTP proxy: run a local HTTP server, intercept all JSON-RPC
11
+ * requests, enforce policy, sign and log everything, forward to remote.
12
+ */
13
+ export declare function startHttpProxy(opts: HttpProxyOptions): Promise<void>;
14
+ //# sourceMappingURL=http-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-proxy.d.ts","sourceRoot":"","sources":["../src/http-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAYlB,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiJ1E"}
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startHttpProxy = startHttpProxy;
4
+ const core_1 = require("@quint-security/core");
5
+ const http_relay_js_1 = require("./http-relay.js");
6
+ const interceptor_js_1 = require("./interceptor.js");
7
+ const logger_js_1 = require("./logger.js");
8
+ /**
9
+ * Start the HTTP proxy: run a local HTTP server, intercept all JSON-RPC
10
+ * requests, enforce policy, sign and log everything, forward to remote.
11
+ */
12
+ async function startHttpProxy(opts) {
13
+ (0, core_1.setLogLevel)(opts.policy.log_level);
14
+ const dataDir = (0, core_1.resolveDataDir)(opts.policy.data_dir);
15
+ // Ensure signing keys exist
16
+ const kp = (0, core_1.ensureKeyPair)(dataDir);
17
+ // Open audit database
18
+ const db = (0, core_1.openAuditDb)(dataDir);
19
+ // Create audit logger
20
+ const logger = new logger_js_1.AuditLogger(db, kp.privateKey, kp.publicKey, opts.policy);
21
+ // Create risk engine
22
+ const riskEngine = new core_1.RiskEngine();
23
+ // Create HTTP relay (with optional auth)
24
+ const authDb = opts.requireAuth ? (0, core_1.openAuthDb)(dataDir) : null;
25
+ const relay = new http_relay_js_1.HttpRelay(opts.port, opts.targetUrl, opts.requireAuth ? (req) => {
26
+ const authHeader = req.headers.authorization;
27
+ if (!authHeader || !authHeader.startsWith("Bearer ")) {
28
+ return "Quint: missing or invalid Authorization header. Use: Bearer <api-key>";
29
+ }
30
+ const token = authHeader.slice(7);
31
+ const result = (0, core_1.authenticateBearer)(authDb, token);
32
+ if (!result) {
33
+ return "Quint: invalid or expired API key";
34
+ }
35
+ return null;
36
+ } : undefined);
37
+ // ── Handle requests from agent → remote MCP server ──
38
+ relay.on("request", (line, requestKey) => {
39
+ const result = (0, interceptor_js_1.inspectRequest)(line, opts.serverName, opts.policy);
40
+ // Log the request
41
+ logger.log({
42
+ serverName: opts.serverName,
43
+ direction: "request",
44
+ method: result.method,
45
+ messageId: result.messageId,
46
+ toolName: result.toolName,
47
+ argumentsJson: result.argumentsJson,
48
+ responseJson: null,
49
+ verdict: result.verdict,
50
+ });
51
+ if (result.verdict === "deny") {
52
+ // Send error response back to agent
53
+ const reqId = result.message && "id" in result.message ? result.message.id : null;
54
+ const errorResponse = (0, interceptor_js_1.buildDenyResponse)(reqId ?? null);
55
+ relay.respondToClient(requestKey, errorResponse);
56
+ (0, core_1.logInfo)(`denied ${result.toolName} on ${opts.serverName}`);
57
+ // Log the synthetic deny response
58
+ logger.log({
59
+ serverName: opts.serverName,
60
+ direction: "response",
61
+ method: result.method,
62
+ messageId: result.messageId,
63
+ toolName: result.toolName,
64
+ argumentsJson: null,
65
+ responseJson: errorResponse,
66
+ verdict: "deny",
67
+ });
68
+ }
69
+ else if (result.toolName) {
70
+ // Run risk scoring on tool calls that passed policy
71
+ const risk = riskEngine.score(result.toolName, result.argumentsJson, "anonymous");
72
+ const riskAction = riskEngine.evaluate(risk);
73
+ if (riskAction === "deny") {
74
+ // Risk score too high — auto-deny
75
+ const reqId = result.message && "id" in result.message ? result.message.id : null;
76
+ const errorResponse = (0, interceptor_js_1.buildDenyResponse)(reqId ?? null);
77
+ relay.respondToClient(requestKey, errorResponse);
78
+ (0, core_1.logWarn)(`risk-denied ${result.toolName} (score=${risk.score}, level=${risk.level}): ${risk.reasons.join("; ")}`);
79
+ logger.log({
80
+ serverName: opts.serverName,
81
+ direction: "response",
82
+ method: result.method,
83
+ messageId: result.messageId,
84
+ toolName: result.toolName,
85
+ argumentsJson: null,
86
+ responseJson: errorResponse,
87
+ verdict: "deny",
88
+ });
89
+ }
90
+ else {
91
+ if (riskAction === "flag") {
92
+ (0, core_1.logWarn)(`high-risk ${result.toolName} (score=${risk.score}, level=${risk.level}): ${risk.reasons.join("; ")}`);
93
+ }
94
+ (0, core_1.logDebug)(`forwarding ${result.method} (risk=${risk.score}) to remote`);
95
+ relay.forwardToRemote(requestKey);
96
+ }
97
+ // Check for revocation threshold
98
+ if (riskEngine.shouldRevoke("anonymous")) {
99
+ (0, core_1.logWarn)(`repeated high-risk actions detected — consider revoking agent credentials`);
100
+ }
101
+ }
102
+ else {
103
+ // Non-tool-call (initialize, tools/list, etc.) — forward directly
104
+ (0, core_1.logDebug)(`forwarding ${result.method} (${result.verdict}) to remote`);
105
+ relay.forwardToRemote(requestKey);
106
+ }
107
+ });
108
+ // ── Handle responses from remote MCP server ──
109
+ relay.on("response", (line) => {
110
+ const result = (0, interceptor_js_1.inspectResponse)(line);
111
+ // Log the response
112
+ logger.log({
113
+ serverName: opts.serverName,
114
+ direction: "response",
115
+ method: result.method,
116
+ messageId: result.messageId,
117
+ toolName: null,
118
+ argumentsJson: null,
119
+ responseJson: result.responseJson,
120
+ verdict: "passthrough",
121
+ });
122
+ });
123
+ // ── Handle errors ──
124
+ relay.on("error", (err) => {
125
+ (0, core_1.logError)(`http-proxy error: ${err.message}`);
126
+ });
127
+ // Handle shutdown
128
+ const shutdown = () => {
129
+ relay.stop();
130
+ db.close();
131
+ authDb?.close();
132
+ process.exit(0);
133
+ };
134
+ process.on("SIGINT", shutdown);
135
+ process.on("SIGTERM", shutdown);
136
+ // Start listening
137
+ await relay.start();
138
+ (0, core_1.logInfo)(`HTTP proxy listening on http://localhost:${opts.port} → ${opts.targetUrl}`);
139
+ }
140
+ //# sourceMappingURL=http-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-proxy.js","sourceRoot":"","sources":["../src/http-proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { type IncomingMessage } from "node:http";
2
+ import { EventEmitter } from "node:events";
3
+ export interface HttpRelayEvents {
4
+ /** Fired for every JSON-RPC request received from the agent via HTTP */
5
+ request: (line: string) => void;
6
+ /** Fired for every JSON-RPC response received from the remote server */
7
+ response: (line: string) => void;
8
+ /** Unrecoverable error */
9
+ error: (err: Error) => void;
10
+ }
11
+ /**
12
+ * Optional auth check function. Return null/undefined if auth passes,
13
+ * or an error message string to reject the request with 401.
14
+ */
15
+ export type AuthCheckFn = (req: IncomingMessage) => string | undefined | null;
16
+ /**
17
+ * HttpRelay manages:
18
+ * - Running a local HTTP server that accepts JSON-RPC POST requests
19
+ * - Forwarding allowed requests to a remote MCP server via fetch()
20
+ * - Streaming SSE responses back when the remote uses text/event-stream
21
+ *
22
+ * The interceptor hooks into request/response events to inspect,
23
+ * allow, deny, or modify messages before they are forwarded.
24
+ */
25
+ export declare class HttpRelay extends EventEmitter {
26
+ private server;
27
+ private port;
28
+ private targetUrl;
29
+ private pending;
30
+ private requestCounter;
31
+ private authCheck;
32
+ constructor(port: number, targetUrl: string, authCheck?: AuthCheckFn);
33
+ start(): Promise<void>;
34
+ stop(): void;
35
+ /**
36
+ * Send a deny response back to the HTTP client for a given pending request.
37
+ */
38
+ respondToClient(requestKey: string, body: string): void;
39
+ /**
40
+ * Forward the original request to the remote MCP server and relay the response.
41
+ */
42
+ forwardToRemote(requestKey: string): Promise<void>;
43
+ private handleRequest;
44
+ }
45
+ //# sourceMappingURL=http-relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-relay.d.ts","sourceRoot":"","sources":["../src/http-relay.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,eAAe,EAAuB,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,wEAAwE;IACxE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,0BAA0B;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC7B;AAQD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;AAE9E;;;;;;;;GAQG;AACH,qBAAa,SAAU,SAAQ,YAAY;IACzC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAA0C;IACzD,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,SAAS,CAA4B;gBAEjC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,WAAW;IAOpE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBtB,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IASvD;;OAEG;IACG,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2FxD,OAAO,CAAC,aAAa;CAoDtB"}
@@ -0,0 +1,193 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HttpRelay = void 0;
4
+ const node_http_1 = require("node:http");
5
+ const node_events_1 = require("node:events");
6
+ /**
7
+ * HttpRelay manages:
8
+ * - Running a local HTTP server that accepts JSON-RPC POST requests
9
+ * - Forwarding allowed requests to a remote MCP server via fetch()
10
+ * - Streaming SSE responses back when the remote uses text/event-stream
11
+ *
12
+ * The interceptor hooks into request/response events to inspect,
13
+ * allow, deny, or modify messages before they are forwarded.
14
+ */
15
+ class HttpRelay extends node_events_1.EventEmitter {
16
+ server = null;
17
+ port;
18
+ targetUrl;
19
+ pending = new Map();
20
+ requestCounter = 0;
21
+ authCheck = null;
22
+ constructor(port, targetUrl, authCheck) {
23
+ super();
24
+ this.port = port;
25
+ this.targetUrl = targetUrl;
26
+ this.authCheck = authCheck ?? null;
27
+ }
28
+ start() {
29
+ return new Promise((resolve, reject) => {
30
+ this.server = (0, node_http_1.createServer)((req, res) => {
31
+ this.handleRequest(req, res);
32
+ });
33
+ this.server.on("error", (err) => {
34
+ this.emit("error", err);
35
+ reject(err);
36
+ });
37
+ this.server.listen(this.port, () => {
38
+ resolve();
39
+ });
40
+ });
41
+ }
42
+ stop() {
43
+ this.server?.close();
44
+ }
45
+ /**
46
+ * Send a deny response back to the HTTP client for a given pending request.
47
+ */
48
+ respondToClient(requestKey, body) {
49
+ const pending = this.pending.get(requestKey);
50
+ if (!pending)
51
+ return;
52
+ this.pending.delete(requestKey);
53
+ pending.res.writeHead(200, { "Content-Type": "application/json" });
54
+ pending.res.end(body);
55
+ }
56
+ /**
57
+ * Forward the original request to the remote MCP server and relay the response.
58
+ */
59
+ async forwardToRemote(requestKey) {
60
+ const pending = this.pending.get(requestKey);
61
+ if (!pending)
62
+ return;
63
+ this.pending.delete(requestKey);
64
+ try {
65
+ // Forward relevant headers from the original request to the remote server
66
+ const forwardHeaders = {
67
+ "Content-Type": "application/json",
68
+ Accept: "application/json, text/event-stream",
69
+ };
70
+ if (pending.headers.authorization) {
71
+ forwardHeaders["Authorization"] = pending.headers.authorization;
72
+ }
73
+ const remoteRes = await fetch(this.targetUrl, {
74
+ method: "POST",
75
+ headers: forwardHeaders,
76
+ body: pending.body,
77
+ });
78
+ const contentType = remoteRes.headers.get("content-type") ?? "";
79
+ if (contentType.includes("text/event-stream") && remoteRes.body) {
80
+ // SSE streaming — relay each event back to the client
81
+ pending.res.writeHead(remoteRes.status, {
82
+ "Content-Type": "text/event-stream",
83
+ "Cache-Control": "no-cache",
84
+ Connection: "keep-alive",
85
+ });
86
+ const reader = remoteRes.body.getReader();
87
+ const decoder = new TextDecoder();
88
+ let buffer = "";
89
+ try {
90
+ while (true) {
91
+ const { done, value } = await reader.read();
92
+ if (done)
93
+ break;
94
+ const chunk = decoder.decode(value, { stream: true });
95
+ buffer += chunk;
96
+ pending.res.write(chunk);
97
+ // Extract complete SSE data lines for logging
98
+ const lines = buffer.split("\n");
99
+ buffer = lines.pop() ?? "";
100
+ for (const line of lines) {
101
+ if (line.startsWith("data: ")) {
102
+ const data = line.slice(6).trim();
103
+ if (data) {
104
+ this.emit("response", data);
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ finally {
111
+ // Flush remaining buffer
112
+ if (buffer.startsWith("data: ")) {
113
+ const data = buffer.slice(6).trim();
114
+ if (data) {
115
+ this.emit("response", data);
116
+ }
117
+ }
118
+ pending.res.end();
119
+ }
120
+ }
121
+ else {
122
+ // Standard JSON response
123
+ const responseBody = await remoteRes.text();
124
+ this.emit("response", responseBody);
125
+ pending.res.writeHead(remoteRes.status, {
126
+ "Content-Type": contentType || "application/json",
127
+ });
128
+ pending.res.end(responseBody);
129
+ }
130
+ }
131
+ catch (err) {
132
+ const errorBody = JSON.stringify({
133
+ jsonrpc: "2.0",
134
+ id: null,
135
+ error: {
136
+ code: -32603,
137
+ message: `Quint: failed to reach remote server: ${err.message}`,
138
+ },
139
+ });
140
+ this.emit("response", errorBody);
141
+ pending.res.writeHead(502, { "Content-Type": "application/json" });
142
+ pending.res.end(errorBody);
143
+ }
144
+ }
145
+ handleRequest(req, res) {
146
+ // Only handle POST requests
147
+ if (req.method !== "POST") {
148
+ res.writeHead(405, { "Content-Type": "application/json" });
149
+ res.end(JSON.stringify({ error: "Method not allowed. Use POST." }));
150
+ return;
151
+ }
152
+ // CORS preflight support
153
+ res.setHeader("Access-Control-Allow-Origin", "*");
154
+ res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
155
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
156
+ // Auth check (if configured)
157
+ if (this.authCheck) {
158
+ const authError = this.authCheck(req);
159
+ if (authError) {
160
+ res.writeHead(401, { "Content-Type": "application/json" });
161
+ res.end(JSON.stringify({
162
+ jsonrpc: "2.0",
163
+ id: null,
164
+ error: { code: -32600, message: authError },
165
+ }));
166
+ return;
167
+ }
168
+ }
169
+ const chunks = [];
170
+ req.on("data", (chunk) => chunks.push(chunk));
171
+ req.on("end", () => {
172
+ const body = Buffer.concat(chunks).toString("utf-8");
173
+ const requestKey = String(++this.requestCounter);
174
+ // Capture headers to forward to remote server
175
+ const headers = {};
176
+ for (const key of ["authorization", "content-type", "accept"]) {
177
+ const val = req.headers[key];
178
+ if (typeof val === "string")
179
+ headers[key] = val;
180
+ }
181
+ this.pending.set(requestKey, { res, body, headers });
182
+ // Emit the request for the interceptor to inspect.
183
+ // The interceptor will call respondToClient() for denials
184
+ // or forwardToRemote() for allowed requests.
185
+ this.emit("request", body, requestKey);
186
+ });
187
+ req.on("error", (err) => {
188
+ this.emit("error", err);
189
+ });
190
+ }
191
+ }
192
+ exports.HttpRelay = HttpRelay;
193
+ //# sourceMappingURL=http-relay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-relay.js","sourceRoot":"","sources":["../src/http-relay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { type PolicyConfig } from "@quint-security/core";
2
+ export { Relay } from "./relay.js";
3
+ export { HttpRelay } from "./http-relay.js";
4
+ export { inspectRequest, inspectResponse, buildDenyResponse } from "./interceptor.js";
5
+ export { AuditLogger } from "./logger.js";
6
+ export { startHttpProxy } from "./http-proxy.js";
7
+ export interface ProxyOptions {
8
+ serverName: string;
9
+ command: string;
10
+ args: string[];
11
+ policy: PolicyConfig;
12
+ }
13
+ /**
14
+ * Start the proxy: spawn child MCP server, intercept all JSON-RPC
15
+ * messages, enforce policy, sign and log everything.
16
+ */
17
+ export declare function startProxy(opts: ProxyOptions): void;
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAUlB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAgJnD"}
package/dist/index.js ADDED
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startHttpProxy = exports.AuditLogger = exports.buildDenyResponse = exports.inspectResponse = exports.inspectRequest = exports.HttpRelay = exports.Relay = void 0;
4
+ exports.startProxy = startProxy;
5
+ const core_1 = require("@quint-security/core");
6
+ const relay_js_1 = require("./relay.js");
7
+ const interceptor_js_1 = require("./interceptor.js");
8
+ const logger_js_1 = require("./logger.js");
9
+ var relay_js_2 = require("./relay.js");
10
+ Object.defineProperty(exports, "Relay", { enumerable: true, get: function () { return relay_js_2.Relay; } });
11
+ var http_relay_js_1 = require("./http-relay.js");
12
+ Object.defineProperty(exports, "HttpRelay", { enumerable: true, get: function () { return http_relay_js_1.HttpRelay; } });
13
+ var interceptor_js_2 = require("./interceptor.js");
14
+ Object.defineProperty(exports, "inspectRequest", { enumerable: true, get: function () { return interceptor_js_2.inspectRequest; } });
15
+ Object.defineProperty(exports, "inspectResponse", { enumerable: true, get: function () { return interceptor_js_2.inspectResponse; } });
16
+ Object.defineProperty(exports, "buildDenyResponse", { enumerable: true, get: function () { return interceptor_js_2.buildDenyResponse; } });
17
+ var logger_js_2 = require("./logger.js");
18
+ Object.defineProperty(exports, "AuditLogger", { enumerable: true, get: function () { return logger_js_2.AuditLogger; } });
19
+ var http_proxy_js_1 = require("./http-proxy.js");
20
+ Object.defineProperty(exports, "startHttpProxy", { enumerable: true, get: function () { return http_proxy_js_1.startHttpProxy; } });
21
+ /**
22
+ * Start the proxy: spawn child MCP server, intercept all JSON-RPC
23
+ * messages, enforce policy, sign and log everything.
24
+ */
25
+ function startProxy(opts) {
26
+ (0, core_1.setLogLevel)(opts.policy.log_level);
27
+ const dataDir = (0, core_1.resolveDataDir)(opts.policy.data_dir);
28
+ // Ensure signing keys exist
29
+ const kp = (0, core_1.ensureKeyPair)(dataDir);
30
+ // Open audit database
31
+ const db = (0, core_1.openAuditDb)(dataDir);
32
+ // Create audit logger
33
+ const logger = new logger_js_1.AuditLogger(db, kp.privateKey, kp.publicKey, opts.policy);
34
+ // Create risk engine
35
+ const riskEngine = new core_1.RiskEngine();
36
+ // Create relay
37
+ const relay = new relay_js_1.Relay(opts.command, opts.args);
38
+ // ── Handle messages from parent (AI agent) → child (MCP server) ──
39
+ relay.on("parentMessage", (line) => {
40
+ const result = (0, interceptor_js_1.inspectRequest)(line, opts.serverName, opts.policy);
41
+ // For non-tool-call requests, log immediately. Tool calls get logged after risk scoring.
42
+ if (!result.toolName || result.verdict === "deny") {
43
+ logger.log({
44
+ serverName: opts.serverName,
45
+ direction: "request",
46
+ method: result.method,
47
+ messageId: result.messageId,
48
+ toolName: result.toolName,
49
+ argumentsJson: result.argumentsJson,
50
+ responseJson: null,
51
+ verdict: result.verdict,
52
+ });
53
+ }
54
+ if (result.verdict === "deny") {
55
+ const reqId = result.message && "id" in result.message ? result.message.id : null;
56
+ const errorResponse = (0, interceptor_js_1.buildDenyResponse)(reqId ?? null);
57
+ relay.sendToParent(errorResponse);
58
+ (0, core_1.logInfo)(`denied ${result.toolName} on ${opts.serverName}`);
59
+ logger.log({
60
+ serverName: opts.serverName,
61
+ direction: "response",
62
+ method: result.method,
63
+ messageId: result.messageId,
64
+ toolName: result.toolName,
65
+ argumentsJson: null,
66
+ responseJson: errorResponse,
67
+ verdict: "deny",
68
+ });
69
+ }
70
+ else if (result.toolName) {
71
+ const risk = riskEngine.score(result.toolName, result.argumentsJson, "anonymous");
72
+ const riskAction = riskEngine.evaluate(risk);
73
+ // Re-log the request with risk score attached
74
+ logger.log({
75
+ serverName: opts.serverName,
76
+ direction: "request",
77
+ method: result.method,
78
+ messageId: result.messageId,
79
+ toolName: result.toolName,
80
+ argumentsJson: result.argumentsJson,
81
+ responseJson: null,
82
+ verdict: result.verdict,
83
+ riskScore: risk.score,
84
+ riskLevel: risk.level,
85
+ });
86
+ if (riskAction === "deny") {
87
+ const reqId = result.message && "id" in result.message ? result.message.id : null;
88
+ const errorResponse = (0, interceptor_js_1.buildDenyResponse)(reqId ?? null);
89
+ relay.sendToParent(errorResponse);
90
+ (0, core_1.logWarn)(`risk-denied ${result.toolName} (score=${risk.score}, level=${risk.level}): ${risk.reasons.join("; ")}`);
91
+ logger.log({
92
+ serverName: opts.serverName,
93
+ direction: "response",
94
+ method: result.method,
95
+ messageId: result.messageId,
96
+ toolName: result.toolName,
97
+ argumentsJson: null,
98
+ responseJson: errorResponse,
99
+ verdict: "deny",
100
+ riskScore: risk.score,
101
+ riskLevel: risk.level,
102
+ });
103
+ }
104
+ else {
105
+ if (riskAction === "flag") {
106
+ (0, core_1.logWarn)(`high-risk ${result.toolName} (score=${risk.score}, level=${risk.level}): ${risk.reasons.join("; ")}`);
107
+ }
108
+ (0, core_1.logDebug)(`forwarding ${result.method} (risk=${risk.score}) to child`);
109
+ relay.sendToChild(line);
110
+ }
111
+ if (riskEngine.shouldRevoke("anonymous")) {
112
+ (0, core_1.logWarn)(`repeated high-risk actions detected — consider revoking agent credentials`);
113
+ }
114
+ }
115
+ else {
116
+ // Non-tool-call — forward directly
117
+ (0, core_1.logDebug)(`forwarding ${result.method} (${result.verdict}) to child`);
118
+ relay.sendToChild(line);
119
+ }
120
+ });
121
+ // ── Handle messages from child (MCP server) → parent (AI agent) ──
122
+ relay.on("childMessage", (line) => {
123
+ const result = (0, interceptor_js_1.inspectResponse)(line);
124
+ // Log the response
125
+ logger.log({
126
+ serverName: opts.serverName,
127
+ direction: "response",
128
+ method: result.method,
129
+ messageId: result.messageId,
130
+ toolName: null,
131
+ argumentsJson: null,
132
+ responseJson: result.responseJson,
133
+ verdict: "passthrough",
134
+ });
135
+ // Always forward responses to parent
136
+ relay.sendToParent(line);
137
+ });
138
+ // ── Handle child exit ──
139
+ relay.on("childExit", (code) => {
140
+ db.close();
141
+ process.exit(code ?? 0);
142
+ });
143
+ relay.on("error", (err) => {
144
+ (0, core_1.logError)(`relay error: ${err.message}`);
145
+ db.close();
146
+ process.exit(1);
147
+ });
148
+ // Start
149
+ relay.start();
150
+ }
151
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type JsonRpcMessage, type Verdict, type PolicyConfig } from "@quint-security/core";
2
+ export interface InspectionResult {
3
+ /** The parsed JSON-RPC message (null if line is not valid JSON-RPC) */
4
+ message: JsonRpcMessage | null;
5
+ /** Policy verdict */
6
+ verdict: Verdict;
7
+ /** Extracted tool name (for tools/call requests) */
8
+ toolName: string | null;
9
+ /** Extracted tool arguments as JSON string */
10
+ argumentsJson: string | null;
11
+ /** JSON-RPC method */
12
+ method: string;
13
+ /** JSON-RPC id */
14
+ messageId: string | null;
15
+ }
16
+ /**
17
+ * Try to parse a line as JSON-RPC and determine the policy verdict.
18
+ * Non-parseable lines or non-tools/call methods get "passthrough".
19
+ */
20
+ export declare function inspectRequest(line: string, serverName: string, policy: PolicyConfig): InspectionResult;
21
+ /**
22
+ * Inspect a response line from the child (just for logging purposes — responses always pass through).
23
+ */
24
+ export declare function inspectResponse(line: string): {
25
+ method: string;
26
+ messageId: string | null;
27
+ responseJson: string | null;
28
+ };
29
+ /**
30
+ * Build a JSON-RPC error response for a denied tool call.
31
+ */
32
+ export declare function buildDenyResponse(requestId: string | number | null): string;
33
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,OAAO,EACZ,KAAK,YAAY,EAKlB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,gBAAgB;IAC/B,uEAAuE;IACvE,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,oDAAoD;IACpD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,YAAY,GACnB,gBAAgB,CA+ClB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAaA;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAU3E"}