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
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
+ }
@@ -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;