@mcp-guardian/server 0.3.0 → 0.5.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 (101) hide show
  1. package/README.md +362 -136
  2. package/dist/auth/auth-types.d.ts +40 -0
  3. package/dist/auth/auth-types.d.ts.map +1 -0
  4. package/dist/auth/auth-types.js +5 -0
  5. package/dist/auth/auth-types.js.map +1 -0
  6. package/dist/auth/dashboard-auth.d.ts +97 -0
  7. package/dist/auth/dashboard-auth.d.ts.map +1 -0
  8. package/dist/auth/dashboard-auth.js +319 -0
  9. package/dist/auth/dashboard-auth.js.map +1 -0
  10. package/dist/auth/dpop.d.ts +38 -0
  11. package/dist/auth/dpop.d.ts.map +1 -0
  12. package/dist/auth/dpop.js +72 -0
  13. package/dist/auth/dpop.js.map +1 -0
  14. package/dist/auth/oauth.d.ts +25 -0
  15. package/dist/auth/oauth.d.ts.map +1 -0
  16. package/dist/auth/oauth.js +96 -0
  17. package/dist/auth/oauth.js.map +1 -0
  18. package/dist/auth/redis-session-cache.d.ts +21 -0
  19. package/dist/auth/redis-session-cache.d.ts.map +1 -0
  20. package/dist/auth/redis-session-cache.js +74 -0
  21. package/dist/auth/redis-session-cache.js.map +1 -0
  22. package/dist/auth/session-cache.d.ts +47 -0
  23. package/dist/auth/session-cache.d.ts.map +1 -0
  24. package/dist/auth/session-cache.js +91 -0
  25. package/dist/auth/session-cache.js.map +1 -0
  26. package/dist/cli.js +48 -3
  27. package/dist/cli.js.map +1 -1
  28. package/dist/database/database-interface.d.ts +17 -0
  29. package/dist/database/database-interface.d.ts.map +1 -0
  30. package/dist/database/database-interface.js +2 -0
  31. package/dist/database/database-interface.js.map +1 -0
  32. package/dist/database/postgres-db.d.ts +18 -0
  33. package/dist/database/postgres-db.d.ts.map +1 -0
  34. package/dist/database/postgres-db.js +118 -0
  35. package/dist/database/postgres-db.js.map +1 -0
  36. package/dist/index.js +1 -1
  37. package/dist/policy/policy-engine.d.ts +19 -0
  38. package/dist/policy/policy-engine.d.ts.map +1 -0
  39. package/dist/policy/policy-engine.js +87 -0
  40. package/dist/policy/policy-engine.js.map +1 -0
  41. package/dist/policy/policy-types.d.ts +42 -0
  42. package/dist/policy/policy-types.d.ts.map +1 -0
  43. package/dist/policy/policy-types.js +5 -0
  44. package/dist/policy/policy-types.js.map +1 -0
  45. package/dist/policy/policy-watcher.d.ts +24 -0
  46. package/dist/policy/policy-watcher.d.ts.map +1 -0
  47. package/dist/policy/policy-watcher.js +68 -0
  48. package/dist/policy/policy-watcher.js.map +1 -0
  49. package/dist/policy/shell-tokenizer.d.ts +92 -0
  50. package/dist/policy/shell-tokenizer.d.ts.map +1 -0
  51. package/dist/policy/shell-tokenizer.js +300 -0
  52. package/dist/policy/shell-tokenizer.js.map +1 -0
  53. package/dist/proxy/http-proxy-server.d.ts +26 -0
  54. package/dist/proxy/http-proxy-server.d.ts.map +1 -0
  55. package/dist/proxy/http-proxy-server.js +172 -0
  56. package/dist/proxy/http-proxy-server.js.map +1 -0
  57. package/dist/proxy/proxy-manager.d.ts +5 -1
  58. package/dist/proxy/proxy-manager.d.ts.map +1 -1
  59. package/dist/proxy/proxy-manager.js +12 -3
  60. package/dist/proxy/proxy-manager.js.map +1 -1
  61. package/dist/proxy/proxy-server.d.ts +20 -5
  62. package/dist/proxy/proxy-server.d.ts.map +1 -1
  63. package/dist/proxy/proxy-server.js +126 -9
  64. package/dist/proxy/proxy-server.js.map +1 -1
  65. package/dist/utils/circuit-breaker.d.ts +29 -0
  66. package/dist/utils/circuit-breaker.d.ts.map +1 -0
  67. package/dist/utils/circuit-breaker.js +81 -0
  68. package/dist/utils/circuit-breaker.js.map +1 -0
  69. package/dist/utils/dashboard-server.d.ts +19 -0
  70. package/dist/utils/dashboard-server.d.ts.map +1 -0
  71. package/dist/utils/dashboard-server.js +258 -0
  72. package/dist/utils/dashboard-server.js.map +1 -0
  73. package/dist/utils/metrics.d.ts +17 -0
  74. package/dist/utils/metrics.d.ts.map +1 -0
  75. package/dist/utils/metrics.js +79 -0
  76. package/dist/utils/metrics.js.map +1 -0
  77. package/dist/utils/mtls-config.d.ts +27 -0
  78. package/dist/utils/mtls-config.d.ts.map +1 -0
  79. package/dist/utils/mtls-config.js +82 -0
  80. package/dist/utils/mtls-config.js.map +1 -0
  81. package/dist/utils/payload-normalizer.d.ts +62 -0
  82. package/dist/utils/payload-normalizer.d.ts.map +1 -0
  83. package/dist/utils/payload-normalizer.js +240 -0
  84. package/dist/utils/payload-normalizer.js.map +1 -0
  85. package/dist/utils/policy-auditor.d.ts +24 -0
  86. package/dist/utils/policy-auditor.d.ts.map +1 -0
  87. package/dist/utils/policy-auditor.js +58 -0
  88. package/dist/utils/policy-auditor.js.map +1 -0
  89. package/dist/utils/redis-rate-limiter.d.ts +22 -0
  90. package/dist/utils/redis-rate-limiter.d.ts.map +1 -0
  91. package/dist/utils/redis-rate-limiter.js +61 -0
  92. package/dist/utils/redis-rate-limiter.js.map +1 -0
  93. package/dist/utils/structured-logger.d.ts +47 -0
  94. package/dist/utils/structured-logger.d.ts.map +1 -0
  95. package/dist/utils/structured-logger.js +48 -0
  96. package/dist/utils/structured-logger.js.map +1 -0
  97. package/dist/utils/tracing.d.ts +7 -0
  98. package/dist/utils/tracing.d.ts.map +1 -0
  99. package/dist/utils/tracing.js +34 -0
  100. package/dist/utils/tracing.js.map +1 -0
  101. package/package.json +14 -8
@@ -0,0 +1,118 @@
1
+ /**
2
+ * PostgreSQL implementation of IDatabase for horizontal scaling.
3
+ * Uses connection pool for production workloads.
4
+ * Enable with: DB_TYPE=postgres DATABASE_URL=postgresql://user:pass@host:5432/db
5
+ */
6
+ import { Pool } from 'pg';
7
+ import { Logger } from '../utils/logger.js';
8
+ export class PostgresDatabase {
9
+ pool;
10
+ initialized = false;
11
+ connectionString;
12
+ constructor() {
13
+ this.connectionString = process.env['DATABASE_URL'] || 'postgresql://localhost:5432/mcp_guardian';
14
+ }
15
+ async initialize() {
16
+ if (this.initialized)
17
+ return;
18
+ this.pool = new Pool({
19
+ connectionString: this.connectionString,
20
+ max: 10,
21
+ idleTimeoutMillis: 30000,
22
+ });
23
+ const client = await this.pool.connect();
24
+ try {
25
+ await client.query(`
26
+ CREATE TABLE IF NOT EXISTS security_scans (
27
+ id SERIAL PRIMARY KEY,
28
+ timestamp TIMESTAMPTZ DEFAULT NOW(),
29
+ server_name TEXT NOT NULL,
30
+ score INTEGER NOT NULL,
31
+ cve_count INTEGER NOT NULL DEFAULT 0,
32
+ details JSONB
33
+ )
34
+ `);
35
+ await client.query(`
36
+ CREATE TABLE IF NOT EXISTS cost_records (
37
+ id SERIAL PRIMARY KEY,
38
+ timestamp TIMESTAMPTZ DEFAULT NOW(),
39
+ server_name TEXT NOT NULL,
40
+ tokens_used INTEGER NOT NULL,
41
+ cost_usd REAL NOT NULL
42
+ )
43
+ `);
44
+ await client.query(`
45
+ CREATE TABLE IF NOT EXISTS health_checks (
46
+ id SERIAL PRIMARY KEY,
47
+ timestamp TIMESTAMPTZ DEFAULT NOW(),
48
+ server_name TEXT NOT NULL,
49
+ latency_ms INTEGER NOT NULL,
50
+ success INTEGER NOT NULL,
51
+ tool_count INTEGER NOT NULL
52
+ )
53
+ `);
54
+ await client.query(`
55
+ CREATE TABLE IF NOT EXISTS call_records (
56
+ id SERIAL PRIMARY KEY,
57
+ timestamp TIMESTAMPTZ DEFAULT NOW(),
58
+ server_name TEXT NOT NULL,
59
+ tool_name TEXT NOT NULL,
60
+ request_tokens INTEGER NOT NULL DEFAULT 0,
61
+ response_tokens INTEGER NOT NULL DEFAULT 0,
62
+ total_tokens INTEGER NOT NULL DEFAULT 0,
63
+ duration_ms INTEGER NOT NULL DEFAULT 0
64
+ )
65
+ `);
66
+ await client.query('CREATE INDEX IF NOT EXISTS idx_security_server ON security_scans(server_name)');
67
+ await client.query('CREATE INDEX IF NOT EXISTS idx_cost_server ON cost_records(server_name)');
68
+ await client.query('CREATE INDEX IF NOT EXISTS idx_health_server ON health_checks(server_name)');
69
+ await client.query('CREATE INDEX IF NOT EXISTS idx_call_server ON call_records(server_name)');
70
+ this.initialized = true;
71
+ Logger.info('PostgreSQL database initialized');
72
+ }
73
+ finally {
74
+ client.release();
75
+ }
76
+ }
77
+ async getRecentSuccessRate(serverName) {
78
+ const result = await this.pool.query('SELECT AVG(success) as avg FROM health_checks WHERE server_name = $1 ORDER BY timestamp DESC LIMIT 10', [serverName]);
79
+ if (result.rows.length > 0 && result.rows[0].avg !== null) {
80
+ return Number(result.rows[0].avg);
81
+ }
82
+ return 1;
83
+ }
84
+ async addSecurityScan(serverName, score, cveCount, details) {
85
+ await this.pool.query('INSERT INTO security_scans (server_name, score, cve_count, details) VALUES ($1, $2, $3, $4)', [serverName, score, cveCount, JSON.stringify(details)]);
86
+ }
87
+ async addCostRecord(serverName, tokens, cost) {
88
+ await this.pool.query('INSERT INTO cost_records (server_name, tokens_used, cost_usd) VALUES ($1, $2, $3)', [serverName, tokens, cost]);
89
+ }
90
+ async addHealthCheck(serverName, latency, success, toolCount) {
91
+ await this.pool.query('INSERT INTO health_checks (server_name, latency_ms, success, tool_count) VALUES ($1, $2, $3, $4)', [serverName, latency, success ? 1 : 0, toolCount]);
92
+ }
93
+ async addCallRecord(record) {
94
+ await this.pool.query('INSERT INTO call_records (server_name, tool_name, request_tokens, response_tokens, total_tokens, duration_ms) VALUES ($1, $2, $3, $4, $5, $6)', [record.serverName, record.toolName, record.requestTokens, record.responseTokens, record.totalTokens, record.durationMs]);
95
+ }
96
+ async getCallRecordsForServer(serverName) {
97
+ const result = await this.pool.query('SELECT server_name, tool_name, request_tokens, response_tokens, total_tokens, duration_ms, timestamp::text FROM call_records WHERE server_name = $1', [serverName]);
98
+ return result.rows.map((row) => ({
99
+ serverName: row.server_name,
100
+ toolName: row.tool_name,
101
+ requestTokens: row.request_tokens,
102
+ responseTokens: row.response_tokens,
103
+ totalTokens: row.total_tokens,
104
+ durationMs: row.duration_ms,
105
+ timestamp: row.timestamp,
106
+ }));
107
+ }
108
+ async flush() {
109
+ // PostgreSQL auto-commits — no flush needed
110
+ }
111
+ async close() {
112
+ if (this.pool) {
113
+ await this.pool.end();
114
+ this.initialized = false;
115
+ }
116
+ }
117
+ }
118
+ //# sourceMappingURL=postgres-db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-db.js","sourceRoot":"","sources":["../../src/database/postgres-db.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { Logger } from './utils/logger.js';
9
9
  import { createContainer } from './container.js';
10
10
  const container = createContainer();
11
11
  const reporter = new ReportGenerator();
12
- const server = new Server({ name: 'mcp-guardian', version: '0.3.0' }, { capabilities: { tools: {} } });
12
+ const server = new Server({ name: 'mcp-guardian', version: '0.5.0' }, { capabilities: { tools: {} } });
13
13
  // ── Logging capability (MCP spec requirement) ─────────────────────
14
14
  let currentLogLevel = 'info';
15
15
  server.setRequestHandler(SetLevelRequestSchema, async (request) => {
@@ -0,0 +1,19 @@
1
+ import { PolicyConfig, PolicyDecision, CallContext, PolicyMode } from './policy-types.js';
2
+ /**
3
+ * Policy Engine — evaluates every intercepted tools/call against configured rules.
4
+ * Supports three modes: audit (passive), warn (flag only), block (active enforcement).
5
+ */
6
+ export declare class PolicyEngine {
7
+ private rules;
8
+ private mode;
9
+ private callCounters;
10
+ constructor(config: PolicyConfig);
11
+ /**
12
+ * Evaluate a tools/call request and return a decision.
13
+ */
14
+ evaluate(context: CallContext): PolicyDecision;
15
+ private evaluateRule;
16
+ private resolveAction;
17
+ getMode(): PolicyMode;
18
+ }
19
+ //# sourceMappingURL=policy-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-engine.d.ts","sourceRoot":"","sources":["../../src/policy/policy-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAgB,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGxG;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,YAAY,CAA8D;gBAEtE,MAAM,EAAE,YAAY;IAKhC;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;IAU9C,OAAO,CAAC,YAAY;IAqDpB,OAAO,CAAC,aAAa;IAMrB,OAAO,IAAI,UAAU;CAGtB"}
@@ -0,0 +1,87 @@
1
+ import { Logger } from '../utils/logger.js';
2
+ /**
3
+ * Policy Engine — evaluates every intercepted tools/call against configured rules.
4
+ * Supports three modes: audit (passive), warn (flag only), block (active enforcement).
5
+ */
6
+ export class PolicyEngine {
7
+ rules;
8
+ mode;
9
+ callCounters = new Map();
10
+ constructor(config) {
11
+ this.rules = config.policy.rules;
12
+ this.mode = config.policy.mode;
13
+ }
14
+ /**
15
+ * Evaluate a tools/call request and return a decision.
16
+ */
17
+ evaluate(context) {
18
+ for (const rule of this.rules) {
19
+ const decision = this.evaluateRule(rule, context);
20
+ if (decision)
21
+ return decision;
22
+ }
23
+ // Default: pass
24
+ return { action: 'pass', rule: 'default', reason: 'No policy rules matched' };
25
+ }
26
+ evaluateRule(rule, ctx) {
27
+ // Tool allowlist/denylist
28
+ if (rule.tools) {
29
+ if (rule.tools.allow && rule.tools.allow.length > 0) {
30
+ if (!rule.tools.allow.includes(ctx.toolName)) {
31
+ return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Tool '${ctx.toolName}' not in allowlist: [${rule.tools.allow.join(', ')}]` };
32
+ }
33
+ }
34
+ if (rule.tools.deny && rule.tools.deny.length > 0) {
35
+ if (rule.tools.deny.includes(ctx.toolName)) {
36
+ return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Tool '${ctx.toolName}' is explicitly denied` };
37
+ }
38
+ }
39
+ }
40
+ // Malicious pattern detection
41
+ if (rule.patterns) {
42
+ const argsStr = ctx.arguments ? JSON.stringify(ctx.arguments) : '';
43
+ for (const pattern of rule.patterns) {
44
+ try {
45
+ if (new RegExp(pattern).test(argsStr)) {
46
+ return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Argument pattern '${pattern}' matched in tool call` };
47
+ }
48
+ }
49
+ catch {
50
+ Logger.warn(`Policy: invalid regex pattern in rule '${rule.name}': ${pattern}`);
51
+ }
52
+ }
53
+ }
54
+ // Max tokens per call
55
+ if (rule.maxTokens && ctx.requestTokens > rule.maxTokens) {
56
+ return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Token count ${ctx.requestTokens} exceeds max ${rule.maxTokens}` };
57
+ }
58
+ // Rate limiting
59
+ if (rule.maxCallsPerMinute) {
60
+ const key = `${ctx.serverName}:${ctx.toolName}`;
61
+ const now = Date.now();
62
+ let counter = this.callCounters.get(key);
63
+ if (!counter || now > counter.resetAt) {
64
+ counter = { count: 1, resetAt: now + 60000 };
65
+ this.callCounters.set(key, counter);
66
+ }
67
+ else {
68
+ counter.count++;
69
+ if (counter.count > rule.maxCallsPerMinute) {
70
+ return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Rate limit exceeded: ${counter.count}/${rule.maxCallsPerMinute} calls per minute` };
71
+ }
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+ resolveAction(ruleAction) {
77
+ if (this.mode === 'audit')
78
+ return 'pass';
79
+ if (this.mode === 'warn' && ruleAction === 'block')
80
+ return 'flag';
81
+ return ruleAction;
82
+ }
83
+ getMode() {
84
+ return this.mode;
85
+ }
86
+ }
87
+ //# sourceMappingURL=policy-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-engine.js","sourceRoot":"","sources":["../../src/policy/policy-engine.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;GAGG;AACH,MAAM,OAAO,YAAY;IACf,KAAK,CAAkC;IACvC,IAAI,CAAa;IACjB,YAAY,GAAoD,IAAI,GAAG,EAAE,CAAC;IAElF,YAAY,MAAoB;QAC9B,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,OAAoB;QAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAClD,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAC;QAChC,CAAC;QAED,gBAAgB;QAChB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;IAChF,CAAC;IAEO,YAAY,CAAC,IAA6C,EAAE,GAAgB;QAClF,0BAA0B;QAC1B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC7C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,QAAQ,wBAAwB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3J,CAAC;YACH,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAClD,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;oBAC3C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,QAAQ,wBAAwB,EAAE,CAAC;gBAC7H,CAAC;YACH,CAAC;QACH,CAAC;QAED,8BAA8B;QAC9B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACpC,IAAI,CAAC;oBACH,IAAI,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;wBACtC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,qBAAqB,OAAO,wBAAwB,EAAE,CAAC;oBACpI,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,CAAC,IAAI,CAAC,0CAA0C,IAAI,CAAC,IAAI,MAAM,OAAO,EAAE,CAAC,CAAC;gBAClF,CAAC;YACH,CAAC;QACH,CAAC;QAED,sBAAsB;QACtB,IAAI,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YACzD,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,GAAG,CAAC,aAAa,gBAAgB,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;QAChJ,CAAC;QAED,gBAAgB;QAChB,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAC3B,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,IAAI,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACzC,IAAI,CAAC,OAAO,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;gBACtC,OAAO,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,KAAK,EAAE,CAAC;gBAC7C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,EAAE,CAAC;gBAChB,IAAI,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBAC3C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,wBAAwB,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,iBAAiB,mBAAmB,EAAE,CAAC;gBAClK,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,aAAa,CAAC,UAAwB;QAC5C,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,MAAM,CAAC;QACzC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,UAAU,KAAK,OAAO;YAAE,OAAO,MAAM,CAAC;QAClE,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Policy types for the MCP Guardian active blocking engine.
3
+ */
4
+ export type PolicyAction = 'pass' | 'block' | 'flag';
5
+ export type PolicyMode = 'audit' | 'warn' | 'block';
6
+ export interface PolicyRule {
7
+ name: string;
8
+ description?: string;
9
+ action: PolicyAction;
10
+ /** Tool name allowlist — if specified, only these tools are permitted */
11
+ tools?: {
12
+ allow?: string[];
13
+ deny?: string[];
14
+ };
15
+ /** Regex patterns for blocking malicious tool arguments */
16
+ patterns?: string[];
17
+ /** Max tokens per call */
18
+ maxTokens?: number;
19
+ /** Max calls per minute per server */
20
+ maxCallsPerMinute?: number;
21
+ }
22
+ export interface PolicyConfig {
23
+ version: string;
24
+ policy: {
25
+ mode: PolicyMode;
26
+ rules: PolicyRule[];
27
+ };
28
+ }
29
+ export interface PolicyDecision {
30
+ action: PolicyAction;
31
+ rule: string;
32
+ reason: string;
33
+ }
34
+ export interface CallContext {
35
+ serverName: string;
36
+ toolName: string;
37
+ arguments?: Record<string, unknown>;
38
+ requestId: string | number;
39
+ requestTokens: number;
40
+ timestamp: string;
41
+ }
42
+ //# sourceMappingURL=policy-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-types.d.ts","sourceRoot":"","sources":["../../src/policy/policy-types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,yEAAyE;IACzE,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACjB,CAAC;IACF,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE;QACN,IAAI,EAAE,UAAU,CAAC;QACjB,KAAK,EAAE,UAAU,EAAE,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Policy types for the MCP Guardian active blocking engine.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=policy-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-types.js","sourceRoot":"","sources":["../../src/policy/policy-types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,24 @@
1
+ import { PolicyEngine } from './policy-engine.js';
2
+ /**
3
+ * Hot-reloadable policy engine wrapper.
4
+ * Watches a YAML policy file for changes and atomically swaps the active policy.
5
+ * In-flight requests continue using the old policy; new requests use the updated one.
6
+ */
7
+ export declare class PolicyWatcher {
8
+ private current;
9
+ private watcher;
10
+ private policyPath;
11
+ constructor(policyPath: string);
12
+ private loadPolicy;
13
+ private startWatching;
14
+ /**
15
+ * Get the current (active) policy engine.
16
+ * This is always the latest loaded version.
17
+ */
18
+ get(): PolicyEngine | null;
19
+ /**
20
+ * Stop watching and clean up.
21
+ */
22
+ close(): void;
23
+ }
24
+ //# sourceMappingURL=policy-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-watcher.d.ts","sourceRoot":"","sources":["../../src/policy/policy-watcher.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,EAAE,MAAM;IAM9B,OAAO,CAAC,UAAU;IAgBlB,OAAO,CAAC,aAAa;IAmBrB;;;OAGG;IACH,GAAG,IAAI,YAAY,GAAG,IAAI;IAI1B;;OAEG;IACH,KAAK,IAAI,IAAI;CAMd"}
@@ -0,0 +1,68 @@
1
+ import { watch } from 'chokidar';
2
+ import { readFileSync } from 'fs';
3
+ import { load } from 'js-yaml';
4
+ import { PolicyEngine } from './policy-engine.js';
5
+ import { Logger } from '../utils/logger.js';
6
+ /**
7
+ * Hot-reloadable policy engine wrapper.
8
+ * Watches a YAML policy file for changes and atomically swaps the active policy.
9
+ * In-flight requests continue using the old policy; new requests use the updated one.
10
+ */
11
+ export class PolicyWatcher {
12
+ current = null;
13
+ watcher = null;
14
+ policyPath;
15
+ constructor(policyPath) {
16
+ this.policyPath = policyPath;
17
+ this.loadPolicy();
18
+ this.startWatching();
19
+ }
20
+ loadPolicy() {
21
+ try {
22
+ const yaml = readFileSync(this.policyPath, 'utf-8');
23
+ const config = load(yaml);
24
+ const oldMode = this.current?.getMode();
25
+ this.current = new PolicyEngine(config);
26
+ Logger.info(`[policy-watcher] Policy loaded (mode: ${config.policy.mode}, rules: ${config.policy.rules.length})${oldMode && oldMode !== config.policy.mode ? ` (mode changed from ${oldMode})` : ''}`);
27
+ }
28
+ catch (err) {
29
+ Logger.error(`[policy-watcher] Failed to load policy: ${err?.message}`);
30
+ // Don't replace the current policy on parse failure
31
+ if (!this.current) {
32
+ throw err; // No existing policy — must fail
33
+ }
34
+ }
35
+ }
36
+ startWatching() {
37
+ this.watcher = watch(this.policyPath, {
38
+ persistent: true,
39
+ ignoreInitial: true,
40
+ awaitWriteFinish: { stabilityThreshold: 300, pollInterval: 100 },
41
+ });
42
+ this.watcher.on('change', () => {
43
+ Logger.info(`[policy-watcher] Policy file changed, reloading...`);
44
+ this.loadPolicy();
45
+ });
46
+ this.watcher.on('error', (err) => {
47
+ Logger.error(`[policy-watcher] Watch error: ${err?.message || String(err)}`);
48
+ });
49
+ Logger.info(`[policy-watcher] Watching ${this.policyPath} for changes`);
50
+ }
51
+ /**
52
+ * Get the current (active) policy engine.
53
+ * This is always the latest loaded version.
54
+ */
55
+ get() {
56
+ return this.current;
57
+ }
58
+ /**
59
+ * Stop watching and clean up.
60
+ */
61
+ close() {
62
+ if (this.watcher) {
63
+ this.watcher.close();
64
+ this.watcher = null;
65
+ }
66
+ }
67
+ }
68
+ //# sourceMappingURL=policy-watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-watcher.js","sourceRoot":"","sources":["../../src/policy/policy-watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAa,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C;;;;GAIG;AACH,MAAM,OAAO,aAAa;IAChB,OAAO,GAAwB,IAAI,CAAC;IACpC,OAAO,GAAqB,IAAI,CAAC;IACjC,UAAU,CAAS;IAE3B,YAAY,UAAkB;QAC5B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAiB,CAAC;YAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;YACxC,IAAI,CAAC,OAAO,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,yCAAyC,MAAM,CAAC,MAAM,CAAC,IAAI,YAAY,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,OAAO,IAAI,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,uBAAuB,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzM,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,2CAA2C,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YACxE,oDAAoD;YACpD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,GAAG,CAAC,CAAC,iCAAiC;YAC9C,CAAC;QACH,CAAC;IACH,CAAC;IAEO,aAAa;QACnB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE;YACpC,UAAU,EAAE,IAAI;YAChB,aAAa,EAAE,IAAI;YACnB,gBAAgB,EAAE,EAAE,kBAAkB,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,EAAE;SACjE,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;YAC7B,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;YAClE,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAQ,EAAE,EAAE;YACpC,MAAM,CAAC,KAAK,CAAC,iCAAiC,GAAG,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/E,CAAC,CAAC,CAAC;QAEH,MAAM,CAAC,IAAI,CAAC,6BAA6B,IAAI,CAAC,UAAU,cAAc,CAAC,CAAC;IAC1E,CAAC;IAED;;;OAGG;IACH,GAAG;QACD,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Shell Command Tokenizer & Semantic Analyzer
3
+ *
4
+ * Parses tool argument strings into tokenized AST nodes for semantic
5
+ * security analysis. Goes beyond regex pattern matching by understanding
6
+ * shell grammar: pipelines, redirects, command substitutions, logical chains.
7
+ *
8
+ * This is the semantic detection layer that addresses the architectural
9
+ * limitation of regex-only detection. Instead of pattern-matching "$(rm -rf /)"
10
+ * we parse it as a CommandSubstitution AST node and then analyze the inner
11
+ * command semantically.
12
+ */
13
+ export declare enum TokenType {
14
+ WORD = "WORD",
15
+ STRING = "STRING",
16
+ VARIABLE = "VARIABLE",
17
+ COMMAND_SUBSTITUTION = "COMMAND_SUBSTITUTION",
18
+ BACKTICK_SUBSTITUTION = "BACKTICK_SUBSTITUTION",
19
+ PIPE = "PIPE",
20
+ REDIRECT = "REDIRECT",
21
+ SEMICOLON = "SEMICOLON",
22
+ AND_IF = "AND_IF",// &&
23
+ OR_IF = "OR_IF",// ||
24
+ BACKGROUND = "BACKGROUND",
25
+ SUBSHELL = "SUBSHELL"
26
+ }
27
+ export interface Token {
28
+ type: TokenType;
29
+ value: string;
30
+ /** Start position in original string */
31
+ start: number;
32
+ /** End position in original string */
33
+ end: number;
34
+ /** For compound tokens (substitution, subshell), nested tokens */
35
+ children?: Token[];
36
+ }
37
+ export interface ShellAST {
38
+ /** Top-level commands (separated by ;, &&, ||, &) */
39
+ commands: Token[];
40
+ /** Whether the input contained potentially dangerous constructs */
41
+ warnings: string[];
42
+ }
43
+ /**
44
+ * Dangerous command categories for semantic analysis.
45
+ */
46
+ export interface CommandRisk {
47
+ /** High risk: command substitution present */
48
+ hasCommandSubstitution: boolean;
49
+ /** High risk: pipe chains present */
50
+ hasPipes: boolean;
51
+ /** Medium risk: redirect operators present */
52
+ hasRedirects: boolean;
53
+ /** Medium risk: logical chain operators */
54
+ hasLogicalChains: boolean;
55
+ /** Dangerous commands detected in tokenized words */
56
+ dangerousCommands: string[];
57
+ /** Shell metacharacters found */
58
+ shellMetacharacters: string[];
59
+ }
60
+ /**
61
+ * ShellTokenizer parses shell-like input into an AST without executing anything.
62
+ * It's a security analyzer, not a full POSIX shell parser — focus is on detecting
63
+ * execution patterns that signal malicious intent.
64
+ */
65
+ export declare class ShellTokenizer {
66
+ private readonly DANGEROUS_COMMANDS;
67
+ /**
68
+ * Tokenize a string that may contain shell syntax.
69
+ */
70
+ tokenize(input: string): ShellAST;
71
+ /**
72
+ * Parse the next token starting at position pos.
73
+ */
74
+ private nextToken;
75
+ /**
76
+ * Parse a delimited token like $(...), ${...}, `...`, (...).
77
+ * Recursively tokenizes inner content.
78
+ */
79
+ private parseDelimited;
80
+ /**
81
+ * Analyze a token for risk.
82
+ */
83
+ analyzeRisk(tokens: Token[]): CommandRisk;
84
+ /**
85
+ * Full semantic analysis: tokenize + assess risk.
86
+ */
87
+ analyze(input: string): {
88
+ ast: ShellAST;
89
+ risk: CommandRisk;
90
+ };
91
+ }
92
+ //# sourceMappingURL=shell-tokenizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-tokenizer.d.ts","sourceRoot":"","sources":["../../src/policy/shell-tokenizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,oBAAoB,yBAAyB;IAC7C,qBAAqB,0BAA0B;IAC/C,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,SAAS,cAAc;IACvB,MAAM,WAAW,CAAM,KAAK;IAC5B,KAAK,UAAU,CAAQ,KAAK;IAC5B,UAAU,eAAe;IACzB,QAAQ,aAAa;CACtB;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,qDAAqD;IACrD,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,sBAAsB,EAAE,OAAO,CAAC;IAChC,qCAAqC;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,YAAY,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qDAAqD;IACrD,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,iCAAiC;IACjC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAShC;IAEH;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ;IA6BjC;;OAEG;IACH,OAAO,CAAC,SAAS;IA2IjB;;;OAGG;IACH,OAAO,CAAC,cAAc;IA0CtB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,WAAW;IAoDzC;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,GAAG,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,WAAW,CAAA;KAAE;CAK7D"}