contextguard 0.1.7 → 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.
Files changed (60) hide show
  1. package/LICENSE +23 -17
  2. package/README.md +157 -109
  3. package/dist/agent.d.ts +24 -0
  4. package/dist/agent.js +369 -0
  5. package/dist/cli.d.ts +11 -0
  6. package/dist/cli.js +266 -0
  7. package/dist/config.d.ts +23 -0
  8. package/dist/config.js +56 -0
  9. package/dist/database.d.ts +116 -0
  10. package/dist/database.js +291 -0
  11. package/dist/index.d.ts +16 -0
  12. package/dist/index.js +18 -0
  13. package/dist/init.d.ts +7 -0
  14. package/dist/init.js +173 -0
  15. package/dist/lib/supabase-client.d.ts +27 -0
  16. package/dist/lib/supabase-client.js +97 -0
  17. package/dist/logger.d.ts +36 -0
  18. package/dist/logger.js +145 -0
  19. package/dist/mcp-security-wrapper.d.ts +84 -0
  20. package/dist/mcp-security-wrapper.js +394 -120
  21. package/dist/mcp-traceability-integration.d.ts +118 -0
  22. package/dist/mcp-traceability-integration.js +302 -0
  23. package/dist/policy.d.ts +30 -0
  24. package/dist/policy.js +273 -0
  25. package/dist/premium-features.d.ts +364 -0
  26. package/dist/premium-features.js +950 -0
  27. package/dist/security-logger.d.ts +45 -0
  28. package/dist/security-logger.js +125 -0
  29. package/dist/security-policy.d.ts +55 -0
  30. package/dist/security-policy.js +140 -0
  31. package/dist/semantic-detector.d.ts +21 -0
  32. package/dist/semantic-detector.js +49 -0
  33. package/dist/sse-proxy.d.ts +21 -0
  34. package/dist/sse-proxy.js +276 -0
  35. package/dist/supabase-client.d.ts +27 -0
  36. package/dist/supabase-client.js +89 -0
  37. package/dist/types/database.types.d.ts +220 -0
  38. package/dist/types/database.types.js +8 -0
  39. package/dist/types/mcp.d.ts +27 -0
  40. package/dist/types/mcp.js +15 -0
  41. package/dist/types/types.d.ts +65 -0
  42. package/dist/types/types.js +8 -0
  43. package/dist/types.d.ts +84 -0
  44. package/dist/types.js +8 -0
  45. package/dist/wrapper.d.ts +115 -0
  46. package/dist/wrapper.js +417 -0
  47. package/package.json +35 -10
  48. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -57
  49. package/CONTRIBUTING.md +0 -532
  50. package/SECURITY.md +0 -254
  51. package/assets/demo.mp4 +0 -0
  52. package/eslint.config.mts +0 -23
  53. package/examples/config/config.json +0 -19
  54. package/examples/mcp-server/demo.js +0 -228
  55. package/examples/mcp-server/package-lock.json +0 -978
  56. package/examples/mcp-server/package.json +0 -16
  57. package/examples/mcp-server/pnpm-lock.yaml +0 -745
  58. package/src/mcp-security-wrapper.ts +0 -529
  59. package/test/test-server.ts +0 -295
  60. package/tsconfig.json +0 -16
@@ -1,529 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Copyright (c) 2025 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
-
10
- import { spawn, ChildProcess } from "child_process";
11
- import * as fs from "fs";
12
- import { createHash } from "crypto";
13
-
14
- interface SecurityConfig {
15
- maxToolCallsPerMinute?: number;
16
- blockedPatterns?: string[];
17
- allowedFilePaths?: string[];
18
- alertThreshold?: number;
19
- enablePromptInjectionDetection?: boolean;
20
- enableSensitiveDataDetection?: boolean;
21
- logPath?: string;
22
- }
23
-
24
- interface SecurityEvent {
25
- timestamp: string;
26
- eventType: string;
27
- severity: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
28
- details: Record<string, unknown>;
29
- sessionId: string;
30
- }
31
-
32
- interface MCPMessage {
33
- jsonrpc: string;
34
- id?: string | number;
35
- method?: string;
36
- params?: {
37
- name?: string;
38
- arguments?: Record<string, string>;
39
- path?: string;
40
- filePath?: string;
41
- [key: string]: unknown;
42
- };
43
- result?: unknown;
44
- error?: unknown;
45
- }
46
-
47
- class SecurityPolicy {
48
- private config: SecurityConfig;
49
- private sensitiveDataPatterns: RegExp[];
50
- private promptInjectionPatterns: RegExp[];
51
-
52
- constructor(config: SecurityConfig) {
53
- this.config = {
54
- maxToolCallsPerMinute: 30,
55
- blockedPatterns: [],
56
- allowedFilePaths: [],
57
- alertThreshold: 5,
58
- enablePromptInjectionDetection: true,
59
- enableSensitiveDataDetection: true,
60
- ...config,
61
- };
62
-
63
- // Sensitive data patterns
64
- this.sensitiveDataPatterns = [
65
- /(?:password|secret|api[_-]?key|token)\s*[:=]\s*['"]?[\w\-.]+['"]?/gi,
66
- /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email
67
- /\b\d{3}-\d{2}-\d{4}\b/g, // SSN
68
- /sk-[a-zA-Z0-9]{20,}/g, // OpenAI API keys (20+ chars)
69
- /ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens
70
- /AKIA[0-9A-Z]{16}/g, // AWS Access Keys
71
- ];
72
-
73
- // Prompt injection patterns
74
- this.promptInjectionPatterns = [
75
- /ignore\s+(previous|all)\s+(instructions|prompts)/gi,
76
- /system:\s*you\s+are\s+now/gi,
77
- /forget\s+(everything|all)/gi,
78
- /new\s+instructions:/gi,
79
- /\[INST\].*?\[\/INST\]/gs,
80
- /<\|im_start\|>/g,
81
- /disregard\s+previous/gi,
82
- /override\s+previous/gi,
83
- ];
84
- }
85
-
86
- checkPromptInjection(text: string): string[] {
87
- if (!this.config.enablePromptInjectionDetection) return [];
88
- const violations: string[] = [];
89
- for (const pattern of this.promptInjectionPatterns) {
90
- const matches = text.match(pattern);
91
- if (matches) {
92
- violations.push(
93
- `Potential prompt injection detected: "${matches[0].substring(
94
- 0,
95
- 50
96
- )}..."`
97
- );
98
- }
99
- }
100
- return violations;
101
- }
102
-
103
- checkSensitiveData(text: string): string[] {
104
- if (!this.config.enableSensitiveDataDetection) return [];
105
- const violations: string[] = [];
106
- for (const pattern of this.sensitiveDataPatterns) {
107
- const matches = text.match(pattern);
108
- if (matches) {
109
- violations.push(
110
- `Sensitive data pattern detected (redacted): ${pattern.source.substring(
111
- 0,
112
- 30
113
- )}...`
114
- );
115
- }
116
- }
117
- return violations;
118
- }
119
-
120
- checkFileAccess(filePath: string): string[] {
121
- const violations: string[] = [];
122
- if (filePath.includes("..")) {
123
- violations.push(`Path traversal attempt detected: ${filePath}`);
124
- }
125
- const dangerousPaths = [
126
- "/etc",
127
- "/root",
128
- "/sys",
129
- "/proc",
130
- "C:\\Windows\\System32",
131
- ];
132
- if (dangerousPaths.some((dangerous) => filePath.startsWith(dangerous))) {
133
- violations.push(`Access to dangerous path detected: ${filePath}`);
134
- }
135
- if (
136
- this.config.allowedFilePaths &&
137
- this.config.allowedFilePaths.length > 0
138
- ) {
139
- const isAllowed = this.config.allowedFilePaths.some((allowed) =>
140
- filePath.startsWith(allowed)
141
- );
142
- if (!isAllowed) {
143
- violations.push(`File path not in allowed list: ${filePath}`);
144
- }
145
- }
146
- return violations;
147
- }
148
-
149
- checkRateLimit(timestamps: number[]): boolean {
150
- const oneMinuteAgo = Date.now() - 60000;
151
- const recentCalls = timestamps.filter((t) => t > oneMinuteAgo);
152
- return recentCalls.length < (this.config.maxToolCallsPerMinute || 30);
153
- }
154
- }
155
-
156
- class SecurityLogger {
157
- private logFile: string;
158
- private events: SecurityEvent[] = [];
159
-
160
- constructor(logFile: string = "mcp_security.log") {
161
- this.logFile = logFile;
162
- }
163
-
164
- logEvent(
165
- eventType: string,
166
- severity: SecurityEvent["severity"],
167
- details: Record<string, unknown>,
168
- sessionId: string
169
- ): void {
170
- const event: SecurityEvent = {
171
- timestamp: new Date().toISOString(),
172
- eventType,
173
- severity,
174
- details,
175
- sessionId,
176
- };
177
- this.events.push(event);
178
- fs.appendFileSync(this.logFile, JSON.stringify(event) + "\n");
179
- if (severity === "HIGH" || severity === "CRITICAL") {
180
- console.error(
181
- `[SECURITY ALERT] ${eventType}: ${JSON.stringify(details)}`
182
- );
183
- }
184
- }
185
-
186
- getStatistics(): Record<string, unknown> {
187
- return {
188
- totalEvents: this.events.length,
189
- eventsByType: this.countByField("eventType"),
190
- eventsBySeverity: this.countByField("severity"),
191
- recentEvents: this.events.slice(-10),
192
- };
193
- }
194
-
195
- private countByField(field: keyof SecurityEvent): Record<string, number> {
196
- const counts: Record<string, number> = {};
197
- for (const event of this.events) {
198
- const value = String(event[field]);
199
- counts[value] = (counts[value] || 0) + 1;
200
- }
201
- return counts;
202
- }
203
- }
204
-
205
- class MCPSecurityWrapper {
206
- private serverCommand: string[];
207
- private policy: SecurityPolicy;
208
- private logger: SecurityLogger;
209
- private process: ChildProcess | null = null;
210
- private toolCallTimestamps: number[] = [];
211
- private sessionId: string;
212
- private clientMessageBuffer: string = "";
213
- private serverMessageBuffer: string = "";
214
-
215
- constructor(
216
- serverCommand: string[],
217
- policy: SecurityPolicy,
218
- logger: SecurityLogger
219
- ) {
220
- this.serverCommand = serverCommand;
221
- this.policy = policy;
222
- this.logger = logger;
223
- this.sessionId = createHash("md5")
224
- .update(Date.now().toString())
225
- .digest("hex")
226
- .substring(0, 8);
227
- }
228
-
229
- async start(): Promise<void> {
230
- this.process = spawn(this.serverCommand[0], this.serverCommand.slice(1), {
231
- stdio: ["pipe", "pipe", "pipe"],
232
- });
233
-
234
- if (!this.process.stdout || !this.process.stdin || !this.process.stderr) {
235
- throw new Error("Failed to create child process streams");
236
- }
237
-
238
- this.logger.logEvent(
239
- "SERVER_START",
240
- "LOW",
241
- {
242
- command: this.serverCommand.join(" "),
243
- pid: this.process.pid,
244
- },
245
- this.sessionId
246
- );
247
-
248
- this.process.stderr.pipe(process.stderr);
249
-
250
- this.process.stdout.on("data", (data: Buffer) => {
251
- const output = data.toString();
252
- this.handleServerOutput(output);
253
- });
254
-
255
- process.stdin.on("data", (data: Buffer) => {
256
- const input = data.toString();
257
- this.handleClientInput(input);
258
- });
259
-
260
- this.process.on("exit", (code) => {
261
- this.logger.logEvent(
262
- "SERVER_EXIT",
263
- "MEDIUM",
264
- {
265
- exitCode: code,
266
- },
267
- this.sessionId
268
- );
269
- console.error("\n=== MCP Security Statistics ===");
270
- console.error(JSON.stringify(this.logger.getStatistics(), null, 2));
271
- // Use setImmediate to allow pending I/O to complete
272
- setImmediate(() => {
273
- process.exit(code || 0);
274
- });
275
- });
276
-
277
- this.process.on("error", (err) => {
278
- this.logger.logEvent(
279
- "SERVER_ERROR",
280
- "HIGH",
281
- {
282
- error: err.message,
283
- },
284
- this.sessionId
285
- );
286
- console.error("Server process error:", err);
287
- });
288
- }
289
-
290
- private handleClientInput(input: string): void {
291
- this.clientMessageBuffer += input;
292
- const lines = this.clientMessageBuffer.split("\n");
293
- this.clientMessageBuffer = lines.pop() || "";
294
- for (const line of lines) {
295
- if (line.trim()) {
296
- this.processClientMessage(line);
297
- }
298
- }
299
- }
300
-
301
- private processClientMessage(line: string): void {
302
- try {
303
- const message: MCPMessage = JSON.parse(line);
304
- this.logger.logEvent(
305
- "CLIENT_REQUEST",
306
- "LOW",
307
- {
308
- method: message.method,
309
- id: message.id,
310
- },
311
- this.sessionId
312
- );
313
-
314
- const violations: string[] = [];
315
- let shouldBlock = false;
316
-
317
- if (message.method === "tools/call") {
318
- const now = Date.now();
319
- this.toolCallTimestamps.push(now);
320
-
321
- // Clean up old timestamps to prevent memory leak
322
- const oneMinuteAgo = now - 60000;
323
- this.toolCallTimestamps = this.toolCallTimestamps.filter(
324
- (t) => t > oneMinuteAgo
325
- );
326
-
327
- if (!this.policy.checkRateLimit(this.toolCallTimestamps)) {
328
- violations.push("Rate limit exceeded for tool calls");
329
- shouldBlock = true;
330
- this.logger.logEvent(
331
- "RATE_LIMIT_EXCEEDED",
332
- "HIGH",
333
- {
334
- method: message.method,
335
- toolName: message.params?.name,
336
- },
337
- this.sessionId
338
- );
339
- }
340
-
341
- const paramsStr = JSON.stringify(message.params);
342
- const injectionViolations = this.policy.checkPromptInjection(paramsStr);
343
- violations.push(...injectionViolations);
344
- const sensitiveViolations = this.policy.checkSensitiveData(paramsStr);
345
- violations.push(...sensitiveViolations);
346
-
347
- // Check multiple possible file path parameter locations
348
- const filePathParams = [
349
- message.params?.arguments?.path,
350
- message.params?.arguments?.filePath,
351
- message.params?.arguments?.file,
352
- message.params?.arguments?.directory,
353
- message.params?.path,
354
- message.params?.filePath,
355
- ].filter((path): path is string => typeof path === "string");
356
-
357
- for (const filePath of filePathParams) {
358
- const fileViolations = this.policy.checkFileAccess(filePath);
359
- violations.push(...fileViolations);
360
- }
361
-
362
- this.logger.logEvent(
363
- "TOOL_CALL",
364
- violations.length > 0 ? "HIGH" : "LOW",
365
- {
366
- toolName: message.params?.name,
367
- hasViolations: violations.length > 0,
368
- violations,
369
- },
370
- this.sessionId
371
- );
372
- }
373
-
374
- if (violations.length > 0) {
375
- this.logger.logEvent(
376
- "SECURITY_VIOLATION",
377
- "CRITICAL",
378
- {
379
- violations,
380
- message: message,
381
- blocked: shouldBlock,
382
- },
383
- this.sessionId
384
- );
385
- console.error(
386
- `\n⚠️ SECURITY VIOLATIONS DETECTED:\n${violations.join("\n")}\n`
387
- );
388
-
389
- if (shouldBlock) {
390
- console.error("🚫 REQUEST BLOCKED\n");
391
- // Send error response back to client
392
- if (message.id !== undefined) {
393
- const errorResponse: MCPMessage = {
394
- jsonrpc: message.jsonrpc,
395
- id: message.id,
396
- error: {
397
- code: -32000,
398
- message: "Security violation: Request blocked",
399
- data: { violations },
400
- },
401
- };
402
- process.stdout.write(JSON.stringify(errorResponse) + "\n");
403
- }
404
- return; // Don't forward to server
405
- }
406
- }
407
-
408
- if (this.process && this.process.stdin) {
409
- this.process.stdin.write(line + "\n");
410
- }
411
- } catch (err) {
412
- this.logger.logEvent(
413
- "PARSE_ERROR",
414
- "MEDIUM",
415
- {
416
- error: err instanceof Error ? err.message : String(err),
417
- line: line.substring(0, 100),
418
- },
419
- this.sessionId
420
- );
421
- console.error(`Failed to parse client message: ${err}`);
422
- // Forward unparseable messages
423
- if (this.process && this.process.stdin) {
424
- this.process.stdin.write(line + "\n");
425
- }
426
- }
427
- }
428
-
429
- private handleServerOutput(output: string): void {
430
- // Forward output immediately
431
- process.stdout.write(output);
432
-
433
- // Buffer and parse for logging
434
- this.serverMessageBuffer += output;
435
- const lines = this.serverMessageBuffer.split("\n");
436
- this.serverMessageBuffer = lines.pop() || "";
437
-
438
- for (const line of lines) {
439
- if (line.trim()) {
440
- try {
441
- const message: MCPMessage = JSON.parse(line);
442
- this.logger.logEvent(
443
- "SERVER_RESPONSE",
444
- "LOW",
445
- {
446
- id: message.id,
447
- hasError: !!message.error,
448
- },
449
- this.sessionId
450
- );
451
- } catch (err) {
452
- // Log parse errors for server output
453
- this.logger.logEvent(
454
- "SERVER_PARSE_ERROR",
455
- "LOW",
456
- {
457
- error: err instanceof Error ? err.message : String(err),
458
- line: line.substring(0, 100),
459
- },
460
- this.sessionId
461
- );
462
- }
463
- }
464
- }
465
- }
466
- }
467
-
468
- async function main() {
469
- const args = process.argv.slice(2);
470
- if (args.length === 0 || args.includes("--help")) {
471
- console.log(`
472
- MCP Security Wrapper - MVP
473
-
474
- Usage:
475
- contextguard --server "node server.js" --config config.json
476
-
477
- Options:
478
- --server <command> Command to start the MCP server (required)
479
- --config <file> Path to security config JSON file (optional)
480
- --help Show this help message
481
-
482
- `);
483
- process.exit(0);
484
- }
485
-
486
- let serverCommand: string = "";
487
- let configFile: string = "";
488
-
489
- for (let i = 0; i < args.length; i++) {
490
- if (args[i] === "--server" && args[i + 1]) {
491
- serverCommand = args[i + 1];
492
- i++;
493
- } else if (args[i] === "--config" && args[i + 1]) {
494
- configFile = args[i + 1];
495
- i++;
496
- }
497
- }
498
-
499
- if (!serverCommand) {
500
- console.error("Error: --server argument is required");
501
- process.exit(1);
502
- }
503
-
504
- let config: SecurityConfig = {};
505
- if (configFile && fs.existsSync(configFile)) {
506
- config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
507
- }
508
-
509
- const policy = new SecurityPolicy(config);
510
- const logger = new SecurityLogger(config.logPath);
511
- const wrapper = new MCPSecurityWrapper(
512
- serverCommand.split(" "),
513
- policy,
514
- logger
515
- );
516
-
517
- // console.log("ContextGuard is running");
518
-
519
- await wrapper.start();
520
- }
521
-
522
- if (require.main === module) {
523
- main().catch((err) => {
524
- console.error("Fatal error:", err);
525
- process.exit(1);
526
- });
527
- }
528
-
529
- export { MCPSecurityWrapper, SecurityPolicy, SecurityLogger };