@mcp-guardian/server 0.3.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +373 -136
- package/dist/auth/auth-types.d.ts +40 -0
- package/dist/auth/auth-types.d.ts.map +1 -0
- package/dist/auth/auth-types.js +5 -0
- package/dist/auth/auth-types.js.map +1 -0
- package/dist/auth/oauth.d.ts +25 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +96 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/session-cache.d.ts +47 -0
- package/dist/auth/session-cache.d.ts.map +1 -0
- package/dist/auth/session-cache.js +91 -0
- package/dist/auth/session-cache.js.map +1 -0
- package/dist/cli.js +48 -3
- package/dist/cli.js.map +1 -1
- package/dist/database/database-interface.d.ts +17 -0
- package/dist/database/database-interface.d.ts.map +1 -0
- package/dist/database/database-interface.js +2 -0
- package/dist/database/database-interface.js.map +1 -0
- package/dist/database/history-db.d.ts.map +1 -1
- package/dist/database/history-db.js.map +1 -1
- package/dist/database/postgres-db.d.ts +18 -0
- package/dist/database/postgres-db.d.ts.map +1 -0
- package/dist/database/postgres-db.js +118 -0
- package/dist/database/postgres-db.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/policy/policy-engine.d.ts +19 -0
- package/dist/policy/policy-engine.d.ts.map +1 -0
- package/dist/policy/policy-engine.js +108 -0
- package/dist/policy/policy-engine.js.map +1 -0
- package/dist/policy/policy-types.d.ts +51 -0
- package/dist/policy/policy-types.d.ts.map +1 -0
- package/dist/policy/policy-types.js +5 -0
- package/dist/policy/policy-types.js.map +1 -0
- package/dist/policy/policy-watcher.d.ts +24 -0
- package/dist/policy/policy-watcher.d.ts.map +1 -0
- package/dist/policy/policy-watcher.js +68 -0
- package/dist/policy/policy-watcher.js.map +1 -0
- package/dist/proxy/proxy-manager.d.ts +5 -1
- package/dist/proxy/proxy-manager.d.ts.map +1 -1
- package/dist/proxy/proxy-manager.js +12 -3
- package/dist/proxy/proxy-manager.js.map +1 -1
- package/dist/proxy/proxy-server.d.ts +25 -5
- package/dist/proxy/proxy-server.d.ts.map +1 -1
- package/dist/proxy/proxy-server.js +239 -12
- package/dist/proxy/proxy-server.js.map +1 -1
- package/dist/utils/circuit-breaker.d.ts +29 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +81 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/structured-logger.d.ts +47 -0
- package/dist/utils/structured-logger.d.ts.map +1 -0
- package/dist/utils/structured-logger.js +48 -0
- package/dist/utils/structured-logger.js.map +1 -0
- package/package.json +18 -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.
|
|
12
|
+
const server = new Server({ name: 'mcp-guardian', version: '0.6.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;IA2EpB,OAAO,CAAC,aAAa;IAMrB,OAAO,IAAI,UAAU;CAGtB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
// v0.5.1: RBAC — scope and client_id constraints
|
|
59
|
+
if (rule.rbac) {
|
|
60
|
+
const identity = ctx.agentIdentity;
|
|
61
|
+
if (!identity) {
|
|
62
|
+
return { action: this.resolveAction(rule.action), rule: rule.name, reason: `RBAC rule '${rule.name}' requires agent identity but none provided` };
|
|
63
|
+
}
|
|
64
|
+
if (rule.rbac.scopes && rule.rbac.scopes.length > 0) {
|
|
65
|
+
const agentScopes = identity.scopes || [];
|
|
66
|
+
const hasScope = rule.rbac.scopes.some(s => agentScopes.includes(s));
|
|
67
|
+
if (!hasScope) {
|
|
68
|
+
return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Agent '${identity.sub}' missing required scope. Need one of: [${rule.rbac.scopes.join(', ')}], have: [${agentScopes.join(', ') || 'none'}]` };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (rule.rbac.clientIds && rule.rbac.clientIds.length > 0) {
|
|
72
|
+
const clientId = identity.clientId || '';
|
|
73
|
+
const matches = rule.rbac.clientIds.some(pattern => new RegExp(pattern).test(clientId));
|
|
74
|
+
if (!matches) {
|
|
75
|
+
return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Client ID '${clientId}' not allowed. Allowed patterns: [${rule.rbac.clientIds.join(', ')}]` };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Rate limiting
|
|
80
|
+
if (rule.maxCallsPerMinute) {
|
|
81
|
+
const key = `${ctx.serverName}:${ctx.toolName}`;
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
let counter = this.callCounters.get(key);
|
|
84
|
+
if (!counter || now > counter.resetAt) {
|
|
85
|
+
counter = { count: 1, resetAt: now + 60000 };
|
|
86
|
+
this.callCounters.set(key, counter);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
counter.count++;
|
|
90
|
+
if (counter.count > rule.maxCallsPerMinute) {
|
|
91
|
+
return { action: this.resolveAction(rule.action), rule: rule.name, reason: `Rate limit exceeded: ${counter.count}/${rule.maxCallsPerMinute} calls per minute` };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
resolveAction(ruleAction) {
|
|
98
|
+
if (this.mode === 'audit')
|
|
99
|
+
return 'pass';
|
|
100
|
+
if (this.mode === 'warn' && ruleAction === 'block')
|
|
101
|
+
return 'flag';
|
|
102
|
+
return ruleAction;
|
|
103
|
+
}
|
|
104
|
+
getMode() {
|
|
105
|
+
return this.mode;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=policy-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-engine.js","sourceRoot":"","sources":["../../src/policy/policy-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
/** v0.5.1: RBAC — scope and client_id constraints */
|
|
22
|
+
rbac?: {
|
|
23
|
+
/** Required scopes the agent must have */
|
|
24
|
+
scopes?: string[];
|
|
25
|
+
/** Allowed client IDs (regex patterns supported) */
|
|
26
|
+
clientIds?: string[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface PolicyConfig {
|
|
30
|
+
version: string;
|
|
31
|
+
policy: {
|
|
32
|
+
mode: PolicyMode;
|
|
33
|
+
rules: PolicyRule[];
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface PolicyDecision {
|
|
37
|
+
action: PolicyAction;
|
|
38
|
+
rule: string;
|
|
39
|
+
reason: string;
|
|
40
|
+
}
|
|
41
|
+
export interface CallContext {
|
|
42
|
+
serverName: string;
|
|
43
|
+
toolName: string;
|
|
44
|
+
arguments?: Record<string, unknown>;
|
|
45
|
+
requestId: string | number;
|
|
46
|
+
requestTokens: number;
|
|
47
|
+
timestamp: string;
|
|
48
|
+
/** v0.5.1: Agent identity from OAuth (for RBAC) */
|
|
49
|
+
agentIdentity?: import('../auth/auth-types.js').AgentIdentity;
|
|
50
|
+
}
|
|
51
|
+
//# 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;IAC3B,qDAAqD;IACrD,IAAI,CAAC,EAAE;QACL,0CAA0C;QAC1C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,oDAAoD;QACpD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;CACH;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;IAClB,mDAAmD;IACnD,aAAa,CAAC,EAAE,OAAO,uBAAuB,EAAE,aAAa,CAAC;CAC/D"}
|
|
@@ -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"}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { HistoryDatabase } from '../database/history-db.js';
|
|
2
2
|
import { McpProxyServer } from './proxy-server.js';
|
|
3
3
|
import { McpServerConfig } from '../types.js';
|
|
4
|
+
import { PolicyEngine } from '../policy/policy-engine.js';
|
|
5
|
+
import { OAuthValidator } from '../auth/oauth.js';
|
|
4
6
|
export declare class ProxyManager {
|
|
5
7
|
private db;
|
|
8
|
+
private policyEngine?;
|
|
9
|
+
private authValidator?;
|
|
6
10
|
private proxies;
|
|
7
|
-
constructor(db: HistoryDatabase);
|
|
11
|
+
constructor(db: HistoryDatabase, policyEngine?: PolicyEngine | undefined, authValidator?: OAuthValidator | undefined);
|
|
8
12
|
getProxies(): McpProxyServer[];
|
|
9
13
|
startAll(configs: McpServerConfig[]): Promise<void>;
|
|
10
14
|
stopAll(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-manager.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy-manager.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,qBAAa,YAAY;IAIrB,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,YAAY,CAAC;IACrB,OAAO,CAAC,aAAa,CAAC;IALxB,OAAO,CAAC,OAAO,CAAwB;gBAG7B,EAAE,EAAE,eAAe,EACnB,YAAY,CAAC,EAAE,YAAY,YAAA,EAC3B,aAAa,CAAC,EAAE,cAAc,YAAA;IAGxC,UAAU,IAAI,cAAc,EAAE;IAIxB,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BzD,OAAO,IAAI,IAAI;CAOhB"}
|
|
@@ -2,9 +2,13 @@ import { McpProxyServer } from './proxy-server.js';
|
|
|
2
2
|
import { Logger } from '../utils/logger.js';
|
|
3
3
|
export class ProxyManager {
|
|
4
4
|
db;
|
|
5
|
+
policyEngine;
|
|
6
|
+
authValidator;
|
|
5
7
|
proxies = [];
|
|
6
|
-
constructor(db) {
|
|
8
|
+
constructor(db, policyEngine, authValidator) {
|
|
7
9
|
this.db = db;
|
|
10
|
+
this.policyEngine = policyEngine;
|
|
11
|
+
this.authValidator = authValidator;
|
|
8
12
|
}
|
|
9
13
|
getProxies() {
|
|
10
14
|
return this.proxies;
|
|
@@ -13,9 +17,14 @@ export class ProxyManager {
|
|
|
13
17
|
for (const config of configs) {
|
|
14
18
|
if (config.transport === 'stdio' && config.command) {
|
|
15
19
|
try {
|
|
16
|
-
const proxy = new McpProxyServer(config.command, config.args || [], config.env || {}, this.db, config.name);
|
|
20
|
+
const proxy = new McpProxyServer(config.command, config.args || [], config.env || {}, this.db, config.name, this.policyEngine, this.authValidator);
|
|
17
21
|
this.proxies.push(proxy);
|
|
18
|
-
|
|
22
|
+
const extras = [];
|
|
23
|
+
if (this.policyEngine)
|
|
24
|
+
extras.push(`policy: ${this.policyEngine.getMode()}`);
|
|
25
|
+
if (this.authValidator)
|
|
26
|
+
extras.push('auth: OAuth 2.1');
|
|
27
|
+
Logger.info(`Proxy started for ${config.name} (${config.command})${extras.length ? ` [${extras.join(', ')}]` : ''}`);
|
|
19
28
|
}
|
|
20
29
|
catch (err) {
|
|
21
30
|
Logger.error(`Failed to start proxy for ${config.name}: ${err?.message}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-manager.js","sourceRoot":"","sources":["../../src/proxy/proxy-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy-manager.js","sourceRoot":"","sources":["../../src/proxy/proxy-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAI5C,MAAM,OAAO,YAAY;IAIb;IACA;IACA;IALF,OAAO,GAAqB,EAAE,CAAC;IAEvC,YACU,EAAmB,EACnB,YAA2B,EAC3B,aAA8B;QAF9B,OAAE,GAAF,EAAE,CAAiB;QACnB,iBAAY,GAAZ,YAAY,CAAe;QAC3B,kBAAa,GAAb,aAAa,CAAiB;IACrC,CAAC;IAEJ,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAA0B;QACvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,SAAS,KAAK,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnD,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,IAAI,cAAc,CAC9B,MAAM,CAAC,OAAO,EACd,MAAM,CAAC,IAAI,IAAI,EAAE,EACjB,MAAM,CAAC,GAAG,IAAI,EAAE,EAChB,IAAI,CAAC,EAAE,EACP,MAAM,CAAC,IAAI,EACX,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,aAAa,CACnB,CAAC;oBACF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBACzB,MAAM,MAAM,GAAa,EAAE,CAAC;oBAC5B,IAAI,IAAI,CAAC,YAAY;wBAAE,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;oBAC7E,IAAI,IAAI,CAAC,aAAa;wBAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;oBACvD,MAAM,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACvH,CAAC;gBAAC,OAAO,GAAQ,EAAE,CAAC;oBAClB,MAAM,CAAC,KAAK,CAAC,6BAA6B,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;gBAC5E,CAAC;YACH,CAAC;iBAAM,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;gBACtB,MAAM,CAAC,IAAI,CAAC,iBAAiB,MAAM,CAAC,IAAI,+BAA+B,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,MAAM,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IACrC,CAAC;CACF"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { HistoryDatabase } from '../database/history-db.js';
|
|
2
|
+
import { PolicyEngine } from '../policy/policy-engine.js';
|
|
3
|
+
import { OAuthValidator } from '../auth/oauth.js';
|
|
2
4
|
/**
|
|
3
|
-
* MCP Proxy Interceptor — sits between the AI client and an MCP server
|
|
4
|
-
*
|
|
5
|
+
* MCP Proxy Interceptor — sits between the AI client and an MCP server.
|
|
6
|
+
*
|
|
7
|
+
* v0.4: Integrated PolicyEngine for active blocking of malicious tool calls.
|
|
8
|
+
* v0.5: OAuth 2.1 JWT validation — validates bearer tokens before policy evaluation.
|
|
9
|
+
* v0.5.2: Circuit breaker for upstream MCP server failures.
|
|
10
|
+
* v0.5.2: Per-client rate limiting (keyed by agent sub + tool name).
|
|
11
|
+
* v0.5.2: Consistent SIEM fields (request_id, proxy_latency_ms, authn_success, authz_allowed).
|
|
5
12
|
*/
|
|
6
13
|
export declare class McpProxyServer {
|
|
7
14
|
private child;
|
|
@@ -11,16 +18,29 @@ export declare class McpProxyServer {
|
|
|
11
18
|
private requestStartTime;
|
|
12
19
|
private requestToolName;
|
|
13
20
|
private requestTokens;
|
|
21
|
+
private requestArguments;
|
|
14
22
|
private serverName;
|
|
15
|
-
|
|
23
|
+
private policyEngine;
|
|
24
|
+
private authValidator;
|
|
25
|
+
private sessionCache;
|
|
26
|
+
private circuitBreaker;
|
|
27
|
+
/** v0.5.2: Per-client rate limit counters (key: agentSub:toolName) */
|
|
28
|
+
private clientRateCounters;
|
|
29
|
+
constructor(command: string, args: string[], env: Record<string, string>, db: HistoryDatabase, serverName?: string, policyEngine?: PolicyEngine, authValidator?: OAuthValidator);
|
|
16
30
|
get stdin(): NodeJS.WritableStream | null;
|
|
17
31
|
private setupStdout;
|
|
18
32
|
private setupStderr;
|
|
33
|
+
private sendError;
|
|
19
34
|
/**
|
|
20
35
|
* Called when the AI client writes a request to be proxied.
|
|
21
|
-
*
|
|
36
|
+
* Pipeline: Auth → Circuit Breaker → Policy + RBAC → Forward.
|
|
22
37
|
*/
|
|
23
|
-
handleClientInput(raw: string): void
|
|
38
|
+
handleClientInput(raw: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Check per-client rate limits against RBAC rules.
|
|
41
|
+
* Returns block reason string or null.
|
|
42
|
+
*/
|
|
43
|
+
private checkPerClientRateLimit;
|
|
24
44
|
kill(): void;
|
|
25
45
|
}
|
|
26
46
|
//# sourceMappingURL=proxy-server.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAKlD;;;;;;;;GAQG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,EAAE,CAAkB;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,aAAa,CAAwB;IAC7C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,cAAc,CAAiB;IACvC,sEAAsE;IACtE,OAAO,CAAC,kBAAkB,CAA8D;gBAGtF,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,EAAE,EAAE,eAAe,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,YAAY,EAC3B,aAAa,CAAC,EAAE,cAAc;IAyBhC,IAAI,KAAK,IAAI,MAAM,CAAC,cAAc,GAAG,IAAI,CAExC;IAED,OAAO,CAAC,WAAW;IAoCnB,OAAO,CAAC,WAAW;IAMnB,OAAO,CAAC,SAAS;IASjB;;;OAGG;IACG,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4MnD;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAY/B,IAAI,IAAI,IAAI;CAOb"}
|