@intutic/mcp-governance-proxy 1.0.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.
Files changed (87) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/.turbo/turbo-test.log +51 -0
  3. package/.turbo/turbo-typecheck.log +1 -0
  4. package/LICENSE +21 -0
  5. package/README.md +87 -0
  6. package/dist/config.d.ts +40 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +100 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/daemon/healthMonitor.d.ts +18 -0
  11. package/dist/daemon/healthMonitor.d.ts.map +1 -0
  12. package/dist/daemon/healthMonitor.js +185 -0
  13. package/dist/daemon/healthMonitor.js.map +1 -0
  14. package/dist/daemon/index.d.ts +2 -0
  15. package/dist/daemon/index.d.ts.map +1 -0
  16. package/dist/daemon/index.js +77 -0
  17. package/dist/daemon/index.js.map +1 -0
  18. package/dist/daemon/policyCache.d.ts +20 -0
  19. package/dist/daemon/policyCache.d.ts.map +1 -0
  20. package/dist/daemon/policyCache.js +124 -0
  21. package/dist/daemon/policyCache.js.map +1 -0
  22. package/dist/daemon/socketServer.d.ts +13 -0
  23. package/dist/daemon/socketServer.d.ts.map +1 -0
  24. package/dist/daemon/socketServer.js +90 -0
  25. package/dist/daemon/socketServer.js.map +1 -0
  26. package/dist/daemon/telemetryBatcher.d.ts +15 -0
  27. package/dist/daemon/telemetryBatcher.d.ts.map +1 -0
  28. package/dist/daemon/telemetryBatcher.js +143 -0
  29. package/dist/daemon/telemetryBatcher.js.map +1 -0
  30. package/dist/daemonClient.d.ts +5 -0
  31. package/dist/daemonClient.d.ts.map +1 -0
  32. package/dist/daemonClient.js +67 -0
  33. package/dist/daemonClient.js.map +1 -0
  34. package/dist/dlp.d.ts +29 -0
  35. package/dist/dlp.d.ts.map +1 -0
  36. package/dist/dlp.js +58 -0
  37. package/dist/dlp.js.map +1 -0
  38. package/dist/emitter.d.ts +34 -0
  39. package/dist/emitter.d.ts.map +1 -0
  40. package/dist/emitter.js +121 -0
  41. package/dist/emitter.js.map +1 -0
  42. package/dist/index.d.ts +24 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +50 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/interceptor.d.ts +38 -0
  47. package/dist/interceptor.d.ts.map +1 -0
  48. package/dist/interceptor.js +93 -0
  49. package/dist/interceptor.js.map +1 -0
  50. package/dist/policy.d.ts +41 -0
  51. package/dist/policy.d.ts.map +1 -0
  52. package/dist/policy.js +130 -0
  53. package/dist/policy.js.map +1 -0
  54. package/dist/proxy.d.ts +43 -0
  55. package/dist/proxy.d.ts.map +1 -0
  56. package/dist/proxy.js +238 -0
  57. package/dist/proxy.js.map +1 -0
  58. package/dist/stderrLog.d.ts +32 -0
  59. package/dist/stderrLog.d.ts.map +1 -0
  60. package/dist/stderrLog.js +45 -0
  61. package/dist/stderrLog.js.map +1 -0
  62. package/dummy-path.jsonl +2 -0
  63. package/package.json +32 -0
  64. package/src/__tests__/config.test.ts +82 -0
  65. package/src/__tests__/dlp.test.ts +103 -0
  66. package/src/__tests__/integration/daemon-shim.test.ts +125 -0
  67. package/src/__tests__/interceptor.test.ts +229 -0
  68. package/src/__tests__/unit/policyCache.test.ts +95 -0
  69. package/src/__tests__/unit/socketServer.test.ts +220 -0
  70. package/src/__tests__/unit/telemetryBatcher.test.ts +78 -0
  71. package/src/config.ts +145 -0
  72. package/src/daemon/healthMonitor.ts +210 -0
  73. package/src/daemon/index.ts +76 -0
  74. package/src/daemon/policyCache.ts +143 -0
  75. package/src/daemon/socketServer.ts +111 -0
  76. package/src/daemon/telemetryBatcher.ts +147 -0
  77. package/src/daemonClient.ts +71 -0
  78. package/src/dlp.ts +72 -0
  79. package/src/emitter.ts +144 -0
  80. package/src/index.ts +53 -0
  81. package/src/interceptor.ts +103 -0
  82. package/src/policy.ts +152 -0
  83. package/src/proxy.ts +327 -0
  84. package/src/stderrLog.ts +56 -0
  85. package/tsconfig.json +10 -0
  86. package/tsconfig.tsbuildinfo +1 -0
  87. package/vitest.config.ts +9 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * emitter.ts — Dual-path governance event emitter.
3
+ *
4
+ * Path A: HTTP POST to /api/v1/hook-events (same endpoint as claudeCodeHooks.ts)
5
+ * Path B: Append JSONL line to ~/.intutic/events/hook-events.jsonl
6
+ *
7
+ * Mirrors the dual-path pattern from claudeCodeHooks.ts / syncLoop.ts.
8
+ *
9
+ * @module
10
+ */
11
+ export type EventKind = 'tool_allowed' | 'tool_blocked' | 'tool_redacted';
12
+ export interface GovernanceEvent {
13
+ incidentId: string;
14
+ kind: EventKind;
15
+ toolName: string;
16
+ toolInput: unknown;
17
+ workspaceId: string;
18
+ harnessType: string;
19
+ reason?: string;
20
+ timestamp: string;
21
+ }
22
+ export declare class GovernanceEmitter {
23
+ private readonly controlPlaneUrl;
24
+ private readonly apiKey;
25
+ private readonly eventsFilePath;
26
+ private readonly workspaceId;
27
+ private readonly mcpProxyMode;
28
+ constructor(controlPlaneUrl: string, apiKey: string, eventsFilePath: string, workspaceId: string, mcpProxyMode?: string);
29
+ emit(kind: EventKind, toolName: string, toolInput: unknown, reason?: string): void;
30
+ private runDualPath;
31
+ private postToControlPlane;
32
+ private appendToFile;
33
+ }
34
+ //# sourceMappingURL=emitter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,cAAc,GAAG,eAAe,CAAA;AAEzE,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAJZ,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,YAAY,GAAE,MAAsB;IAGvD,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;IAkClF,OAAO,CAAC,WAAW;YAYL,kBAAkB;YAoBlB,YAAY;CAM3B"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * emitter.ts — Dual-path governance event emitter.
3
+ *
4
+ * Path A: HTTP POST to /api/v1/hook-events (same endpoint as claudeCodeHooks.ts)
5
+ * Path B: Append JSONL line to ~/.intutic/events/hook-events.jsonl
6
+ *
7
+ * Mirrors the dual-path pattern from claudeCodeHooks.ts / syncLoop.ts.
8
+ *
9
+ * @module
10
+ */
11
+ import * as node_fs from 'node:fs/promises';
12
+ import * as node_https from 'node:https';
13
+ import * as node_http from 'node:http';
14
+ import * as node_path from 'node:path';
15
+ import * as node_crypto from 'node:crypto';
16
+ import { createStderrLogger as createLogger } from './stderrLog.js';
17
+ import { callDaemonSocket } from './daemonClient.js';
18
+ const log = createLogger('mcp-proxy-emitter');
19
+ export class GovernanceEmitter {
20
+ controlPlaneUrl;
21
+ apiKey;
22
+ eventsFilePath;
23
+ workspaceId;
24
+ mcpProxyMode;
25
+ constructor(controlPlaneUrl, apiKey, eventsFilePath, workspaceId, mcpProxyMode = 'per-session') {
26
+ this.controlPlaneUrl = controlPlaneUrl;
27
+ this.apiKey = apiKey;
28
+ this.eventsFilePath = eventsFilePath;
29
+ this.workspaceId = workspaceId;
30
+ this.mcpProxyMode = mcpProxyMode;
31
+ }
32
+ emit(kind, toolName, toolInput, reason) {
33
+ const event = {
34
+ incidentId: node_crypto.randomUUID(),
35
+ kind,
36
+ toolName,
37
+ toolInput,
38
+ workspaceId: this.workspaceId,
39
+ harnessType: 'mcp-governance-proxy',
40
+ reason,
41
+ timestamp: new Date().toISOString(),
42
+ };
43
+ if (this.mcpProxyMode === 'daemon') {
44
+ const eventPayload = {
45
+ event: kind === 'tool_blocked' ? 'tool_blocked' : 'tool_allowed',
46
+ toolName,
47
+ workspaceId: this.workspaceId,
48
+ harnessType: 'mcp-governance-proxy',
49
+ timestamp: event.timestamp,
50
+ reason,
51
+ toolInput,
52
+ };
53
+ callDaemonSocket('telemetry.enqueue', eventPayload).then(() => {
54
+ log.debug({ action: 'telemetry_enqueued' }, 'Telemetry successfully enqueued to daemon');
55
+ }).catch((err) => {
56
+ log.warn({ action: 'telemetry_daemon_failed', err: err.message }, 'Failed to enqueue telemetry to daemon socket — falling back to dual-path');
57
+ this.runDualPath(event);
58
+ });
59
+ return;
60
+ }
61
+ this.runDualPath(event);
62
+ }
63
+ runDualPath(event) {
64
+ // Path A: HTTP POST (best effort)
65
+ this.postToControlPlane(event).catch((err) => {
66
+ log.warn({ action: 'emit_path_a_failed', err: err.message }, 'Path A emission failed');
67
+ });
68
+ // Path B: JSONL file append (best effort)
69
+ this.appendToFile(event).catch((err) => {
70
+ log.warn({ action: 'emit_path_b_failed', err: err.message }, 'Path B emission failed');
71
+ });
72
+ }
73
+ async postToControlPlane(event) {
74
+ const payload = JSON.stringify({
75
+ events: [
76
+ {
77
+ event: event.kind === 'tool_blocked' ? 'tool_blocked' : 'tool_allowed',
78
+ toolName: event.toolName,
79
+ toolInput: event.toolInput,
80
+ workspaceId: event.workspaceId,
81
+ harnessType: event.harnessType,
82
+ incidentId: event.incidentId,
83
+ reason: event.reason,
84
+ timestamp: event.timestamp,
85
+ },
86
+ ],
87
+ });
88
+ const url = `${this.controlPlaneUrl}/api/v1/telemetry/enqueue`;
89
+ await httpPost(url, this.apiKey, payload);
90
+ }
91
+ async appendToFile(event) {
92
+ const dir = node_path.dirname(this.eventsFilePath);
93
+ await node_fs.mkdir(dir, { recursive: true });
94
+ const line = JSON.stringify(event) + '\n';
95
+ await node_fs.appendFile(this.eventsFilePath, line, 'utf-8');
96
+ }
97
+ }
98
+ function httpPost(url, apiKey, body) {
99
+ return new Promise((resolve, reject) => {
100
+ const parsed = new URL(url);
101
+ const lib = parsed.protocol === 'https:' ? node_https : node_http;
102
+ const req = lib.request(url, {
103
+ method: 'POST',
104
+ headers: {
105
+ 'Content-Type': 'application/json',
106
+ 'Content-Length': Buffer.byteLength(body),
107
+ Authorization: `Bearer ${apiKey}`,
108
+ },
109
+ timeout: 4000,
110
+ }, (res) => {
111
+ // Drain response body to free socket
112
+ res.resume();
113
+ res.on('end', resolve);
114
+ });
115
+ req.on('error', reject);
116
+ req.on('timeout', () => { req.destroy(); reject(new Error('HTTP POST timed out')); });
117
+ req.write(body);
118
+ req.end();
119
+ });
120
+ }
121
+ //# sourceMappingURL=emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emitter.js","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * index.ts — CLI entry point for @intutic/mcp-governance-proxy.
4
+ *
5
+ * CRITICAL: In the MCP proxy, stdout is exclusively reserved for JSON-RPC frames.
6
+ * All logging MUST go to stderr. We redirect pino's output to fd 2 (stderr)
7
+ * by overriding the destination before any logger is instantiated.
8
+ *
9
+ * Usage:
10
+ * intutic-mcp-proxy [--workspace-id <id>] -- <real-mcp-server> [args...]
11
+ *
12
+ * Example (harness config):
13
+ * {
14
+ * "command": "node",
15
+ * "args": ["/path/to/@intutic/mcp-governance-proxy/dist/index.js",
16
+ * "--workspace-id", "ws_xxx",
17
+ * "--",
18
+ * "npx", "-y", "@modelcontextprotocol/server-filesystem", "/projects"]
19
+ * }
20
+ *
21
+ * @module
22
+ */
23
+ export {};
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG"}
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * index.ts — CLI entry point for @intutic/mcp-governance-proxy.
4
+ *
5
+ * CRITICAL: In the MCP proxy, stdout is exclusively reserved for JSON-RPC frames.
6
+ * All logging MUST go to stderr. We redirect pino's output to fd 2 (stderr)
7
+ * by overriding the destination before any logger is instantiated.
8
+ *
9
+ * Usage:
10
+ * intutic-mcp-proxy [--workspace-id <id>] -- <real-mcp-server> [args...]
11
+ *
12
+ * Example (harness config):
13
+ * {
14
+ * "command": "node",
15
+ * "args": ["/path/to/@intutic/mcp-governance-proxy/dist/index.js",
16
+ * "--workspace-id", "ws_xxx",
17
+ * "--",
18
+ * "npx", "-y", "@modelcontextprotocol/server-filesystem", "/projects"]
19
+ * }
20
+ *
21
+ * @module
22
+ */
23
+ import { loadConfig } from './config.js';
24
+ import { McpGovernanceProxy } from './proxy.js';
25
+ import { createStderrLogger as createLogger } from './stderrLog.js';
26
+ const log = createLogger('mcp-proxy-main');
27
+ async function main() {
28
+ let config;
29
+ try {
30
+ config = await loadConfig();
31
+ }
32
+ catch (err) {
33
+ // Write to stderr — never stdout
34
+ process.stderr.write(`[intutic-mcp-proxy] Configuration error: ${err.message}\n`);
35
+ process.exit(1);
36
+ }
37
+ const proxy = new McpGovernanceProxy(config);
38
+ try {
39
+ await proxy.run();
40
+ }
41
+ catch (err) {
42
+ log.error({ action: 'proxy_fatal', err: err.message }, 'Proxy exited with error');
43
+ process.exit(1);
44
+ }
45
+ }
46
+ main().catch((err) => {
47
+ process.stderr.write(`[intutic-mcp-proxy] Fatal error: ${String(err)}\n`);
48
+ process.exit(1);
49
+ });
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,OAAO,EAAE,kBAAkB,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEnE,MAAM,GAAG,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAA;AAE1C,KAAK,UAAU,IAAI;IACjB,IAAI,MAAM,CAAA;IACV,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,UAAU,EAAE,CAAA;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iCAAiC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4CAA6C,GAAa,CAAC,OAAO,IAAI,CAAC,CAAA;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;IAE5C,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,GAAG,EAAE,CAAA;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,GAAG,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,yBAAyB,CAAC,CAAA;QAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oCAAoC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * interceptor.ts — tools/call decision engine.
3
+ *
4
+ * Given a tool name and arguments, evaluates:
5
+ * 1. DLP scan (credential / destructive pattern detection)
6
+ * 2. SOP policy rules (fetched from control plane via PolicyClient)
7
+ *
8
+ * Returns an allow / block / redact decision.
9
+ *
10
+ * @module
11
+ */
12
+ import type { PolicyClient } from './policy.js';
13
+ import type { GovernanceEmitter } from './emitter.js';
14
+ export type Decision = {
15
+ action: 'allow';
16
+ } | {
17
+ action: 'block';
18
+ reason: string;
19
+ } | {
20
+ action: 'redact';
21
+ reason: string;
22
+ redactedInput: unknown;
23
+ };
24
+ export declare class ToolCallInterceptor {
25
+ private readonly policy;
26
+ private readonly emitter;
27
+ private readonly failOpen;
28
+ constructor(policy: PolicyClient, emitter: GovernanceEmitter, failOpen?: boolean);
29
+ /**
30
+ * Evaluate a tools/call request and return a governance decision.
31
+ *
32
+ * @param toolName - The MCP tool name (e.g. "mcp__filesystem__read_file" or "Bash")
33
+ * @param toolInput - The tool_input / arguments object
34
+ * @returns Decision: allow, block, or redact
35
+ */
36
+ decide(toolName: string, toolInput: unknown): Promise<Decision>;
37
+ }
38
+ //# sourceMappingURL=interceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAIrD,MAAM,MAAM,QAAQ,GAChB;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GACnB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAAA;AAEhE,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAFR,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,EAC1B,QAAQ,GAAE,OAAc;IAG3C;;;;;;OAMG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;CAgEtE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * interceptor.ts — tools/call decision engine.
3
+ *
4
+ * Given a tool name and arguments, evaluates:
5
+ * 1. DLP scan (credential / destructive pattern detection)
6
+ * 2. SOP policy rules (fetched from control plane via PolicyClient)
7
+ *
8
+ * Returns an allow / block / redact decision.
9
+ *
10
+ * @module
11
+ */
12
+ import { createStderrLogger as createLogger } from './stderrLog.js';
13
+ import { scanToolInput, formatDlpBlockReason } from './dlp.js';
14
+ const log = createLogger('mcp-proxy-interceptor');
15
+ export class ToolCallInterceptor {
16
+ policy;
17
+ emitter;
18
+ failOpen;
19
+ constructor(policy, emitter, failOpen = true) {
20
+ this.policy = policy;
21
+ this.emitter = emitter;
22
+ this.failOpen = failOpen;
23
+ }
24
+ /**
25
+ * Evaluate a tools/call request and return a governance decision.
26
+ *
27
+ * @param toolName - The MCP tool name (e.g. "mcp__filesystem__read_file" or "Bash")
28
+ * @param toolInput - The tool_input / arguments object
29
+ * @returns Decision: allow, block, or redact
30
+ */
31
+ async decide(toolName, toolInput) {
32
+ log.debug({ action: 'interceptor_decide', toolName }, 'Evaluating tool call');
33
+ // 1. DLP scan
34
+ try {
35
+ const dlp = scanToolInput(toolInput);
36
+ if (dlp.hasFinding) {
37
+ const reason = formatDlpBlockReason(dlp.findings);
38
+ log.warn({ action: 'dlp_block', toolName, findings: dlp.findings }, 'DLP block');
39
+ this.emitter.emit('tool_blocked', toolName, toolInput, reason);
40
+ return { action: 'block', reason };
41
+ }
42
+ }
43
+ catch (err) {
44
+ log.error({ action: 'dlp_error', err: err.message }, 'DLP scan error — skipping');
45
+ if (!this.failOpen) {
46
+ return {
47
+ action: 'block',
48
+ reason: 'Governance check failed — Intutic control plane unreachable. ' +
49
+ 'Tool call blocked by workspace policy (fail-closed mode). ' +
50
+ 'Contact your administrator or update mcpProxyFailBehavior to open.',
51
+ };
52
+ }
53
+ }
54
+ // 2. SOP policy rule match
55
+ try {
56
+ const toolInputJson = JSON.stringify(toolInput ?? {});
57
+ const rule = this.policy.matchRule(toolName, toolInputJson);
58
+ if (rule) {
59
+ if (rule.action === 'block') {
60
+ log.warn({ action: 'policy_block', toolName, ruleId: rule.id, reason: rule.reason }, 'Policy block');
61
+ this.emitter.emit('tool_blocked', toolName, toolInput, rule.reason);
62
+ return { action: 'block', reason: rule.reason };
63
+ }
64
+ if (rule.action === 'warn') {
65
+ log.warn({ action: 'policy_warn', toolName, ruleId: rule.id, reason: rule.reason }, 'Policy warning (allowing)');
66
+ // Fall through to allow — warnings are logged only
67
+ }
68
+ // 'require_approval' treated as block in headless proxy (no interactive UI)
69
+ if (rule.action === 'require_approval') {
70
+ const reason = `Tool requires human approval per SOP rule ${rule.id}: ${rule.reason}`;
71
+ log.warn({ action: 'policy_approval_required', toolName, ruleId: rule.id }, reason);
72
+ this.emitter.emit('tool_blocked', toolName, toolInput, reason);
73
+ return { action: 'block', reason };
74
+ }
75
+ }
76
+ }
77
+ catch (err) {
78
+ log.error({ action: 'policy_error', err: err.message }, 'Policy evaluation error');
79
+ if (!this.failOpen) {
80
+ return {
81
+ action: 'block',
82
+ reason: 'Governance check failed — Intutic control plane unreachable. ' +
83
+ 'Tool call blocked by workspace policy (fail-closed mode). ' +
84
+ 'Contact your administrator or update mcpProxyFailBehavior to open.',
85
+ };
86
+ }
87
+ }
88
+ // 3. Allow — emit telemetry event
89
+ this.emitter.emit('tool_allowed', toolName, toolInput);
90
+ return { action: 'allow' };
91
+ }
92
+ }
93
+ //# sourceMappingURL=interceptor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interceptor.js","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * policy.ts — PolicyClient: fetches and caches SOP governance rules from the Intutic control plane.
3
+ *
4
+ * Rules are refreshed every policyTtlMs (default: 60s). On fetch failure, the
5
+ * last-known-good set is retained (fail-open behaviour).
6
+ *
7
+ * @module
8
+ */
9
+ export interface SopRule {
10
+ id: string;
11
+ /** Regex pattern matched against tool name (e.g. "mcp__github__.*" or "Bash") */
12
+ toolPattern: string;
13
+ /** Optional regex matched against JSON.stringify(tool_input) */
14
+ argPattern?: string;
15
+ /** Action to take when the rule matches */
16
+ action: 'block' | 'warn' | 'require_approval';
17
+ /** Human-readable reason reported back to harness */
18
+ reason: string;
19
+ }
20
+ export declare class PolicyClient {
21
+ private readonly controlPlaneUrl;
22
+ private readonly apiKey;
23
+ private readonly workspaceId;
24
+ private readonly ttlMs;
25
+ private readonly mcpProxyMode;
26
+ private rules;
27
+ private lastFetchAt;
28
+ private refreshTimer;
29
+ constructor(controlPlaneUrl: string, apiKey: string, workspaceId: string, ttlMs?: number, mcpProxyMode?: string);
30
+ /** Start background refresh timer. */
31
+ start(): void;
32
+ /** Stop the background refresh timer. */
33
+ stop(): void;
34
+ /** Return the current cached rule set. */
35
+ getRules(): readonly SopRule[];
36
+ /** Find the first matching rule for a given tool name + serialized args. */
37
+ matchRule(toolName: string, toolInputJson: string): SopRule | null;
38
+ /** Fetch fresh rules from the control plane. */
39
+ refresh(): Promise<void>;
40
+ }
41
+ //# sourceMappingURL=policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAA;IACnB,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,2CAA2C;IAC3C,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,kBAAkB,CAAA;IAC7C,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAA;CACf;AAMD,qBAAa,YAAY;IAMrB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAT/B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,YAAY,CAA8B;gBAG/B,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,KAAK,GAAE,MAAe,EACtB,YAAY,GAAE,MAAsB;IAGvD,sCAAsC;IACtC,KAAK,IAAI,IAAI;IAab,yCAAyC;IACzC,IAAI,IAAI,IAAI;IAOZ,0CAA0C;IAC1C,QAAQ,IAAI,SAAS,OAAO,EAAE;IAI9B,4EAA4E;IAC5E,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAiBlE,gDAAgD;IAC1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAyB/B"}
package/dist/policy.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * policy.ts — PolicyClient: fetches and caches SOP governance rules from the Intutic control plane.
3
+ *
4
+ * Rules are refreshed every policyTtlMs (default: 60s). On fetch failure, the
5
+ * last-known-good set is retained (fail-open behaviour).
6
+ *
7
+ * @module
8
+ */
9
+ import * as node_https from 'node:https';
10
+ import * as node_http from 'node:http';
11
+ import { createStderrLogger as createLogger } from './stderrLog.js';
12
+ import { callDaemonSocket } from './daemonClient.js';
13
+ const log = createLogger('mcp-proxy-policy');
14
+ export class PolicyClient {
15
+ controlPlaneUrl;
16
+ apiKey;
17
+ workspaceId;
18
+ ttlMs;
19
+ mcpProxyMode;
20
+ rules = [];
21
+ lastFetchAt = 0;
22
+ refreshTimer = null;
23
+ constructor(controlPlaneUrl, apiKey, workspaceId, ttlMs = 60_000, mcpProxyMode = 'per-session') {
24
+ this.controlPlaneUrl = controlPlaneUrl;
25
+ this.apiKey = apiKey;
26
+ this.workspaceId = workspaceId;
27
+ this.ttlMs = ttlMs;
28
+ this.mcpProxyMode = mcpProxyMode;
29
+ }
30
+ /** Start background refresh timer. */
31
+ start() {
32
+ this.refreshTimer = setInterval(() => {
33
+ void this.refresh().catch(() => {
34
+ // Errors already logged inside refresh()
35
+ });
36
+ }, this.ttlMs);
37
+ // Don't block Node.js exit on this timer
38
+ this.refreshTimer.unref();
39
+ // Kick off initial fetch (non-blocking — proxy starts immediately)
40
+ void this.refresh().catch(() => { });
41
+ }
42
+ /** Stop the background refresh timer. */
43
+ stop() {
44
+ if (this.refreshTimer) {
45
+ clearInterval(this.refreshTimer);
46
+ this.refreshTimer = null;
47
+ }
48
+ }
49
+ /** Return the current cached rule set. */
50
+ getRules() {
51
+ return this.rules;
52
+ }
53
+ /** Find the first matching rule for a given tool name + serialized args. */
54
+ matchRule(toolName, toolInputJson) {
55
+ for (const rule of this.rules) {
56
+ try {
57
+ const toolRegex = new RegExp(rule.toolPattern);
58
+ if (!toolRegex.test(toolName))
59
+ continue;
60
+ if (rule.argPattern) {
61
+ const argRegex = new RegExp(rule.argPattern);
62
+ if (!argRegex.test(toolInputJson))
63
+ continue;
64
+ }
65
+ return rule;
66
+ }
67
+ catch {
68
+ // Malformed regex in rule — skip silently
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /** Fetch fresh rules from the control plane. */
74
+ async refresh() {
75
+ if (this.mcpProxyMode === 'daemon') {
76
+ try {
77
+ const policy = await callDaemonSocket('policy.get', { workspaceId: this.workspaceId });
78
+ if (policy) {
79
+ this.rules = (policy.sopRules || []);
80
+ this.lastFetchAt = Date.now();
81
+ log.info({ action: 'policy_refreshed_from_daemon', ruleCount: this.rules.length }, 'SOP rules refreshed from daemon');
82
+ return;
83
+ }
84
+ }
85
+ catch (err) {
86
+ log.warn({ action: 'policy_daemon_failed', err: err.message }, 'Failed to refresh policy from daemon socket — falling back to HTTP');
87
+ }
88
+ }
89
+ const url = `${this.controlPlaneUrl}/api/v1/sop/rules?workspaceId=${encodeURIComponent(this.workspaceId)}&active=true`;
90
+ log.debug({ action: 'policy_refresh', url }, 'Fetching SOP rules from control plane');
91
+ const body = await httpGet(url, this.apiKey);
92
+ const parsed = JSON.parse(body);
93
+ const rules = Array.isArray(parsed.rules) ? parsed.rules : [];
94
+ this.rules = rules;
95
+ this.lastFetchAt = Date.now();
96
+ log.info({ action: 'policy_refreshed', ruleCount: rules.length }, 'SOP rules refreshed');
97
+ }
98
+ }
99
+ /** Minimal HTTP/HTTPS GET helper (avoids fetch / node-fetch dep). */
100
+ function httpGet(url, apiKey) {
101
+ return new Promise((resolve, reject) => {
102
+ const parsed = new URL(url);
103
+ const lib = parsed.protocol === 'https:' ? node_https : node_http;
104
+ const req = lib.get(url, {
105
+ headers: {
106
+ Authorization: `Bearer ${apiKey}`,
107
+ Accept: 'application/json',
108
+ },
109
+ timeout: 5000,
110
+ }, (res) => {
111
+ const chunks = [];
112
+ res.on('data', (c) => chunks.push(c));
113
+ res.on('end', () => {
114
+ const body = Buffer.concat(chunks).toString('utf-8');
115
+ if ((res.statusCode ?? 0) >= 400) {
116
+ reject(new Error(`HTTP ${res.statusCode ?? 'unknown'}: ${body}`));
117
+ }
118
+ else {
119
+ resolve(body);
120
+ }
121
+ });
122
+ });
123
+ req.on('error', reject);
124
+ req.on('timeout', () => {
125
+ req.destroy();
126
+ reject(new Error('Policy fetch timed out'));
127
+ });
128
+ });
129
+ }
130
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * proxy.ts — McpGovernanceProxy: transparent stdio MCP proxy with governance interception.
3
+ *
4
+ * Architecture (proxy mode):
5
+ * Harness stdin → [McpGovernanceProxy] → real MCP server stdin
6
+ * Real MCP server stdout → [McpGovernanceProxy] → Harness stdout
7
+ *
8
+ * Architecture (standalone mode — the `intutic` harness entry):
9
+ * Harness ↔ [McpGovernanceProxy as MCP Server] ↔ Control Plane REST API
10
+ * Exposes governance tools: intutic_governance_status, intutic_list_sops, intutic_list_incidents.
11
+ *
12
+ * CRITICAL: Never write to process.stdout except for valid JSON-RPC frames.
13
+ * All logging MUST go to process.stderr via @intutic/logger.
14
+ *
15
+ * JSON-RPC framing: newline-delimited JSON (one JSON object per line).
16
+ *
17
+ * @module
18
+ */
19
+ import type { ProxyConfig } from './config.js';
20
+ export declare class McpGovernanceProxy {
21
+ private readonly config;
22
+ private readonly policy;
23
+ private readonly emitter;
24
+ private readonly interceptor;
25
+ private realServer;
26
+ constructor(config: ProxyConfig);
27
+ /**
28
+ * Start the proxy. Dispatches to standalone or proxy mode based on config.
29
+ */
30
+ run(): Promise<void>;
31
+ /**
32
+ * Run as a standalone MCP server (the `intutic` harness entry).
33
+ * Exposes Intutic governance tools directly to the harness.
34
+ * Gracefully degrades when the control plane is unreachable.
35
+ */
36
+ private runStandalone;
37
+ /**
38
+ * Start the proxy. Spawns the real MCP server and begins proxying stdin/stdout.
39
+ * Returns a promise that resolves when the real server exits.
40
+ */
41
+ private runProxy;
42
+ }
43
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAkD9C,qBAAa,kBAAkB;IAMjB,OAAO,CAAC,QAAQ,CAAC,MAAM;IALnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,UAAU,CAAuC;gBAE5B,MAAM,EAAE,WAAW;IAoBhD;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAS1B;;;;OAIG;YACW,aAAa;IA0G3B;;;OAGG;YACW,QAAQ;CAqGvB"}