contextguard 0.1.8 → 0.2.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/LICENSE +23 -17
- package/README.md +157 -109
- package/dist/agent.d.ts +24 -0
- package/dist/agent.js +369 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +266 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +56 -0
- package/dist/database.d.ts +116 -0
- package/dist/database.js +291 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +18 -0
- package/dist/init.d.ts +7 -0
- package/dist/init.js +173 -0
- package/dist/lib/supabase-client.d.ts +27 -0
- package/dist/lib/supabase-client.js +97 -0
- package/dist/logger.d.ts +36 -0
- package/dist/logger.js +145 -0
- package/dist/mcp-security-wrapper.d.ts +84 -0
- package/dist/mcp-security-wrapper.js +389 -147
- package/dist/mcp-traceability-integration.d.ts +118 -0
- package/dist/mcp-traceability-integration.js +302 -0
- package/dist/policy.d.ts +30 -0
- package/dist/policy.js +273 -0
- package/dist/premium-features.d.ts +364 -0
- package/dist/premium-features.js +950 -0
- package/dist/security-logger.d.ts +45 -0
- package/dist/security-logger.js +125 -0
- package/dist/security-policy.d.ts +55 -0
- package/dist/security-policy.js +140 -0
- package/dist/semantic-detector.d.ts +21 -0
- package/dist/semantic-detector.js +49 -0
- package/dist/sse-proxy.d.ts +21 -0
- package/dist/sse-proxy.js +276 -0
- package/dist/supabase-client.d.ts +27 -0
- package/dist/supabase-client.js +89 -0
- package/dist/types/database.types.d.ts +220 -0
- package/dist/types/database.types.js +8 -0
- package/dist/types/mcp.d.ts +27 -0
- package/dist/types/mcp.js +15 -0
- package/dist/types/types.d.ts +65 -0
- package/dist/types/types.js +8 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.js +8 -0
- package/dist/wrapper.d.ts +115 -0
- package/dist/wrapper.js +417 -0
- package/package.json +35 -11
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -57
- package/CONTRIBUTING.md +0 -532
- package/SECURITY.md +0 -254
- package/assets/demo.mp4 +0 -0
- package/eslint.config.mts +0 -23
- package/examples/config/config.json +0 -19
- package/examples/mcp-server/demo.js +0 -228
- package/examples/mcp-server/package-lock.json +0 -978
- package/examples/mcp-server/package.json +0 -16
- package/examples/mcp-server/pnpm-lock.yaml +0 -745
- package/src/mcp-security-wrapper.ts +0 -570
- package/test/test-server.ts +0 -295
- package/tsconfig.json +0 -16
package/dist/agent.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Amir Mironi
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createAgent = void 0;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const policy_1 = require("./policy");
|
|
12
|
+
const logger_1 = require("./logger");
|
|
13
|
+
const supabase_client_1 = require("./lib/supabase-client");
|
|
14
|
+
const mcp_1 = require("./types/mcp");
|
|
15
|
+
/**
|
|
16
|
+
* Merge policy with defaults
|
|
17
|
+
*/
|
|
18
|
+
const mergePolicyWithDefaults = (policy) => ({
|
|
19
|
+
...policy_1.DEFAULT_POLICY,
|
|
20
|
+
...policy,
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Create an MCP security agent
|
|
24
|
+
* @param serverCommand - Command to start MCP server
|
|
25
|
+
* @param policyConfig - Policy configuration
|
|
26
|
+
* @param supabaseConfig - Optional Supabase configuration
|
|
27
|
+
* @returns Agent functions
|
|
28
|
+
*/
|
|
29
|
+
const createAgent = (serverCommand, policyConfig = {}, supabaseConfig) => {
|
|
30
|
+
const config = mergePolicyWithDefaults(policyConfig);
|
|
31
|
+
const policy = (0, policy_1.createPolicyChecker)(config);
|
|
32
|
+
// Create Supabase client if config provided
|
|
33
|
+
const supabaseClient = supabaseConfig
|
|
34
|
+
? (0, supabase_client_1.createSupabaseClient)(supabaseConfig)
|
|
35
|
+
: undefined;
|
|
36
|
+
const logger = (0, logger_1.createLogger)(config.logPath, supabaseClient, supabaseConfig?.agentId, config.alertWebhook, config.alertOnSeverity);
|
|
37
|
+
const sessionId = (0, mcp_1.generateSessionId)();
|
|
38
|
+
const state = {
|
|
39
|
+
process: null,
|
|
40
|
+
toolCallTimestamps: [],
|
|
41
|
+
clientMessageBuffer: "",
|
|
42
|
+
serverMessageBuffer: "",
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Extract file paths from message parameters
|
|
46
|
+
*/
|
|
47
|
+
const extractFilePaths = (message) => [
|
|
48
|
+
message.params?.arguments?.path,
|
|
49
|
+
message.params?.arguments?.filePath,
|
|
50
|
+
message.params?.arguments?.file,
|
|
51
|
+
message.params?.arguments?.directory,
|
|
52
|
+
message.params?.path,
|
|
53
|
+
message.params?.filePath,
|
|
54
|
+
].filter((path) => typeof path === "string");
|
|
55
|
+
/**
|
|
56
|
+
* Handle tool call with security checks
|
|
57
|
+
*/
|
|
58
|
+
const handleToolCall = (message) => {
|
|
59
|
+
const violations = [];
|
|
60
|
+
let shouldBlock = false;
|
|
61
|
+
// Rate limiting
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
state.toolCallTimestamps.push(now);
|
|
64
|
+
// Clean up old timestamps
|
|
65
|
+
const oneMinuteAgo = now - 60000;
|
|
66
|
+
state.toolCallTimestamps = state.toolCallTimestamps.filter((t) => t > oneMinuteAgo);
|
|
67
|
+
if (!policy.checkRateLimit(state.toolCallTimestamps)) {
|
|
68
|
+
violations.push("Rate limit exceeded for tool calls");
|
|
69
|
+
shouldBlock = policy.isBlockingMode();
|
|
70
|
+
logger.logEvent("RATE_LIMIT_EXCEEDED", "HIGH", {
|
|
71
|
+
method: message.method,
|
|
72
|
+
toolName: message.params?.name,
|
|
73
|
+
}, sessionId);
|
|
74
|
+
}
|
|
75
|
+
// Check tool allowlist / blocklist
|
|
76
|
+
if (message.params?.name) {
|
|
77
|
+
violations.push(...policy.checkToolAccess(message.params.name));
|
|
78
|
+
}
|
|
79
|
+
// Check parameters for security issues
|
|
80
|
+
const paramsStr = JSON.stringify(message.params);
|
|
81
|
+
violations.push(...policy.checkPromptInjection(paramsStr));
|
|
82
|
+
violations.push(...policy.checkSensitiveData(paramsStr));
|
|
83
|
+
violations.push(...policy.checkBlockedPatterns(paramsStr));
|
|
84
|
+
violations.push(...policy.checkSQLInjection(paramsStr));
|
|
85
|
+
violations.push(...policy.checkXSS(paramsStr));
|
|
86
|
+
violations.push(...policy.checkCustomRules(paramsStr));
|
|
87
|
+
// Check file paths
|
|
88
|
+
const filePathParams = extractFilePaths(message);
|
|
89
|
+
for (const filePath of filePathParams) {
|
|
90
|
+
violations.push(...policy.checkFileAccess(filePath));
|
|
91
|
+
}
|
|
92
|
+
// Log tool call
|
|
93
|
+
logger.logEvent("TOOL_CALL", violations.length > 0 ? "HIGH" : "LOW", {
|
|
94
|
+
toolName: message.params?.name,
|
|
95
|
+
hasViolations: violations.length > 0,
|
|
96
|
+
violations,
|
|
97
|
+
}, sessionId);
|
|
98
|
+
// In blocking mode, any violation blocks
|
|
99
|
+
if (violations.length > 0 && policy.isBlockingMode()) {
|
|
100
|
+
shouldBlock = true;
|
|
101
|
+
}
|
|
102
|
+
return { violations, shouldBlock };
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Handle security violations
|
|
106
|
+
*/
|
|
107
|
+
const handleViolations = (message, violations, shouldBlock) => {
|
|
108
|
+
logger.logEvent("SECURITY_VIOLATION", "CRITICAL", {
|
|
109
|
+
violations,
|
|
110
|
+
message,
|
|
111
|
+
blocked: shouldBlock,
|
|
112
|
+
}, sessionId);
|
|
113
|
+
console.error(`\n⚠️ SECURITY VIOLATIONS DETECTED:\n${violations.join("\n")}\n`);
|
|
114
|
+
if (shouldBlock) {
|
|
115
|
+
console.error("🚫 REQUEST BLOCKED\n");
|
|
116
|
+
// Send error response
|
|
117
|
+
if (message.id !== undefined) {
|
|
118
|
+
const errorResponse = {
|
|
119
|
+
jsonrpc: message.jsonrpc,
|
|
120
|
+
id: message.id,
|
|
121
|
+
error: {
|
|
122
|
+
code: -32000,
|
|
123
|
+
message: "Security violation: Request blocked",
|
|
124
|
+
data: { violations },
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Monitor mode: log and warn but still forward
|
|
132
|
+
console.error('⚠️ MONITOR MODE — request forwarded (set mode: "block" to block)\n');
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Handle parse errors
|
|
137
|
+
*/
|
|
138
|
+
const handleParseError = (err, line) => {
|
|
139
|
+
logger.logEvent("PARSE_ERROR", "MEDIUM", {
|
|
140
|
+
error: err instanceof Error ? err.message : String(err),
|
|
141
|
+
line: line.substring(0, 100),
|
|
142
|
+
}, sessionId);
|
|
143
|
+
console.error(`Failed to parse client message: ${err}`);
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Handle sensitive data leak in server response
|
|
147
|
+
*/
|
|
148
|
+
const handleSensitiveDataLeak = (message, violations) => {
|
|
149
|
+
logger.logEvent("SENSITIVE_DATA_LEAK", "CRITICAL", {
|
|
150
|
+
violations,
|
|
151
|
+
responseId: message.id,
|
|
152
|
+
blocked: policy.isBlockingMode(),
|
|
153
|
+
}, sessionId);
|
|
154
|
+
console.error(`\n🚨 SENSITIVE DATA DETECTED IN RESPONSE:\n${violations.join("\n")}\n`);
|
|
155
|
+
if (policy.isBlockingMode()) {
|
|
156
|
+
console.error("🚫 RESPONSE BLOCKED\n");
|
|
157
|
+
// Send sanitized error response
|
|
158
|
+
if (message.id !== undefined) {
|
|
159
|
+
const errorResponse = {
|
|
160
|
+
jsonrpc: message.jsonrpc,
|
|
161
|
+
id: message.id,
|
|
162
|
+
error: {
|
|
163
|
+
code: -32001,
|
|
164
|
+
message: "Security violation: Response contains sensitive data",
|
|
165
|
+
data: { violations },
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
169
|
+
}
|
|
170
|
+
return false; // Don't forward
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.error("⚠️ MONITOR MODE — response forwarded\n");
|
|
174
|
+
return true; // Forward anyway
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Process a single client message
|
|
179
|
+
*/
|
|
180
|
+
const processClientMessage = (line) => {
|
|
181
|
+
try {
|
|
182
|
+
const message = JSON.parse(line);
|
|
183
|
+
logger.logEvent("CLIENT_REQUEST", "LOW", {
|
|
184
|
+
method: message.method,
|
|
185
|
+
id: message.id,
|
|
186
|
+
}, sessionId);
|
|
187
|
+
const violations = [];
|
|
188
|
+
let shouldBlock = false;
|
|
189
|
+
// Handle tool calls with security checks
|
|
190
|
+
if (message.method === "tools/call") {
|
|
191
|
+
const result = handleToolCall(message);
|
|
192
|
+
violations.push(...result.violations);
|
|
193
|
+
shouldBlock = result.shouldBlock;
|
|
194
|
+
}
|
|
195
|
+
// Handle violations
|
|
196
|
+
if (violations.length > 0) {
|
|
197
|
+
handleViolations(message, violations, shouldBlock);
|
|
198
|
+
if (shouldBlock) {
|
|
199
|
+
return; // Don't forward blocked requests
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Forward to server
|
|
203
|
+
if (state.process?.stdin) {
|
|
204
|
+
state.process.stdin.write(line + "\n");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
handleParseError(err, line);
|
|
209
|
+
// Forward unparseable messages
|
|
210
|
+
if (state.process?.stdin) {
|
|
211
|
+
state.process.stdin.write(line + "\n");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Handle client input (buffered line-by-line)
|
|
217
|
+
*/
|
|
218
|
+
const handleClientInput = (input) => {
|
|
219
|
+
state.clientMessageBuffer += input;
|
|
220
|
+
const lines = state.clientMessageBuffer.split("\n");
|
|
221
|
+
state.clientMessageBuffer = lines.pop() || "";
|
|
222
|
+
for (const line of lines) {
|
|
223
|
+
if (line.trim()) {
|
|
224
|
+
processClientMessage(line);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Process a single server message
|
|
230
|
+
*/
|
|
231
|
+
const processServerMessage = (line) => {
|
|
232
|
+
let shouldForward = true;
|
|
233
|
+
try {
|
|
234
|
+
const message = JSON.parse(line);
|
|
235
|
+
// Check for sensitive data in response
|
|
236
|
+
const violations = [];
|
|
237
|
+
const responseStr = JSON.stringify(message.result || message);
|
|
238
|
+
violations.push(...policy.checkSensitiveData(responseStr));
|
|
239
|
+
if (violations.length > 0) {
|
|
240
|
+
shouldForward = handleSensitiveDataLeak(message, violations);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
logger.logEvent("SERVER_RESPONSE", "LOW", {
|
|
244
|
+
id: message.id,
|
|
245
|
+
hasError: !!message.error,
|
|
246
|
+
}, sessionId);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
// Log parse errors for server output
|
|
251
|
+
logger.logEvent("SERVER_PARSE_ERROR", "LOW", {
|
|
252
|
+
error: err instanceof Error ? err.message : String(err),
|
|
253
|
+
line: line.substring(0, 100),
|
|
254
|
+
}, sessionId);
|
|
255
|
+
}
|
|
256
|
+
// Forward the line if not blocked
|
|
257
|
+
if (shouldForward) {
|
|
258
|
+
process.stdout.write(line + "\n");
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Handle server output (buffered line-by-line)
|
|
263
|
+
*/
|
|
264
|
+
const handleServerOutput = (output) => {
|
|
265
|
+
state.serverMessageBuffer += output;
|
|
266
|
+
const lines = state.serverMessageBuffer.split("\n");
|
|
267
|
+
state.serverMessageBuffer = lines.pop() || "";
|
|
268
|
+
for (const line of lines) {
|
|
269
|
+
if (line.trim()) {
|
|
270
|
+
processServerMessage(line);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
/**
|
|
275
|
+
* Handle process exit
|
|
276
|
+
*/
|
|
277
|
+
const handleProcessExit = (code) => {
|
|
278
|
+
logger.logEvent("SERVER_EXIT", "MEDIUM", { exitCode: code }, sessionId);
|
|
279
|
+
console.error("\n=== MCP Security Statistics ===");
|
|
280
|
+
console.error(JSON.stringify(logger.getStatistics(), null, 2));
|
|
281
|
+
const finish = () => setImmediate(() => process.exit(code || 0));
|
|
282
|
+
if (supabaseClient && supabaseConfig?.agentId) {
|
|
283
|
+
supabaseClient
|
|
284
|
+
.updateAgentStatus(supabaseConfig.agentId, "offline")
|
|
285
|
+
.catch(() => { })
|
|
286
|
+
.finally(finish);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
finish();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
return {
|
|
293
|
+
/**
|
|
294
|
+
* Start the MCP server wrapper
|
|
295
|
+
*/
|
|
296
|
+
start: async () => {
|
|
297
|
+
// Fetch policy from Supabase if configured
|
|
298
|
+
if (supabaseClient && supabaseConfig?.agentId) {
|
|
299
|
+
try {
|
|
300
|
+
const remotePolicy = await supabaseClient.fetchPolicy(supabaseConfig.agentId);
|
|
301
|
+
if (remotePolicy) {
|
|
302
|
+
console.log("✓ Loaded policy from Supabase");
|
|
303
|
+
// Merge remote policy with local config
|
|
304
|
+
Object.assign(config, remotePolicy);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.warn("⚠ Failed to fetch policy from Supabase:", error);
|
|
309
|
+
}
|
|
310
|
+
// Update agent status to online
|
|
311
|
+
await supabaseClient.updateAgentStatus(supabaseConfig.agentId, "online");
|
|
312
|
+
}
|
|
313
|
+
state.process = (0, child_process_1.spawn)(serverCommand[0], serverCommand.slice(1), {
|
|
314
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
315
|
+
});
|
|
316
|
+
if (!state.process.stdout ||
|
|
317
|
+
!state.process.stdin ||
|
|
318
|
+
!state.process.stderr) {
|
|
319
|
+
throw new Error("Failed to create child process streams");
|
|
320
|
+
}
|
|
321
|
+
logger.logEvent("SERVER_START", "LOW", {
|
|
322
|
+
command: serverCommand.join(" "),
|
|
323
|
+
pid: state.process.pid,
|
|
324
|
+
}, sessionId);
|
|
325
|
+
// Pipe stderr to parent process
|
|
326
|
+
state.process.stderr.pipe(process.stderr);
|
|
327
|
+
// Handle server output
|
|
328
|
+
state.process.stdout.on("data", (data) => {
|
|
329
|
+
handleServerOutput(data.toString());
|
|
330
|
+
});
|
|
331
|
+
// Handle client input
|
|
332
|
+
process.stdin.on("data", (data) => {
|
|
333
|
+
handleClientInput(data.toString());
|
|
334
|
+
});
|
|
335
|
+
// Handle process exit
|
|
336
|
+
state.process.on("exit", (code) => {
|
|
337
|
+
handleProcessExit(code);
|
|
338
|
+
});
|
|
339
|
+
// Handle process errors
|
|
340
|
+
state.process.on("error", (err) => {
|
|
341
|
+
logger.logEvent("SERVER_ERROR", "HIGH", { error: err.message }, sessionId);
|
|
342
|
+
console.error("Server process error:", err);
|
|
343
|
+
});
|
|
344
|
+
// Graceful shutdown on SIGTERM/SIGINT
|
|
345
|
+
const shutdown = (signal) => {
|
|
346
|
+
logger.logEvent("AGENT_SHUTDOWN", "MEDIUM", { signal }, sessionId);
|
|
347
|
+
if (state.process)
|
|
348
|
+
state.process.kill();
|
|
349
|
+
const finish = () => process.exit(0);
|
|
350
|
+
if (supabaseClient && supabaseConfig?.agentId) {
|
|
351
|
+
supabaseClient
|
|
352
|
+
.updateAgentStatus(supabaseConfig.agentId, "offline")
|
|
353
|
+
.catch(() => { })
|
|
354
|
+
.finally(finish);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
finish();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
361
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
362
|
+
},
|
|
363
|
+
/**
|
|
364
|
+
* Get the security logger instance
|
|
365
|
+
*/
|
|
366
|
+
getLogger: () => logger,
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
exports.createAgent = createAgent;
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Amir Mironi
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Main CLI entry point
|
|
10
|
+
*/
|
|
11
|
+
export declare function main(): Promise<void>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* Copyright (c) 2026 Amir Mironi
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.main = main;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const agent_1 = require("./agent");
|
|
46
|
+
const sse_proxy_1 = require("./sse-proxy");
|
|
47
|
+
const policy_1 = require("./policy");
|
|
48
|
+
const init_1 = require("./init");
|
|
49
|
+
/**
|
|
50
|
+
* Display help message
|
|
51
|
+
*/
|
|
52
|
+
function showHelp() {
|
|
53
|
+
console.log(`
|
|
54
|
+
MCP Security Wrapper - ContextGuard
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
# Auto-patch Claude Desktop config (recommended)
|
|
58
|
+
contextguard init --api-key <key>
|
|
59
|
+
|
|
60
|
+
# stdio transport (Claude Desktop)
|
|
61
|
+
contextguard --server "node server.js" --config config.json
|
|
62
|
+
|
|
63
|
+
# SSE/HTTP transport proxy
|
|
64
|
+
contextguard --transport sse --port 3100 --target http://localhost:3000 --config config.json
|
|
65
|
+
|
|
66
|
+
Commands:
|
|
67
|
+
init Auto-patch claude_desktop_config.json
|
|
68
|
+
|
|
69
|
+
Init options:
|
|
70
|
+
--api-key <key> Your ContextGuard API key (required)
|
|
71
|
+
--config <file> Path to claude_desktop_config.json (auto-detected if omitted)
|
|
72
|
+
--dry-run Preview changes without writing
|
|
73
|
+
|
|
74
|
+
Options:
|
|
75
|
+
--server <cmd> Command to start MCP server (stdio mode)
|
|
76
|
+
--transport <type> stdio | sse | http (default: stdio)
|
|
77
|
+
--port <n> Proxy listen port (SSE/HTTP mode, default: 3100)
|
|
78
|
+
--target <url> Upstream server URL (SSE/HTTP mode)
|
|
79
|
+
--config <file> Path to security config JSON (optional)
|
|
80
|
+
--help Show this help
|
|
81
|
+
|
|
82
|
+
Configuration File Format (JSON):
|
|
83
|
+
{
|
|
84
|
+
"mode": "monitor", // "monitor" (log only) | "block" (block + log)
|
|
85
|
+
"maxToolCallsPerMinute": 30,
|
|
86
|
+
"enablePromptInjectionDetection": true,
|
|
87
|
+
"enableSensitiveDataDetection": true,
|
|
88
|
+
"enablePathTraversalPrevention": true,
|
|
89
|
+
"enableSQLInjectionDetection": true,
|
|
90
|
+
"enableXSSDetection": true,
|
|
91
|
+
"enableSemanticDetection": false,
|
|
92
|
+
"blockedPatterns": ["ignore previous", "system prompt"],
|
|
93
|
+
"allowedFilePaths": ["/safe/path"],
|
|
94
|
+
"allowedTools": [],
|
|
95
|
+
"blockedTools": [],
|
|
96
|
+
"customRules": [{ "name": "my-rule", "pattern": "\\bforbidden\\b" }],
|
|
97
|
+
"alertWebhook": "https://hooks.example.com/alert",
|
|
98
|
+
"alertOnSeverity": ["HIGH", "CRITICAL"],
|
|
99
|
+
"logPath": "mcp_security.log"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
For more information, visit: https://contextguard.dev
|
|
103
|
+
`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Parse command line arguments
|
|
107
|
+
* @returns Parsed arguments
|
|
108
|
+
*/
|
|
109
|
+
function parseArgs() {
|
|
110
|
+
const args = process.argv.slice(2);
|
|
111
|
+
let serverCommand = "";
|
|
112
|
+
let configFile = "";
|
|
113
|
+
let transport = "stdio";
|
|
114
|
+
let port = 3100;
|
|
115
|
+
let targetUrl = "";
|
|
116
|
+
for (let i = 0; i < args.length; i++) {
|
|
117
|
+
if (args[i] === "--server" && args[i + 1]) {
|
|
118
|
+
serverCommand = args[i + 1];
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
else if (args[i] === "--config" && args[i + 1]) {
|
|
122
|
+
configFile = args[i + 1];
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
else if (args[i] === "--transport" && args[i + 1]) {
|
|
126
|
+
transport = args[i + 1];
|
|
127
|
+
i++;
|
|
128
|
+
}
|
|
129
|
+
else if (args[i] === "--port" && args[i + 1]) {
|
|
130
|
+
port = parseInt(args[i + 1], 10);
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
else if (args[i] === "--target" && args[i + 1]) {
|
|
134
|
+
targetUrl = args[i + 1];
|
|
135
|
+
i++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { serverCommand, configFile, transport, port, targetUrl };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parse a shell command string into an array of arguments,
|
|
142
|
+
* respecting single and double quoted strings.
|
|
143
|
+
*/
|
|
144
|
+
function parseCommand(cmd) {
|
|
145
|
+
const tokens = [];
|
|
146
|
+
let current = "";
|
|
147
|
+
let inQuote = "";
|
|
148
|
+
for (const char of cmd) {
|
|
149
|
+
if (inQuote) {
|
|
150
|
+
if (char === inQuote)
|
|
151
|
+
inQuote = "";
|
|
152
|
+
else
|
|
153
|
+
current += char;
|
|
154
|
+
}
|
|
155
|
+
else if (char === '"' || char === "'") {
|
|
156
|
+
inQuote = char;
|
|
157
|
+
}
|
|
158
|
+
else if (char === " ") {
|
|
159
|
+
if (current) {
|
|
160
|
+
tokens.push(current);
|
|
161
|
+
current = "";
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
current += char;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (current)
|
|
169
|
+
tokens.push(current);
|
|
170
|
+
return tokens;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Load configuration from file
|
|
174
|
+
* @param configFile - Path to config file
|
|
175
|
+
* @returns Security configuration
|
|
176
|
+
*/
|
|
177
|
+
function loadConfig(configFile) {
|
|
178
|
+
if (!fs.existsSync(configFile)) {
|
|
179
|
+
console.error(`Error: Config file not found: ${configFile}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
184
|
+
// validateConfig(config);
|
|
185
|
+
return config;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error(`Error: Failed to load config file: ${error}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
// TypeScript doesn't know process.exit() never returns
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Main CLI entry point
|
|
196
|
+
*/
|
|
197
|
+
async function main() {
|
|
198
|
+
const args = process.argv.slice(2);
|
|
199
|
+
// Show help if requested or no args
|
|
200
|
+
if (args.length === 0 || args.includes("--help")) {
|
|
201
|
+
showHelp();
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
204
|
+
// init subcommand — auto-patch claude_desktop_config.json
|
|
205
|
+
if (args[0] === "init") {
|
|
206
|
+
await (0, init_1.runInit)(args.slice(1));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Parse arguments
|
|
210
|
+
const { serverCommand, configFile, transport, port, targetUrl } = parseArgs();
|
|
211
|
+
// Load configuration
|
|
212
|
+
let config = {};
|
|
213
|
+
if (configFile) {
|
|
214
|
+
config = loadConfig(configFile);
|
|
215
|
+
}
|
|
216
|
+
// Merge with defaults
|
|
217
|
+
const fullConfig = { ...policy_1.DEFAULT_POLICY, ...config };
|
|
218
|
+
// CLI flags override config file
|
|
219
|
+
if (transport !== "stdio") {
|
|
220
|
+
fullConfig.transport = transport;
|
|
221
|
+
}
|
|
222
|
+
if (port !== 3100) {
|
|
223
|
+
fullConfig.port = port;
|
|
224
|
+
}
|
|
225
|
+
if (targetUrl) {
|
|
226
|
+
fullConfig.targetUrl = targetUrl;
|
|
227
|
+
}
|
|
228
|
+
const effectiveTransport = fullConfig.transport || "stdio";
|
|
229
|
+
// SSE/HTTP transport mode
|
|
230
|
+
if (effectiveTransport === "sse" || effectiveTransport === "http") {
|
|
231
|
+
if (!fullConfig.targetUrl && !targetUrl) {
|
|
232
|
+
console.error("Error: --target <url> is required for SSE/HTTP transport");
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
const proxy = (0, sse_proxy_1.createSSEProxy)(fullConfig);
|
|
236
|
+
proxy.start(fullConfig.port || 3100, fullConfig.targetUrl || targetUrl);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// stdio transport mode
|
|
240
|
+
if (!serverCommand) {
|
|
241
|
+
console.error("Error: --server argument is required for stdio transport");
|
|
242
|
+
console.error("Run 'contextguard --help' for usage information");
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
// Load Supabase configuration from environment
|
|
246
|
+
const supabaseConfig = process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_KEY
|
|
247
|
+
? {
|
|
248
|
+
url: process.env.SUPABASE_URL,
|
|
249
|
+
serviceKey: process.env.SUPABASE_SERVICE_KEY,
|
|
250
|
+
agentId: process.env.AGENT_ID || "default-agent",
|
|
251
|
+
}
|
|
252
|
+
: undefined;
|
|
253
|
+
if (supabaseConfig) {
|
|
254
|
+
console.log(`✓ Supabase integration enabled (Agent ID: ${supabaseConfig.agentId})`);
|
|
255
|
+
}
|
|
256
|
+
// Create and start agent
|
|
257
|
+
const agent = (0, agent_1.createAgent)(parseCommand(serverCommand), config, supabaseConfig);
|
|
258
|
+
await agent.start();
|
|
259
|
+
}
|
|
260
|
+
// Run CLI if this is the main module
|
|
261
|
+
if (require.main === module) {
|
|
262
|
+
main().catch((err) => {
|
|
263
|
+
console.error("Fatal error:", err);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
});
|
|
266
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Amir Mironi
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { SecurityConfig } from "./types";
|
|
8
|
+
/**
|
|
9
|
+
* Default security configuration values
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_CONFIG: Required<SecurityConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Merges user configuration with defaults
|
|
14
|
+
* @param userConfig - User-provided configuration
|
|
15
|
+
* @returns Merged configuration with defaults
|
|
16
|
+
*/
|
|
17
|
+
export declare function mergeConfig(userConfig?: SecurityConfig): Required<SecurityConfig>;
|
|
18
|
+
/**
|
|
19
|
+
* Validates configuration values
|
|
20
|
+
* @param config - Configuration to validate
|
|
21
|
+
* @throws Error if configuration is invalid
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateConfig(config: SecurityConfig): void;
|