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.
- 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 +394 -120
- 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 -10
- 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 -529
- package/test/test-server.ts +0 -295
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Re-export AgentPolicy from database types
|
|
9
|
+
*/
|
|
10
|
+
export type { AgentPolicy } from "./database.types";
|
|
11
|
+
/**
|
|
12
|
+
* Security event severity levels
|
|
13
|
+
*/
|
|
14
|
+
export type SecuritySeverity = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
15
|
+
/**
|
|
16
|
+
* Security event logged by the system
|
|
17
|
+
*/
|
|
18
|
+
export interface SecurityEvent {
|
|
19
|
+
/** ISO timestamp of the event */
|
|
20
|
+
timestamp: string;
|
|
21
|
+
/** Type of security event */
|
|
22
|
+
eventType: string;
|
|
23
|
+
/** Severity level */
|
|
24
|
+
severity: SecuritySeverity;
|
|
25
|
+
/** Additional event details */
|
|
26
|
+
details: Record<string, unknown>;
|
|
27
|
+
/** Session identifier */
|
|
28
|
+
sessionId: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* MCP JSON-RPC message structure
|
|
32
|
+
*/
|
|
33
|
+
export interface MCPMessage {
|
|
34
|
+
/** JSON-RPC version */
|
|
35
|
+
jsonrpc: string;
|
|
36
|
+
/** Request/response ID */
|
|
37
|
+
id?: string | number;
|
|
38
|
+
/** Method name for requests */
|
|
39
|
+
method?: string;
|
|
40
|
+
/** Method parameters */
|
|
41
|
+
params?: {
|
|
42
|
+
name?: string;
|
|
43
|
+
arguments?: Record<string, string>;
|
|
44
|
+
path?: string;
|
|
45
|
+
filePath?: string;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
};
|
|
48
|
+
/** Response result */
|
|
49
|
+
result?: unknown;
|
|
50
|
+
/** Error object */
|
|
51
|
+
error?: unknown;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Statistics about security events
|
|
55
|
+
*/
|
|
56
|
+
export interface SecurityStatistics {
|
|
57
|
+
/** Total number of events logged */
|
|
58
|
+
totalEvents: number;
|
|
59
|
+
/** Events grouped by type */
|
|
60
|
+
eventsByType: Record<string, number>;
|
|
61
|
+
/** Events grouped by severity */
|
|
62
|
+
eventsBySeverity: Record<string, number>;
|
|
63
|
+
/** Most recent events */
|
|
64
|
+
recentEvents: SecurityEvent[];
|
|
65
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Security configuration options for the MCP wrapper
|
|
9
|
+
*/
|
|
10
|
+
export interface CgPolicyType {
|
|
11
|
+
/** Maximum number of tool calls allowed per minute (default: 30) */
|
|
12
|
+
maxToolCallsPerMinute?: number;
|
|
13
|
+
/** Patterns to block in tool calls */
|
|
14
|
+
blockedPatterns?: string[];
|
|
15
|
+
/** Allowed file paths for file operations (empty = all allowed) */
|
|
16
|
+
allowedFilePaths?: string[];
|
|
17
|
+
/** Number of violations before triggering alerts (default: 5) */
|
|
18
|
+
alertThreshold?: number;
|
|
19
|
+
/** Enable prompt injection detection (default: true) */
|
|
20
|
+
enablePromptInjectionDetection?: boolean;
|
|
21
|
+
/** Enable sensitive data detection (default: true) */
|
|
22
|
+
enableSensitiveDataDetection?: boolean;
|
|
23
|
+
/** Path to security log file */
|
|
24
|
+
logPath?: string;
|
|
25
|
+
/** Enable pro features (requires license) */
|
|
26
|
+
enableProFeatures?: boolean;
|
|
27
|
+
/** License file path for pro features */
|
|
28
|
+
licenseFilePath?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Security event severity levels
|
|
32
|
+
*/
|
|
33
|
+
export type SecuritySeverity = "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
34
|
+
/**
|
|
35
|
+
* Security event logged by the system
|
|
36
|
+
*/
|
|
37
|
+
export interface SecurityEvent {
|
|
38
|
+
/** ISO timestamp of the event */
|
|
39
|
+
timestamp: string;
|
|
40
|
+
/** Type of security event */
|
|
41
|
+
eventType: string;
|
|
42
|
+
/** Severity level */
|
|
43
|
+
severity: SecuritySeverity;
|
|
44
|
+
/** Additional event details */
|
|
45
|
+
details: Record<string, unknown>;
|
|
46
|
+
/** Session identifier */
|
|
47
|
+
sessionId: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* MCP JSON-RPC message structure
|
|
51
|
+
*/
|
|
52
|
+
export interface MCPMessage {
|
|
53
|
+
/** JSON-RPC version */
|
|
54
|
+
jsonrpc: string;
|
|
55
|
+
/** Request/response ID */
|
|
56
|
+
id?: string | number;
|
|
57
|
+
/** Method name for requests */
|
|
58
|
+
method?: string;
|
|
59
|
+
/** Method parameters */
|
|
60
|
+
params?: {
|
|
61
|
+
name?: string;
|
|
62
|
+
arguments?: Record<string, string>;
|
|
63
|
+
path?: string;
|
|
64
|
+
filePath?: string;
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
};
|
|
67
|
+
/** Response result */
|
|
68
|
+
result?: unknown;
|
|
69
|
+
/** Error object */
|
|
70
|
+
error?: unknown;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Statistics about security events
|
|
74
|
+
*/
|
|
75
|
+
export interface SecurityStatistics {
|
|
76
|
+
/** Total number of events logged */
|
|
77
|
+
totalEvents: number;
|
|
78
|
+
/** Events grouped by type */
|
|
79
|
+
eventsByType: Record<string, number>;
|
|
80
|
+
/** Events grouped by severity */
|
|
81
|
+
eventsBySeverity: Record<string, number>;
|
|
82
|
+
/** Most recent events */
|
|
83
|
+
recentEvents: SecurityEvent[];
|
|
84
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
import { SecurityLogger } from "./security-logger";
|
|
9
|
+
import { MCPTraceabilityManager } from "./premium-features";
|
|
10
|
+
/**
|
|
11
|
+
* MCP Security Wrapper
|
|
12
|
+
* Wraps MCP servers with security monitoring and optional pro features
|
|
13
|
+
*/
|
|
14
|
+
export declare class MCPSecurityWrapper {
|
|
15
|
+
private serverCommand;
|
|
16
|
+
private policy;
|
|
17
|
+
private logger;
|
|
18
|
+
private process;
|
|
19
|
+
private toolCallTimestamps;
|
|
20
|
+
private sessionId;
|
|
21
|
+
private clientMessageBuffer;
|
|
22
|
+
private serverMessageBuffer;
|
|
23
|
+
private licenseManager?;
|
|
24
|
+
private traceabilityManager?;
|
|
25
|
+
private contextTracker?;
|
|
26
|
+
private proFeaturesEnabled;
|
|
27
|
+
private databaseReporter?;
|
|
28
|
+
private databaseEnabled;
|
|
29
|
+
constructor(serverCommand: string[], config?: SecurityConfig);
|
|
30
|
+
/**
|
|
31
|
+
* Generate a unique session ID
|
|
32
|
+
* @returns 8-character hex session ID
|
|
33
|
+
*/
|
|
34
|
+
private generateSessionId;
|
|
35
|
+
/**
|
|
36
|
+
* Initialize pro features if license is valid
|
|
37
|
+
* @param licenseFilePath - Path to license file
|
|
38
|
+
*/
|
|
39
|
+
private initializeProFeatures;
|
|
40
|
+
/**
|
|
41
|
+
* Initialize database reporting for UI integration
|
|
42
|
+
* @param dbConfig - Database configuration
|
|
43
|
+
*/
|
|
44
|
+
private initializeDatabaseReporting;
|
|
45
|
+
/**
|
|
46
|
+
* Start the MCP server wrapper
|
|
47
|
+
*/
|
|
48
|
+
start(): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Handle client input (buffered line-by-line)
|
|
51
|
+
* @param input - Raw input from client
|
|
52
|
+
*/
|
|
53
|
+
private handleClientInput;
|
|
54
|
+
/**
|
|
55
|
+
* Process a single client message
|
|
56
|
+
* @param line - JSON-RPC message line
|
|
57
|
+
*/
|
|
58
|
+
private processClientMessage;
|
|
59
|
+
/**
|
|
60
|
+
* Handle tool call with security checks and traceability
|
|
61
|
+
* @param message - MCP message
|
|
62
|
+
* @returns Violations and block status
|
|
63
|
+
*/
|
|
64
|
+
private handleToolCall;
|
|
65
|
+
/**
|
|
66
|
+
* Extract file paths from message parameters
|
|
67
|
+
* @param message - MCP message
|
|
68
|
+
* @returns Array of file paths
|
|
69
|
+
*/
|
|
70
|
+
private extractFilePaths;
|
|
71
|
+
/**
|
|
72
|
+
* Handle security violations
|
|
73
|
+
* @param message - Original message
|
|
74
|
+
* @param violations - List of violations
|
|
75
|
+
* @param shouldBlock - Whether to block the request
|
|
76
|
+
*/
|
|
77
|
+
private handleViolations;
|
|
78
|
+
/**
|
|
79
|
+
* Handle parse errors
|
|
80
|
+
* @param err - Error object
|
|
81
|
+
* @param line - Original line that failed to parse
|
|
82
|
+
*/
|
|
83
|
+
private handleParseError;
|
|
84
|
+
/**
|
|
85
|
+
* Handle server output (buffered line-by-line)
|
|
86
|
+
* @param output - Raw output from server
|
|
87
|
+
*/
|
|
88
|
+
private handleServerOutput;
|
|
89
|
+
/**
|
|
90
|
+
* Process a single server message
|
|
91
|
+
* @param line - JSON-RPC message line
|
|
92
|
+
*/
|
|
93
|
+
private processServerMessage;
|
|
94
|
+
/**
|
|
95
|
+
* Handle sensitive data leak in server response
|
|
96
|
+
* @param message - Server message
|
|
97
|
+
* @param violations - List of violations
|
|
98
|
+
*/
|
|
99
|
+
private handleSensitiveDataLeak;
|
|
100
|
+
/**
|
|
101
|
+
* Handle process exit
|
|
102
|
+
* @param code - Exit code
|
|
103
|
+
*/
|
|
104
|
+
private handleProcessExit;
|
|
105
|
+
/**
|
|
106
|
+
* Get the security logger instance
|
|
107
|
+
* @returns SecurityLogger instance
|
|
108
|
+
*/
|
|
109
|
+
getLogger(): SecurityLogger;
|
|
110
|
+
/**
|
|
111
|
+
* Get the traceability manager (pro feature)
|
|
112
|
+
* @returns MCPTraceabilityManager instance or undefined
|
|
113
|
+
*/
|
|
114
|
+
getTraceabilityManager(): MCPTraceabilityManager | undefined;
|
|
115
|
+
}
|
package/dist/wrapper.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 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.MCPSecurityWrapper = void 0;
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const crypto_1 = require("crypto");
|
|
12
|
+
const security_policy_1 = require("./security-policy");
|
|
13
|
+
const security_logger_1 = require("./security-logger");
|
|
14
|
+
const config_1 = require("./config");
|
|
15
|
+
const premium_features_1 = require("./premium-features");
|
|
16
|
+
const database_1 = require("./database");
|
|
17
|
+
/**
|
|
18
|
+
* MCP Security Wrapper
|
|
19
|
+
* Wraps MCP servers with security monitoring and optional pro features
|
|
20
|
+
*/
|
|
21
|
+
class MCPSecurityWrapper {
|
|
22
|
+
constructor(serverCommand, config = {}) {
|
|
23
|
+
this.process = null;
|
|
24
|
+
this.toolCallTimestamps = [];
|
|
25
|
+
this.clientMessageBuffer = "";
|
|
26
|
+
this.serverMessageBuffer = "";
|
|
27
|
+
this.proFeaturesEnabled = false;
|
|
28
|
+
this.databaseEnabled = false;
|
|
29
|
+
const fullConfig = (0, config_1.mergeConfig)(config);
|
|
30
|
+
this.serverCommand = serverCommand;
|
|
31
|
+
this.policy = new security_policy_1.SecurityPolicy(fullConfig);
|
|
32
|
+
this.logger = new security_logger_1.SecurityLogger(fullConfig.logPath);
|
|
33
|
+
this.sessionId = this.generateSessionId();
|
|
34
|
+
// Initialize pro features if enabled
|
|
35
|
+
if (fullConfig.enableProFeatures) {
|
|
36
|
+
this.initializeProFeatures(fullConfig.licenseFilePath);
|
|
37
|
+
}
|
|
38
|
+
// Initialize database reporting if enabled
|
|
39
|
+
if (fullConfig.enableDatabaseReporting) {
|
|
40
|
+
this.initializeDatabaseReporting(fullConfig.database);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate a unique session ID
|
|
45
|
+
* @returns 8-character hex session ID
|
|
46
|
+
*/
|
|
47
|
+
generateSessionId() {
|
|
48
|
+
return (0, crypto_1.createHash)("md5")
|
|
49
|
+
.update(Date.now().toString())
|
|
50
|
+
.digest("hex")
|
|
51
|
+
.substring(0, 8);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Initialize pro features if license is valid
|
|
55
|
+
* @param licenseFilePath - Path to license file
|
|
56
|
+
*/
|
|
57
|
+
initializeProFeatures(licenseFilePath) {
|
|
58
|
+
try {
|
|
59
|
+
this.licenseManager = new premium_features_1.LicenseManager(licenseFilePath);
|
|
60
|
+
if (this.licenseManager.validateLicense()) {
|
|
61
|
+
this.proFeaturesEnabled = true;
|
|
62
|
+
this.traceabilityManager = new premium_features_1.MCPTraceabilityManager(this.licenseManager);
|
|
63
|
+
this.contextTracker = new premium_features_1.ContextTracker(this.licenseManager);
|
|
64
|
+
console.log(`✓ Pro features enabled (${this.licenseManager.getTier()} tier)`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.warn("⚠ Invalid or expired license. Pro features disabled.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.warn("⚠ Failed to initialize pro features:", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Initialize database reporting for UI integration
|
|
76
|
+
* @param dbConfig - Database configuration
|
|
77
|
+
*/
|
|
78
|
+
initializeDatabaseReporting(dbConfig) {
|
|
79
|
+
try {
|
|
80
|
+
const databaseConfig = {
|
|
81
|
+
type: dbConfig.type || "json",
|
|
82
|
+
dbPath: dbConfig.path,
|
|
83
|
+
batchSize: dbConfig.batchSize,
|
|
84
|
+
flushIntervalMs: dbConfig.flushIntervalMs,
|
|
85
|
+
enabled: true,
|
|
86
|
+
};
|
|
87
|
+
this.databaseReporter = database_1.DatabaseReporterFactory.create(databaseConfig);
|
|
88
|
+
this.databaseEnabled = true;
|
|
89
|
+
console.log(`✓ Database reporting enabled (${databaseConfig.type})`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.warn("⚠ Failed to initialize database reporting:", error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Start the MCP server wrapper
|
|
97
|
+
*/
|
|
98
|
+
async start() {
|
|
99
|
+
this.process = (0, child_process_1.spawn)(this.serverCommand[0], this.serverCommand.slice(1), {
|
|
100
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
101
|
+
});
|
|
102
|
+
if (!this.process.stdout || !this.process.stdin || !this.process.stderr) {
|
|
103
|
+
throw new Error("Failed to create child process streams");
|
|
104
|
+
}
|
|
105
|
+
this.logger.logEvent("SERVER_START", "LOW", {
|
|
106
|
+
command: this.serverCommand.join(" "),
|
|
107
|
+
pid: this.process.pid,
|
|
108
|
+
proFeaturesEnabled: this.proFeaturesEnabled,
|
|
109
|
+
}, this.sessionId);
|
|
110
|
+
// Pipe stderr to parent process
|
|
111
|
+
this.process.stderr.pipe(process.stderr);
|
|
112
|
+
// Handle server output
|
|
113
|
+
this.process.stdout.on("data", (data) => {
|
|
114
|
+
const output = data.toString();
|
|
115
|
+
this.handleServerOutput(output);
|
|
116
|
+
});
|
|
117
|
+
// Handle client input
|
|
118
|
+
process.stdin.on("data", (data) => {
|
|
119
|
+
const input = data.toString();
|
|
120
|
+
this.handleClientInput(input);
|
|
121
|
+
});
|
|
122
|
+
// Handle process exit
|
|
123
|
+
this.process.on("exit", (code) => {
|
|
124
|
+
this.handleProcessExit(code);
|
|
125
|
+
});
|
|
126
|
+
// Handle process errors
|
|
127
|
+
this.process.on("error", (err) => {
|
|
128
|
+
this.logger.logEvent("SERVER_ERROR", "HIGH", { error: err.message }, this.sessionId);
|
|
129
|
+
console.error("Server process error:", err);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Handle client input (buffered line-by-line)
|
|
134
|
+
* @param input - Raw input from client
|
|
135
|
+
*/
|
|
136
|
+
handleClientInput(input) {
|
|
137
|
+
this.clientMessageBuffer += input;
|
|
138
|
+
const lines = this.clientMessageBuffer.split("\n");
|
|
139
|
+
this.clientMessageBuffer = lines.pop() || "";
|
|
140
|
+
for (const line of lines) {
|
|
141
|
+
if (line.trim()) {
|
|
142
|
+
this.processClientMessage(line);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Process a single client message
|
|
148
|
+
* @param line - JSON-RPC message line
|
|
149
|
+
*/
|
|
150
|
+
processClientMessage(line) {
|
|
151
|
+
try {
|
|
152
|
+
const message = JSON.parse(line);
|
|
153
|
+
this.logger.logEvent("CLIENT_REQUEST", "LOW", {
|
|
154
|
+
method: message.method,
|
|
155
|
+
id: message.id,
|
|
156
|
+
}, this.sessionId);
|
|
157
|
+
const violations = [];
|
|
158
|
+
let shouldBlock = false;
|
|
159
|
+
// Handle tool calls with security checks
|
|
160
|
+
if (message.method === "tools/call") {
|
|
161
|
+
const result = this.handleToolCall(message);
|
|
162
|
+
violations.push(...result.violations);
|
|
163
|
+
shouldBlock = result.shouldBlock;
|
|
164
|
+
}
|
|
165
|
+
// Handle violations
|
|
166
|
+
if (violations.length > 0) {
|
|
167
|
+
this.handleViolations(message, violations, shouldBlock);
|
|
168
|
+
if (shouldBlock) {
|
|
169
|
+
return; // Don't forward blocked requests
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Forward to server
|
|
173
|
+
if (this.process && this.process.stdin) {
|
|
174
|
+
this.process.stdin.write(line + "\n");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
this.handleParseError(err, line);
|
|
179
|
+
// Forward unparseable messages
|
|
180
|
+
if (this.process && this.process.stdin) {
|
|
181
|
+
this.process.stdin.write(line + "\n");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Handle tool call with security checks and traceability
|
|
187
|
+
* @param message - MCP message
|
|
188
|
+
* @returns Violations and block status
|
|
189
|
+
*/
|
|
190
|
+
handleToolCall(message) {
|
|
191
|
+
const violations = [];
|
|
192
|
+
let shouldBlock = false;
|
|
193
|
+
// Rate limiting
|
|
194
|
+
const now = Date.now();
|
|
195
|
+
this.toolCallTimestamps.push(now);
|
|
196
|
+
// Clean up old timestamps
|
|
197
|
+
const oneMinuteAgo = now - 60000;
|
|
198
|
+
this.toolCallTimestamps = this.toolCallTimestamps.filter((t) => t > oneMinuteAgo);
|
|
199
|
+
if (!this.policy.checkRateLimit(this.toolCallTimestamps)) {
|
|
200
|
+
violations.push("Rate limit exceeded for tool calls");
|
|
201
|
+
shouldBlock = true;
|
|
202
|
+
this.logger.logEvent("RATE_LIMIT_EXCEEDED", "HIGH", {
|
|
203
|
+
method: message.method,
|
|
204
|
+
toolName: message.params?.name,
|
|
205
|
+
}, this.sessionId);
|
|
206
|
+
}
|
|
207
|
+
// Check parameters for security issues
|
|
208
|
+
const paramsStr = JSON.stringify(message.params);
|
|
209
|
+
violations.push(...this.policy.checkPromptInjection(paramsStr));
|
|
210
|
+
violations.push(...this.policy.checkSensitiveData(paramsStr));
|
|
211
|
+
// Check file paths
|
|
212
|
+
const filePathParams = this.extractFilePaths(message);
|
|
213
|
+
for (const filePath of filePathParams) {
|
|
214
|
+
violations.push(...this.policy.checkFileAccess(filePath));
|
|
215
|
+
// Track file access in pro features
|
|
216
|
+
if (this.contextTracker) {
|
|
217
|
+
this.contextTracker.recordFileAccess(this.sessionId, filePath, "read");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Log tool call
|
|
221
|
+
this.logger.logEvent("TOOL_CALL", violations.length > 0 ? "HIGH" : "LOW", {
|
|
222
|
+
toolName: message.params?.name,
|
|
223
|
+
hasViolations: violations.length > 0,
|
|
224
|
+
violations,
|
|
225
|
+
}, this.sessionId);
|
|
226
|
+
// Report to database if enabled
|
|
227
|
+
if (this.databaseEnabled && this.databaseReporter) {
|
|
228
|
+
this.databaseReporter.reportToolCall(this.sessionId, message.params?.name || "unknown", message.params || {}, violations, shouldBlock).catch(err => console.error("Database reporting error:", err));
|
|
229
|
+
}
|
|
230
|
+
return { violations, shouldBlock };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Extract file paths from message parameters
|
|
234
|
+
* @param message - MCP message
|
|
235
|
+
* @returns Array of file paths
|
|
236
|
+
*/
|
|
237
|
+
extractFilePaths(message) {
|
|
238
|
+
return [
|
|
239
|
+
message.params?.arguments?.path,
|
|
240
|
+
message.params?.arguments?.filePath,
|
|
241
|
+
message.params?.arguments?.file,
|
|
242
|
+
message.params?.arguments?.directory,
|
|
243
|
+
message.params?.path,
|
|
244
|
+
message.params?.filePath,
|
|
245
|
+
].filter((path) => typeof path === "string");
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Handle security violations
|
|
249
|
+
* @param message - Original message
|
|
250
|
+
* @param violations - List of violations
|
|
251
|
+
* @param shouldBlock - Whether to block the request
|
|
252
|
+
*/
|
|
253
|
+
handleViolations(message, violations, shouldBlock) {
|
|
254
|
+
const event = {
|
|
255
|
+
timestamp: new Date().toISOString(),
|
|
256
|
+
sessionId: this.sessionId,
|
|
257
|
+
eventType: "SECURITY_VIOLATION",
|
|
258
|
+
severity: "CRITICAL",
|
|
259
|
+
details: {
|
|
260
|
+
violations,
|
|
261
|
+
message: message,
|
|
262
|
+
blocked: shouldBlock,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
this.logger.logEvent(event.eventType, event.severity, event.details, event.sessionId);
|
|
266
|
+
// Report to database if enabled
|
|
267
|
+
if (this.databaseEnabled && this.databaseReporter) {
|
|
268
|
+
this.databaseReporter.reportEvent(event, {
|
|
269
|
+
method: message.method,
|
|
270
|
+
toolName: message.params?.name,
|
|
271
|
+
}).catch(err => console.error("Database reporting error:", err));
|
|
272
|
+
}
|
|
273
|
+
console.error(`\n⚠️ SECURITY VIOLATIONS DETECTED:\n${violations.join("\n")}\n`);
|
|
274
|
+
if (shouldBlock) {
|
|
275
|
+
console.error("🚫 REQUEST BLOCKED\n");
|
|
276
|
+
// Send error response
|
|
277
|
+
if (message.id !== undefined) {
|
|
278
|
+
const errorResponse = {
|
|
279
|
+
jsonrpc: message.jsonrpc,
|
|
280
|
+
id: message.id,
|
|
281
|
+
error: {
|
|
282
|
+
code: -32000,
|
|
283
|
+
message: "Security violation: Request blocked",
|
|
284
|
+
data: { violations },
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Handle parse errors
|
|
293
|
+
* @param err - Error object
|
|
294
|
+
* @param line - Original line that failed to parse
|
|
295
|
+
*/
|
|
296
|
+
handleParseError(err, line) {
|
|
297
|
+
this.logger.logEvent("PARSE_ERROR", "MEDIUM", {
|
|
298
|
+
error: err instanceof Error ? err.message : String(err),
|
|
299
|
+
line: line.substring(0, 100),
|
|
300
|
+
}, this.sessionId);
|
|
301
|
+
console.error(`Failed to parse client message: ${err}`);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Handle server output (buffered line-by-line)
|
|
305
|
+
* @param output - Raw output from server
|
|
306
|
+
*/
|
|
307
|
+
handleServerOutput(output) {
|
|
308
|
+
this.serverMessageBuffer += output;
|
|
309
|
+
const lines = this.serverMessageBuffer.split("\n");
|
|
310
|
+
this.serverMessageBuffer = lines.pop() || "";
|
|
311
|
+
for (const line of lines) {
|
|
312
|
+
if (line.trim()) {
|
|
313
|
+
this.processServerMessage(line);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Process a single server message
|
|
319
|
+
* @param line - JSON-RPC message line
|
|
320
|
+
*/
|
|
321
|
+
processServerMessage(line) {
|
|
322
|
+
let shouldForward = true;
|
|
323
|
+
try {
|
|
324
|
+
const message = JSON.parse(line);
|
|
325
|
+
// Check for sensitive data in response
|
|
326
|
+
const violations = [];
|
|
327
|
+
const responseStr = JSON.stringify(message.result || message);
|
|
328
|
+
violations.push(...this.policy.checkSensitiveData(responseStr));
|
|
329
|
+
if (violations.length > 0) {
|
|
330
|
+
this.handleSensitiveDataLeak(message, violations);
|
|
331
|
+
shouldForward = false;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
this.logger.logEvent("SERVER_RESPONSE", "LOW", {
|
|
335
|
+
id: message.id,
|
|
336
|
+
hasError: !!message.error,
|
|
337
|
+
}, this.sessionId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
// Log parse errors for server output
|
|
342
|
+
this.logger.logEvent("SERVER_PARSE_ERROR", "LOW", {
|
|
343
|
+
error: err instanceof Error ? err.message : String(err),
|
|
344
|
+
line: line.substring(0, 100),
|
|
345
|
+
}, this.sessionId);
|
|
346
|
+
}
|
|
347
|
+
// Forward the line if not blocked
|
|
348
|
+
if (shouldForward) {
|
|
349
|
+
process.stdout.write(line + "\n");
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Handle sensitive data leak in server response
|
|
354
|
+
* @param message - Server message
|
|
355
|
+
* @param violations - List of violations
|
|
356
|
+
*/
|
|
357
|
+
handleSensitiveDataLeak(message, violations) {
|
|
358
|
+
this.logger.logEvent("SENSITIVE_DATA_LEAK", "CRITICAL", {
|
|
359
|
+
violations,
|
|
360
|
+
responseId: message.id,
|
|
361
|
+
}, this.sessionId);
|
|
362
|
+
console.error(`\n🚨 SENSITIVE DATA DETECTED IN RESPONSE:\n${violations.join("\n")}\n`);
|
|
363
|
+
console.error("🚫 RESPONSE BLOCKED\n");
|
|
364
|
+
// Send sanitized error response
|
|
365
|
+
if (message.id !== undefined) {
|
|
366
|
+
const errorResponse = {
|
|
367
|
+
jsonrpc: message.jsonrpc,
|
|
368
|
+
id: message.id,
|
|
369
|
+
error: {
|
|
370
|
+
code: -32001,
|
|
371
|
+
message: "Security violation: Response contains sensitive data",
|
|
372
|
+
data: { violations },
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
process.stdout.write(JSON.stringify(errorResponse) + "\n");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Handle process exit
|
|
380
|
+
* @param code - Exit code
|
|
381
|
+
*/
|
|
382
|
+
async handleProcessExit(code) {
|
|
383
|
+
this.logger.logEvent("SERVER_EXIT", "MEDIUM", { exitCode: code }, this.sessionId);
|
|
384
|
+
console.error("\n=== MCP Security Statistics ===");
|
|
385
|
+
console.error(JSON.stringify(this.logger.getStatistics(), null, 2));
|
|
386
|
+
// Flush and close database reporter
|
|
387
|
+
if (this.databaseEnabled && this.databaseReporter) {
|
|
388
|
+
try {
|
|
389
|
+
await this.databaseReporter.flush();
|
|
390
|
+
await this.databaseReporter.close();
|
|
391
|
+
console.log("✓ Database events flushed");
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
console.error("Failed to flush database:", error);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Use setImmediate to allow pending I/O to complete
|
|
398
|
+
setImmediate(() => {
|
|
399
|
+
process.exit(code || 0);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Get the security logger instance
|
|
404
|
+
* @returns SecurityLogger instance
|
|
405
|
+
*/
|
|
406
|
+
getLogger() {
|
|
407
|
+
return this.logger;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Get the traceability manager (pro feature)
|
|
411
|
+
* @returns MCPTraceabilityManager instance or undefined
|
|
412
|
+
*/
|
|
413
|
+
getTraceabilityManager() {
|
|
414
|
+
return this.traceabilityManager;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
exports.MCPSecurityWrapper = MCPSecurityWrapper;
|