contextguard 0.2.2 → 0.2.3
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/dist/agent.d.ts +5 -5
- package/dist/agent.js +9 -62
- package/dist/cli.js +53 -88
- package/dist/index.d.ts +3 -5
- package/dist/index.js +4 -6
- package/dist/logger.d.ts +9 -3
- package/dist/logger.js +13 -6
- package/dist/security-engine.d.ts +30 -0
- package/dist/security-engine.js +273 -0
- package/dist/sse-proxy.d.ts +2 -1
- package/dist/sse-proxy.js +5 -4
- package/package.json +2 -2
package/dist/agent.d.ts
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { Logger } from "./logger";
|
|
8
|
-
import { SupabaseConfig } from "./lib/supabase-client";
|
|
7
|
+
import { Logger, ContextGuardConfig } from "./logger";
|
|
9
8
|
import { AgentPolicyRow } from "./types/database.types";
|
|
10
9
|
/**
|
|
11
10
|
* Agent interface
|
|
@@ -17,8 +16,9 @@ export interface Agent {
|
|
|
17
16
|
/**
|
|
18
17
|
* Create an MCP security agent
|
|
19
18
|
* @param serverCommand - Command to start MCP server
|
|
20
|
-
* @param policyConfig - Policy configuration
|
|
21
|
-
* @param
|
|
19
|
+
* @param policyConfig - Policy configuration (should be complete with defaults already merged)
|
|
20
|
+
* @param agentId - Agent identifier
|
|
21
|
+
* @param contextGuardConfig - Optional ContextGuard API configuration
|
|
22
22
|
* @returns Agent functions
|
|
23
23
|
*/
|
|
24
|
-
export declare const createAgent: (serverCommand: string[], policyConfig
|
|
24
|
+
export declare const createAgent: (serverCommand: string[], policyConfig: Required<AgentPolicyRow>, agentId: string, contextGuardConfig?: ContextGuardConfig) => Agent;
|
package/dist/agent.js
CHANGED
|
@@ -8,32 +8,20 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.createAgent = void 0;
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
|
-
const
|
|
11
|
+
const security_engine_1 = require("./security-engine");
|
|
12
12
|
const logger_1 = require("./logger");
|
|
13
|
-
const supabase_client_1 = require("./lib/supabase-client");
|
|
14
13
|
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
14
|
/**
|
|
23
15
|
* Create an MCP security agent
|
|
24
16
|
* @param serverCommand - Command to start MCP server
|
|
25
|
-
* @param policyConfig - Policy configuration
|
|
26
|
-
* @param
|
|
17
|
+
* @param policyConfig - Policy configuration (should be complete with defaults already merged)
|
|
18
|
+
* @param agentId - Agent identifier
|
|
19
|
+
* @param contextGuardConfig - Optional ContextGuard API configuration
|
|
27
20
|
* @returns Agent functions
|
|
28
21
|
*/
|
|
29
|
-
const createAgent = (serverCommand, policyConfig
|
|
30
|
-
const
|
|
31
|
-
const
|
|
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);
|
|
22
|
+
const createAgent = (serverCommand, policyConfig, agentId, contextGuardConfig) => {
|
|
23
|
+
const policy = (0, security_engine_1.createPolicyChecker)(policyConfig);
|
|
24
|
+
const logger = (0, logger_1.createLogger)(policyConfig.logPath, agentId, policyConfig.alertWebhook, policyConfig.alertOnSeverity, contextGuardConfig);
|
|
37
25
|
const sessionId = (0, mcp_1.generateSessionId)();
|
|
38
26
|
const state = {
|
|
39
27
|
process: null,
|
|
@@ -278,45 +266,13 @@ const createAgent = (serverCommand, policyConfig = {}, supabaseConfig) => {
|
|
|
278
266
|
logger.logEvent("SERVER_EXIT", "MEDIUM", { exitCode: code }, sessionId);
|
|
279
267
|
console.error("\n=== MCP Security Statistics ===");
|
|
280
268
|
console.error(JSON.stringify(logger.getStatistics(), null, 2));
|
|
281
|
-
|
|
282
|
-
if (supabaseClient && supabaseConfig?.agentId) {
|
|
283
|
-
supabaseClient
|
|
284
|
-
.updateAgentStatus(supabaseConfig.agentId, "offline")
|
|
285
|
-
.catch(() => { })
|
|
286
|
-
.finally(finish);
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
finish();
|
|
290
|
-
}
|
|
269
|
+
setImmediate(() => process.exit(code || 0));
|
|
291
270
|
};
|
|
292
271
|
return {
|
|
293
272
|
/**
|
|
294
273
|
* Start the MCP server wrapper
|
|
295
274
|
*/
|
|
296
275
|
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
|
-
// Send periodic heartbeats every 30 seconds
|
|
313
|
-
const heartbeatInterval = setInterval(() => {
|
|
314
|
-
supabaseClient
|
|
315
|
-
.updateAgentStatus(supabaseConfig.agentId, "online")
|
|
316
|
-
.catch(() => { });
|
|
317
|
-
}, 30000);
|
|
318
|
-
heartbeatInterval.unref();
|
|
319
|
-
}
|
|
320
276
|
state.process = (0, child_process_1.spawn)(serverCommand[0], serverCommand.slice(1), {
|
|
321
277
|
stdio: ["pipe", "pipe", "pipe"],
|
|
322
278
|
});
|
|
@@ -353,16 +309,7 @@ const createAgent = (serverCommand, policyConfig = {}, supabaseConfig) => {
|
|
|
353
309
|
logger.logEvent("AGENT_SHUTDOWN", "MEDIUM", { signal }, sessionId);
|
|
354
310
|
if (state.process)
|
|
355
311
|
state.process.kill();
|
|
356
|
-
|
|
357
|
-
if (supabaseClient && supabaseConfig?.agentId) {
|
|
358
|
-
supabaseClient
|
|
359
|
-
.updateAgentStatus(supabaseConfig.agentId, "offline")
|
|
360
|
-
.catch(() => { })
|
|
361
|
-
.finally(finish);
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
364
|
-
finish();
|
|
365
|
-
}
|
|
312
|
+
process.exit(0);
|
|
366
313
|
};
|
|
367
314
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
368
315
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
package/dist/cli.js
CHANGED
|
@@ -41,11 +41,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
})();
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.main = main;
|
|
44
|
-
const fs = __importStar(require("fs"));
|
|
45
44
|
const os = __importStar(require("os"));
|
|
46
45
|
const agent_1 = require("./agent");
|
|
47
46
|
const sse_proxy_1 = require("./sse-proxy");
|
|
48
|
-
const
|
|
47
|
+
const security_engine_1 = require("./security-engine");
|
|
49
48
|
const init_1 = require("./init");
|
|
50
49
|
/**
|
|
51
50
|
* Display help message
|
|
@@ -59,10 +58,10 @@ Usage:
|
|
|
59
58
|
contextguard init --api-key <key>
|
|
60
59
|
|
|
61
60
|
# stdio transport (Claude Desktop)
|
|
62
|
-
contextguard --server "node server.js"
|
|
61
|
+
contextguard --server "node server.js"
|
|
63
62
|
|
|
64
63
|
# SSE/HTTP transport proxy
|
|
65
|
-
contextguard --transport sse --port 3100 --target http://localhost:3000
|
|
64
|
+
contextguard --transport sse --port 3100 --target http://localhost:3000
|
|
66
65
|
|
|
67
66
|
Commands:
|
|
68
67
|
init Auto-patch claude_desktop_config.json
|
|
@@ -77,28 +76,10 @@ Options:
|
|
|
77
76
|
--transport <type> stdio | sse | http (default: stdio)
|
|
78
77
|
--port <n> Proxy listen port (SSE/HTTP mode, default: 3100)
|
|
79
78
|
--target <url> Upstream server URL (SSE/HTTP mode)
|
|
80
|
-
--config <file> Path to security config JSON (optional)
|
|
81
79
|
--help Show this help
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"mode": "monitor", // "monitor" (log only) | "block" (block + log)
|
|
86
|
-
"maxToolCallsPerMinute": 30,
|
|
87
|
-
"enablePromptInjectionDetection": true,
|
|
88
|
-
"enableSensitiveDataDetection": true,
|
|
89
|
-
"enablePathTraversalPrevention": true,
|
|
90
|
-
"enableSQLInjectionDetection": true,
|
|
91
|
-
"enableXSSDetection": true,
|
|
92
|
-
"enableSemanticDetection": false,
|
|
93
|
-
"blockedPatterns": ["ignore previous", "system prompt"],
|
|
94
|
-
"allowedFilePaths": ["/safe/path"],
|
|
95
|
-
"allowedTools": [],
|
|
96
|
-
"blockedTools": [],
|
|
97
|
-
"customRules": [{ "name": "my-rule", "pattern": "\\bforbidden\\b" }],
|
|
98
|
-
"alertWebhook": "https://hooks.example.com/alert",
|
|
99
|
-
"alertOnSeverity": ["HIGH", "CRITICAL"],
|
|
100
|
-
"logPath": "mcp_security.log"
|
|
101
|
-
}
|
|
81
|
+
Note: Security policies are managed via the ContextGuard dashboard.
|
|
82
|
+
Set CONTEXTGUARD_API_KEY to enable remote policy management.
|
|
102
83
|
|
|
103
84
|
For more information, visit: https://contextguard.dev
|
|
104
85
|
`);
|
|
@@ -110,7 +91,6 @@ For more information, visit: https://contextguard.dev
|
|
|
110
91
|
function parseArgs() {
|
|
111
92
|
const args = process.argv.slice(2);
|
|
112
93
|
let serverCommand = "";
|
|
113
|
-
let configFile = "";
|
|
114
94
|
let transport = "stdio";
|
|
115
95
|
let port = 3100;
|
|
116
96
|
let targetUrl = "";
|
|
@@ -119,10 +99,6 @@ function parseArgs() {
|
|
|
119
99
|
serverCommand = args[i + 1];
|
|
120
100
|
i++;
|
|
121
101
|
}
|
|
122
|
-
else if (args[i] === "--config" && args[i + 1]) {
|
|
123
|
-
configFile = args[i + 1];
|
|
124
|
-
i++;
|
|
125
|
-
}
|
|
126
102
|
else if (args[i] === "--transport" && args[i + 1]) {
|
|
127
103
|
transport = args[i + 1];
|
|
128
104
|
i++;
|
|
@@ -136,7 +112,7 @@ function parseArgs() {
|
|
|
136
112
|
i++;
|
|
137
113
|
}
|
|
138
114
|
}
|
|
139
|
-
return { serverCommand,
|
|
115
|
+
return { serverCommand, transport, port, targetUrl };
|
|
140
116
|
}
|
|
141
117
|
/**
|
|
142
118
|
* Parse a shell command string into an array of arguments,
|
|
@@ -170,28 +146,6 @@ function parseCommand(cmd) {
|
|
|
170
146
|
tokens.push(current);
|
|
171
147
|
return tokens;
|
|
172
148
|
}
|
|
173
|
-
/**
|
|
174
|
-
* Load configuration from file
|
|
175
|
-
* @param configFile - Path to config file
|
|
176
|
-
* @returns Security configuration
|
|
177
|
-
*/
|
|
178
|
-
function loadConfig(configFile) {
|
|
179
|
-
if (!fs.existsSync(configFile)) {
|
|
180
|
-
console.error(`Error: Config file not found: ${configFile}`);
|
|
181
|
-
process.exit(1);
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
const config = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
185
|
-
// validateConfig(config);
|
|
186
|
-
return config;
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
console.error(`Error: Failed to load config file: ${error}`);
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
// TypeScript doesn't know process.exit() never returns
|
|
193
|
-
return {};
|
|
194
|
-
}
|
|
195
149
|
/**
|
|
196
150
|
* Main CLI entry point
|
|
197
151
|
*/
|
|
@@ -208,33 +162,23 @@ async function main() {
|
|
|
208
162
|
return;
|
|
209
163
|
}
|
|
210
164
|
// Parse arguments
|
|
211
|
-
const { serverCommand,
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
if (configFile) {
|
|
215
|
-
config = loadConfig(configFile);
|
|
216
|
-
}
|
|
217
|
-
// Merge with defaults
|
|
218
|
-
const fullConfig = { ...policy_1.DEFAULT_POLICY, ...config };
|
|
219
|
-
// CLI flags override config file
|
|
220
|
-
if (transport !== "stdio") {
|
|
221
|
-
fullConfig.transport = transport;
|
|
222
|
-
}
|
|
223
|
-
if (port !== 3100) {
|
|
224
|
-
fullConfig.port = port;
|
|
225
|
-
}
|
|
226
|
-
if (targetUrl) {
|
|
227
|
-
fullConfig.targetUrl = targetUrl;
|
|
228
|
-
}
|
|
229
|
-
const effectiveTransport = fullConfig.transport || "stdio";
|
|
165
|
+
const { serverCommand, transport, port, targetUrl } = parseArgs();
|
|
166
|
+
// Agent identifier (used by both stdio and SSE/HTTP modes)
|
|
167
|
+
const agentId = process.env.AGENT_ID || `${os.hostname()}-${process.pid}`;
|
|
230
168
|
// SSE/HTTP transport mode
|
|
231
|
-
if (
|
|
232
|
-
if (!
|
|
169
|
+
if (transport === "sse" || transport === "http") {
|
|
170
|
+
if (!targetUrl) {
|
|
233
171
|
console.error("Error: --target <url> is required for SSE/HTTP transport");
|
|
234
172
|
process.exit(1);
|
|
235
173
|
}
|
|
236
|
-
const
|
|
237
|
-
|
|
174
|
+
const proxyConfig = {
|
|
175
|
+
...security_engine_1.DEFAULT_POLICY,
|
|
176
|
+
transport: transport,
|
|
177
|
+
port,
|
|
178
|
+
targetUrl,
|
|
179
|
+
};
|
|
180
|
+
const proxy = (0, sse_proxy_1.createSSEProxy)(proxyConfig, agentId);
|
|
181
|
+
proxy.start(port, targetUrl);
|
|
238
182
|
return;
|
|
239
183
|
}
|
|
240
184
|
// stdio transport mode
|
|
@@ -243,20 +187,30 @@ async function main() {
|
|
|
243
187
|
console.error("Run 'contextguard --help' for usage information");
|
|
244
188
|
process.exit(1);
|
|
245
189
|
}
|
|
246
|
-
//
|
|
247
|
-
const supabaseConfig = process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_KEY
|
|
248
|
-
? {
|
|
249
|
-
url: process.env.SUPABASE_URL,
|
|
250
|
-
serviceKey: process.env.SUPABASE_SERVICE_KEY,
|
|
251
|
-
agentId: process.env.AGENT_ID || "default-agent",
|
|
252
|
-
}
|
|
253
|
-
: undefined;
|
|
254
|
-
// Supabase integration is configured silently to avoid polluting stderr
|
|
255
|
-
// Start REST-based heartbeat if CONTEXTGUARD_API_KEY is set
|
|
190
|
+
// ContextGuard API configuration
|
|
256
191
|
const cgApiKey = process.env.CONTEXTGUARD_API_KEY;
|
|
192
|
+
const baseUrl = process.env.CONTEXTGUARD_BASE_URL || "https://contextguard.dev";
|
|
193
|
+
let remotePolicy = null;
|
|
257
194
|
if (cgApiKey) {
|
|
258
|
-
|
|
259
|
-
const
|
|
195
|
+
// Fetch policy from ContextGuard API
|
|
196
|
+
const fetchPolicy = async () => {
|
|
197
|
+
try {
|
|
198
|
+
const response = await fetch(`${baseUrl}/api/agents/policy/${agentId}`, {
|
|
199
|
+
headers: {
|
|
200
|
+
Authorization: `Bearer ${cgApiKey}`,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
if (response.ok) {
|
|
204
|
+
const data = await response.json();
|
|
205
|
+
if (data.policy) {
|
|
206
|
+
remotePolicy = data.policy;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// silently ignore — use local config if API is unreachable
|
|
212
|
+
}
|
|
213
|
+
};
|
|
260
214
|
const sendHeartbeat = async () => {
|
|
261
215
|
try {
|
|
262
216
|
await fetch(`${baseUrl}/api/agents/heartbeat`, {
|
|
@@ -278,13 +232,24 @@ async function main() {
|
|
|
278
232
|
// silently ignore — don't break the agent if dashboard is unreachable
|
|
279
233
|
}
|
|
280
234
|
};
|
|
235
|
+
// Fetch policy first, then start heartbeat
|
|
236
|
+
await fetchPolicy();
|
|
281
237
|
sendHeartbeat();
|
|
282
238
|
const interval = setInterval(sendHeartbeat, 30000);
|
|
283
239
|
interval.unref();
|
|
240
|
+
// Periodically refresh policy (every 5 minutes)
|
|
241
|
+
const policyInterval = setInterval(fetchPolicy, 5 * 60000);
|
|
242
|
+
policyInterval.unref();
|
|
284
243
|
// Heartbeat configured silently to avoid polluting stderr
|
|
285
244
|
}
|
|
245
|
+
// Use remote policy if available, otherwise use default policy
|
|
246
|
+
const finalConfig = remotePolicy ?? security_engine_1.DEFAULT_POLICY;
|
|
247
|
+
// Create ContextGuard config if API key is set
|
|
248
|
+
const contextGuardConfig = cgApiKey
|
|
249
|
+
? { apiKey: cgApiKey, baseUrl }
|
|
250
|
+
: undefined;
|
|
286
251
|
// Create and start agent
|
|
287
|
-
const agent = (0, agent_1.createAgent)(parseCommand(serverCommand),
|
|
252
|
+
const agent = (0, agent_1.createAgent)(parseCommand(serverCommand), finalConfig, agentId, contextGuardConfig);
|
|
288
253
|
await agent.start();
|
|
289
254
|
}
|
|
290
255
|
// Run CLI if this is the main module
|
package/dist/index.d.ts
CHANGED
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
export { createAgent } from "./agent";
|
|
8
8
|
export type { Agent } from "./agent";
|
|
9
9
|
export type { MCPMessage } from "./types/mcp";
|
|
10
|
-
export { createPolicyChecker, DEFAULT_POLICY } from "./
|
|
11
|
-
export type { PolicyChecker } from "./
|
|
10
|
+
export { createPolicyChecker, DEFAULT_POLICY } from "./security-engine";
|
|
11
|
+
export type { PolicyChecker } from "./security-engine";
|
|
12
12
|
export { createLogger } from "./logger";
|
|
13
|
-
export type { Logger, SecurityStatistics } from "./logger";
|
|
14
|
-
export { createSupabaseClient } from "./lib/supabase-client";
|
|
15
|
-
export type { SupabaseConfig } from "./lib/supabase-client";
|
|
13
|
+
export type { Logger, SecurityStatistics, ContextGuardConfig } from "./logger";
|
|
16
14
|
export type { AgentPolicyRow, AgentPolicyInsert, AgentPolicyUpdate, SecurityEventRow, SecurityEventInsert, SecurityEventUpdate, AgentStatusRow, AgentStatusInsert, AgentStatusUpdate, EventStatisticsRow, SecuritySeverity, AgentStatus, } from "./types/database.types";
|
package/dist/index.js
CHANGED
|
@@ -6,13 +6,11 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
9
|
+
exports.createLogger = exports.DEFAULT_POLICY = exports.createPolicyChecker = exports.createAgent = void 0;
|
|
10
10
|
var agent_1 = require("./agent");
|
|
11
11
|
Object.defineProperty(exports, "createAgent", { enumerable: true, get: function () { return agent_1.createAgent; } });
|
|
12
|
-
var
|
|
13
|
-
Object.defineProperty(exports, "createPolicyChecker", { enumerable: true, get: function () { return
|
|
14
|
-
Object.defineProperty(exports, "DEFAULT_POLICY", { enumerable: true, get: function () { return
|
|
12
|
+
var security_engine_1 = require("./security-engine");
|
|
13
|
+
Object.defineProperty(exports, "createPolicyChecker", { enumerable: true, get: function () { return security_engine_1.createPolicyChecker; } });
|
|
14
|
+
Object.defineProperty(exports, "DEFAULT_POLICY", { enumerable: true, get: function () { return security_engine_1.DEFAULT_POLICY; } });
|
|
15
15
|
var logger_1 = require("./logger");
|
|
16
16
|
Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_1.createLogger; } });
|
|
17
|
-
var supabase_client_1 = require("./lib/supabase-client");
|
|
18
|
-
Object.defineProperty(exports, "createSupabaseClient", { enumerable: true, get: function () { return supabase_client_1.createSupabaseClient; } });
|
package/dist/logger.d.ts
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import { SupabaseClient } from "./lib/supabase-client";
|
|
8
7
|
import { SecurityEventRow, SecuritySeverity } from "./types/database.types";
|
|
8
|
+
/**
|
|
9
|
+
* ContextGuard API configuration
|
|
10
|
+
*/
|
|
11
|
+
export interface ContextGuardConfig {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
}
|
|
9
15
|
/**
|
|
10
16
|
* Security statistics interface
|
|
11
17
|
*/
|
|
@@ -27,10 +33,10 @@ export interface Logger {
|
|
|
27
33
|
/**
|
|
28
34
|
* Create a security event logger
|
|
29
35
|
* @param logFile - Path to log file
|
|
30
|
-
* @param supabaseClient - Optional Supabase client for remote logging
|
|
31
36
|
* @param agentId - Agent identifier for event attribution
|
|
32
37
|
* @param alertWebhook - Optional webhook URL for HIGH/CRITICAL alerts
|
|
33
38
|
* @param alertOnSeverity - Severity levels that trigger webhook alerts
|
|
39
|
+
* @param contextGuardConfig - Optional ContextGuard API config for remote logging
|
|
34
40
|
* @returns Logger functions
|
|
35
41
|
*/
|
|
36
|
-
export declare const createLogger: (logFile?: string,
|
|
42
|
+
export declare const createLogger: (logFile?: string, agentId?: string, alertWebhook?: string, alertOnSeverity?: string[], contextGuardConfig?: ContextGuardConfig) => Logger;
|
package/dist/logger.js
CHANGED
|
@@ -73,13 +73,13 @@ const fireWebhookAlert = (webhookUrl, event) => {
|
|
|
73
73
|
/**
|
|
74
74
|
* Create a security event logger
|
|
75
75
|
* @param logFile - Path to log file
|
|
76
|
-
* @param supabaseClient - Optional Supabase client for remote logging
|
|
77
76
|
* @param agentId - Agent identifier for event attribution
|
|
78
77
|
* @param alertWebhook - Optional webhook URL for HIGH/CRITICAL alerts
|
|
79
78
|
* @param alertOnSeverity - Severity levels that trigger webhook alerts
|
|
79
|
+
* @param contextGuardConfig - Optional ContextGuard API config for remote logging
|
|
80
80
|
* @returns Logger functions
|
|
81
81
|
*/
|
|
82
|
-
const createLogger = (logFile = path.join(os.homedir(), ".contextguard", "mcp_security.log"),
|
|
82
|
+
const createLogger = (logFile = path.join(os.homedir(), ".contextguard", "mcp_security.log"), agentId = "", alertWebhook, alertOnSeverity = ["HIGH", "CRITICAL"], contextGuardConfig) => {
|
|
83
83
|
let events = [];
|
|
84
84
|
return {
|
|
85
85
|
/**
|
|
@@ -118,10 +118,17 @@ const createLogger = (logFile = path.join(os.homedir(), ".contextguard", "mcp_se
|
|
|
118
118
|
if (alertWebhook && alertOnSeverity.includes(severity)) {
|
|
119
119
|
fireWebhookAlert(alertWebhook, event);
|
|
120
120
|
}
|
|
121
|
-
// Report to
|
|
122
|
-
if (
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
// Report to ContextGuard API if configured
|
|
122
|
+
if (contextGuardConfig) {
|
|
123
|
+
fetch(`${contextGuardConfig.baseUrl}/api/events`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: {
|
|
126
|
+
"Content-Type": "application/json",
|
|
127
|
+
Authorization: `Bearer ${contextGuardConfig.apiKey}`,
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify(event),
|
|
130
|
+
}).catch(() => {
|
|
131
|
+
// Silently ignore - don't break agent if API is unreachable
|
|
125
132
|
});
|
|
126
133
|
}
|
|
127
134
|
},
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 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 { AgentPolicyRow } from "./types/database.types";
|
|
8
|
+
/**
|
|
9
|
+
* Policy checker interface
|
|
10
|
+
*/
|
|
11
|
+
export interface PolicyChecker {
|
|
12
|
+
checkPromptInjection: (text: string) => string[];
|
|
13
|
+
checkSensitiveData: (text: string) => string[];
|
|
14
|
+
checkFileAccess: (filePath: string) => string[];
|
|
15
|
+
checkRateLimit: (timestamps: number[]) => boolean;
|
|
16
|
+
checkBlockedPatterns: (text: string) => string[];
|
|
17
|
+
checkToolAccess: (toolName: string) => string[];
|
|
18
|
+
checkSQLInjection: (text: string) => string[];
|
|
19
|
+
checkXSS: (text: string) => string[];
|
|
20
|
+
checkCustomRules: (text: string) => string[];
|
|
21
|
+
isBlockingMode: () => boolean;
|
|
22
|
+
getConfig: () => Required<AgentPolicyRow>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create a policy checker with the given configuration
|
|
26
|
+
* @param config - Policy configuration
|
|
27
|
+
* @returns Policy checker functions
|
|
28
|
+
*/
|
|
29
|
+
export declare const createPolicyChecker: (config: Required<AgentPolicyRow>) => PolicyChecker;
|
|
30
|
+
export declare const DEFAULT_POLICY: Required<AgentPolicyRow>;
|
|
@@ -0,0 +1,273 @@
|
|
|
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.DEFAULT_POLICY = exports.createPolicyChecker = void 0;
|
|
10
|
+
/**
|
|
11
|
+
* Initialize patterns for detecting sensitive data
|
|
12
|
+
*/
|
|
13
|
+
const initSensitiveDataPatterns = () => [
|
|
14
|
+
// Generic secrets
|
|
15
|
+
/(?:password|secret|api[_-]?key|token)\s*[:=]\s*['"]?[\w\-.]+['"]?/gi,
|
|
16
|
+
// Email addresses
|
|
17
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
18
|
+
// Social Security Numbers
|
|
19
|
+
/\b\d{3}-\d{2}-\d{4}\b/g,
|
|
20
|
+
// OpenAI API keys
|
|
21
|
+
/sk-[a-zA-Z0-9]{20,}/g,
|
|
22
|
+
// GitHub tokens
|
|
23
|
+
/ghp_[a-zA-Z0-9]{36}/g,
|
|
24
|
+
// AWS Access Keys
|
|
25
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
26
|
+
// Stripe API keys
|
|
27
|
+
/sk_(live|test)_[a-zA-Z0-9]{24,}/g,
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Initialize patterns for detecting SQL injection
|
|
31
|
+
*/
|
|
32
|
+
const initSQLInjectionPatterns = () => [
|
|
33
|
+
/\bUNION\b.{0,50}\bSELECT\b/gi,
|
|
34
|
+
/\bDROP\b\s+\b(TABLE|DATABASE|SCHEMA)\b/gi,
|
|
35
|
+
/\bDELETE\b\s+\bFROM\b/gi,
|
|
36
|
+
/\bTRUNCATE\b\s+\bTABLE\b/gi,
|
|
37
|
+
/\bINSERT\b\s+\bINTO\b.{0,50}\bVALUES\b/gi,
|
|
38
|
+
/\bEXEC\s*\(|\bEXECUTE\s*\(/gi,
|
|
39
|
+
/--\s*$|\/\*[\s\S]*?\*\//gm,
|
|
40
|
+
/\bOR\b\s+['"]?\d+['"]?\s*=\s*['"]?\d+['"]?/gi,
|
|
41
|
+
/'\s*OR\s*'1'\s*=\s*'1/gi,
|
|
42
|
+
/;\s*(DROP|DELETE|INSERT|UPDATE|CREATE|ALTER)\b/gi,
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Initialize patterns for detecting XSS attacks
|
|
46
|
+
*/
|
|
47
|
+
const initXSSPatterns = () => [
|
|
48
|
+
/<script[\s>]/gi,
|
|
49
|
+
/javascript:/gi,
|
|
50
|
+
/on\w+\s*=\s*["']?[^"'>]*/gi, // onerror=, onload=, onclick=, etc.
|
|
51
|
+
/<iframe[\s>]/gi,
|
|
52
|
+
/<object[\s>]/gi,
|
|
53
|
+
/<embed[\s>]/gi,
|
|
54
|
+
/eval\s*\(/gi,
|
|
55
|
+
/document\.(cookie|write|location)/gi,
|
|
56
|
+
/window\.(location|open)\s*[=(]/gi,
|
|
57
|
+
/<img[^>]+src\s*=\s*["']?javascript:/gi,
|
|
58
|
+
];
|
|
59
|
+
/**
|
|
60
|
+
* Initialize patterns for detecting prompt injection
|
|
61
|
+
*/
|
|
62
|
+
const initPromptInjectionPatterns = () => [
|
|
63
|
+
/ignore\s+(previous|all)\s+(instructions|prompts)/gi,
|
|
64
|
+
/system:\s*you\s+are\s+now/gi,
|
|
65
|
+
/forget\s+(everything|all)/gi,
|
|
66
|
+
/new\s+instructions:/gi,
|
|
67
|
+
/\[INST\].*?\[\/INST\]/gs,
|
|
68
|
+
/<\|im_start\|>/g,
|
|
69
|
+
/disregard\s+previous/gi,
|
|
70
|
+
/override\s+previous/gi,
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* Create a policy checker with the given configuration
|
|
74
|
+
* @param config - Policy configuration
|
|
75
|
+
* @returns Policy checker functions
|
|
76
|
+
*/
|
|
77
|
+
const createPolicyChecker = (config) => {
|
|
78
|
+
const sensitiveDataPatterns = initSensitiveDataPatterns();
|
|
79
|
+
const promptInjectionPatterns = initPromptInjectionPatterns();
|
|
80
|
+
const sqlInjectionPatterns = initSQLInjectionPatterns();
|
|
81
|
+
const xssPatterns = initXSSPatterns();
|
|
82
|
+
return {
|
|
83
|
+
/**
|
|
84
|
+
* Check text for prompt injection attempts
|
|
85
|
+
*/
|
|
86
|
+
checkPromptInjection: (text) => {
|
|
87
|
+
if (!config.enablePromptInjectionDetection) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const violations = [];
|
|
91
|
+
for (const pattern of promptInjectionPatterns) {
|
|
92
|
+
const matches = text.match(pattern);
|
|
93
|
+
if (matches) {
|
|
94
|
+
violations.push(`Potential prompt injection detected: "${matches[0].substring(0, 50)}..."`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return violations;
|
|
98
|
+
},
|
|
99
|
+
/**
|
|
100
|
+
* Check text for sensitive data exposure
|
|
101
|
+
*/
|
|
102
|
+
checkSensitiveData: (text) => {
|
|
103
|
+
if (!config.enableSensitiveDataDetection) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const violations = [];
|
|
107
|
+
for (const pattern of sensitiveDataPatterns) {
|
|
108
|
+
const matches = text.match(pattern);
|
|
109
|
+
if (matches) {
|
|
110
|
+
violations.push(`Sensitive data pattern detected (redacted): ${pattern.source.substring(0, 30)}...`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return violations;
|
|
114
|
+
},
|
|
115
|
+
/**
|
|
116
|
+
* Check file path for security violations
|
|
117
|
+
*/
|
|
118
|
+
checkFileAccess: (filePath) => {
|
|
119
|
+
if (!config.enablePathTraversalPrevention) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const violations = [];
|
|
123
|
+
// Check for path traversal
|
|
124
|
+
if (filePath.includes("..")) {
|
|
125
|
+
violations.push(`Path traversal attempt detected: ${filePath}`);
|
|
126
|
+
}
|
|
127
|
+
// Check for dangerous system paths
|
|
128
|
+
const dangerousPaths = [
|
|
129
|
+
"/etc",
|
|
130
|
+
"/root",
|
|
131
|
+
"/sys",
|
|
132
|
+
"/proc",
|
|
133
|
+
"C:\\Windows\\System32",
|
|
134
|
+
];
|
|
135
|
+
if (dangerousPaths.some((dangerous) => filePath.startsWith(dangerous))) {
|
|
136
|
+
violations.push(`Access to dangerous path detected: ${filePath}`);
|
|
137
|
+
}
|
|
138
|
+
// Check against allowed paths whitelist
|
|
139
|
+
if (config.allowedFilePaths.length > 0) {
|
|
140
|
+
const isAllowed = config.allowedFilePaths.some((allowed) => filePath.startsWith(allowed));
|
|
141
|
+
if (!isAllowed) {
|
|
142
|
+
violations.push(`File path not in allowed list: ${filePath}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return violations;
|
|
146
|
+
},
|
|
147
|
+
/**
|
|
148
|
+
* Check if rate limit is exceeded
|
|
149
|
+
*/
|
|
150
|
+
checkRateLimit: (timestamps) => {
|
|
151
|
+
const oneMinuteAgo = Date.now() - 60000;
|
|
152
|
+
const recentCalls = timestamps.filter((t) => t > oneMinuteAgo);
|
|
153
|
+
return recentCalls.length < config.maxToolCallsPerMinute;
|
|
154
|
+
},
|
|
155
|
+
/**
|
|
156
|
+
* Check text against blocked patterns
|
|
157
|
+
*/
|
|
158
|
+
checkBlockedPatterns: (text) => {
|
|
159
|
+
const violations = [];
|
|
160
|
+
for (const pattern of config.blockedPatterns) {
|
|
161
|
+
if (text.toLowerCase().includes(pattern.toLowerCase())) {
|
|
162
|
+
violations.push(`Blocked pattern detected: "${pattern}"`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return violations;
|
|
166
|
+
},
|
|
167
|
+
/**
|
|
168
|
+
* Check if a tool is allowed or blocked
|
|
169
|
+
*/
|
|
170
|
+
checkToolAccess: (toolName) => {
|
|
171
|
+
const violations = [];
|
|
172
|
+
if (config.blockedTools.includes(toolName)) {
|
|
173
|
+
violations.push(`Tool "${toolName}" is explicitly blocked`);
|
|
174
|
+
}
|
|
175
|
+
else if (config.allowedTools.length > 0 &&
|
|
176
|
+
!config.allowedTools.includes(toolName)) {
|
|
177
|
+
violations.push(`Tool "${toolName}" is not in the allowed list: [${config.allowedTools.join(", ")}]`);
|
|
178
|
+
}
|
|
179
|
+
return violations;
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* Check text for SQL injection attempts
|
|
183
|
+
*/
|
|
184
|
+
checkSQLInjection: (text) => {
|
|
185
|
+
if (!config.enableSQLInjectionDetection) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
const violations = [];
|
|
189
|
+
for (const pattern of sqlInjectionPatterns) {
|
|
190
|
+
if (pattern.test(text)) {
|
|
191
|
+
violations.push(`SQL injection pattern detected: ${pattern.source.substring(0, 40)}...`);
|
|
192
|
+
pattern.lastIndex = 0; // reset stateful regex
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return violations;
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* Check text for XSS attack patterns
|
|
199
|
+
*/
|
|
200
|
+
checkXSS: (text) => {
|
|
201
|
+
if (!config.enableXSSDetection) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
const violations = [];
|
|
205
|
+
for (const pattern of xssPatterns) {
|
|
206
|
+
if (pattern.test(text)) {
|
|
207
|
+
violations.push(`XSS pattern detected: ${pattern.source.substring(0, 40)}...`);
|
|
208
|
+
pattern.lastIndex = 0; // reset stateful regex
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return violations;
|
|
212
|
+
},
|
|
213
|
+
/**
|
|
214
|
+
* Check text against custom user-defined rules
|
|
215
|
+
*/
|
|
216
|
+
checkCustomRules: (text) => {
|
|
217
|
+
const violations = [];
|
|
218
|
+
for (const rule of config.customRules) {
|
|
219
|
+
try {
|
|
220
|
+
const regex = new RegExp(rule.pattern, "gi");
|
|
221
|
+
if (regex.test(text)) {
|
|
222
|
+
violations.push(`Custom rule "${rule.name}" triggered`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
// Invalid regex pattern — skip silently
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return violations;
|
|
230
|
+
},
|
|
231
|
+
/**
|
|
232
|
+
* Check if blocking mode is enabled
|
|
233
|
+
*/
|
|
234
|
+
isBlockingMode: () => {
|
|
235
|
+
return config.mode === "block";
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* Get the full configuration
|
|
239
|
+
*/
|
|
240
|
+
getConfig: () => {
|
|
241
|
+
return config;
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
exports.createPolicyChecker = createPolicyChecker;
|
|
246
|
+
exports.DEFAULT_POLICY = {
|
|
247
|
+
id: "",
|
|
248
|
+
agent_id: "",
|
|
249
|
+
created_at: "",
|
|
250
|
+
updated_at: "",
|
|
251
|
+
maxToolCallsPerMinute: 30,
|
|
252
|
+
blockedPatterns: [],
|
|
253
|
+
allowedFilePaths: [],
|
|
254
|
+
alertThreshold: 5,
|
|
255
|
+
enablePromptInjectionDetection: true,
|
|
256
|
+
enableSensitiveDataDetection: true,
|
|
257
|
+
enablePathTraversalPrevention: true,
|
|
258
|
+
enableSQLInjectionDetection: true,
|
|
259
|
+
enableSemanticDetection: false,
|
|
260
|
+
enableXSSDetection: true,
|
|
261
|
+
customRules: [],
|
|
262
|
+
mode: "monitor",
|
|
263
|
+
logPath: "mcp_security.log",
|
|
264
|
+
enableProFeatures: false,
|
|
265
|
+
licenseFilePath: ".contextguard-license",
|
|
266
|
+
transport: "stdio",
|
|
267
|
+
port: 3100,
|
|
268
|
+
targetUrl: "",
|
|
269
|
+
allowedTools: [],
|
|
270
|
+
blockedTools: [],
|
|
271
|
+
alertWebhook: "",
|
|
272
|
+
alertOnSeverity: ["HIGH", "CRITICAL"],
|
|
273
|
+
};
|
package/dist/sse-proxy.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface SSEProxy {
|
|
|
16
16
|
/**
|
|
17
17
|
* Create an SSE/HTTP proxy for MCP servers
|
|
18
18
|
* @param policyConfig - Policy configuration
|
|
19
|
+
* @param agentId - Agent identifier
|
|
19
20
|
* @returns SSE proxy functions
|
|
20
21
|
*/
|
|
21
|
-
export declare const createSSEProxy: (policyConfig: Required<AgentPolicyRow
|
|
22
|
+
export declare const createSSEProxy: (policyConfig: Required<AgentPolicyRow>, agentId?: string) => SSEProxy;
|
package/dist/sse-proxy.js
CHANGED
|
@@ -42,17 +42,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
42
42
|
exports.createSSEProxy = void 0;
|
|
43
43
|
const http = __importStar(require("http"));
|
|
44
44
|
const https = __importStar(require("https"));
|
|
45
|
-
const
|
|
45
|
+
const security_engine_1 = require("./security-engine");
|
|
46
46
|
const logger_1 = require("./logger");
|
|
47
47
|
const mcp_1 = require("./types/mcp");
|
|
48
48
|
/**
|
|
49
49
|
* Create an SSE/HTTP proxy for MCP servers
|
|
50
50
|
* @param policyConfig - Policy configuration
|
|
51
|
+
* @param agentId - Agent identifier
|
|
51
52
|
* @returns SSE proxy functions
|
|
52
53
|
*/
|
|
53
|
-
const createSSEProxy = (policyConfig) => {
|
|
54
|
-
const policy = (0,
|
|
55
|
-
const logger = (0, logger_1.createLogger)(policyConfig.logPath);
|
|
54
|
+
const createSSEProxy = (policyConfig, agentId = "sse-proxy") => {
|
|
55
|
+
const policy = (0, security_engine_1.createPolicyChecker)(policyConfig);
|
|
56
|
+
const logger = (0, logger_1.createLogger)(policyConfig.logPath, agentId);
|
|
56
57
|
const sessionId = (0, mcp_1.generateSessionId)();
|
|
57
58
|
let toolCallTimestamps = [];
|
|
58
59
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "contextguard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Security monitoring wrapper for MCP servers with enterprise features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -67,4 +67,4 @@
|
|
|
67
67
|
"@anthropic-ai/sdk": "^0.54.0",
|
|
68
68
|
"@supabase/supabase-js": "^2.79.0"
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}
|