sovr-mcp-proxy 6.0.1 → 7.0.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/LICENSE +54 -19
- package/README.md +387 -164
- package/dist/cli.js +1553 -24
- package/dist/cli.mjs +185 -18
- package/dist/commandNormalizer.d.mts +95 -0
- package/dist/commandNormalizer.d.ts +95 -0
- package/dist/commandNormalizer.js +365 -0
- package/dist/commandNormalizer.mjs +336 -0
- package/dist/hooksAdapter.d.mts +122 -0
- package/dist/hooksAdapter.d.ts +122 -0
- package/dist/hooksAdapter.js +321 -0
- package/dist/hooksAdapter.mjs +291 -0
- package/dist/index.js +1065 -2
- package/dist/index.mjs +98 -2
- package/dist/toolReplacement.d.mts +108 -0
- package/dist/toolReplacement.d.ts +108 -0
- package/dist/toolReplacement.js +234 -0
- package/dist/toolReplacement.mjs +204 -0
- package/dist/whitelistEngine.d.mts +167 -0
- package/dist/whitelistEngine.d.ts +167 -0
- package/dist/whitelistEngine.js +435 -0
- package/dist/whitelistEngine.mjs +403 -0
- package/package.json +46 -41
- package/server.json +0 -14
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
7
|
import { EventEmitter } from "events";
|
|
8
|
-
var PROXY_VERSION = "
|
|
8
|
+
var PROXY_VERSION = "7.0.0";
|
|
9
9
|
var SOVR_MIN_VERSION_URL = "https://api.sovr.inc/api/sovr/v1/version/check";
|
|
10
10
|
var SOVR_DASHBOARD_URL = "https://sovr.inc/dashboard/api-keys";
|
|
11
11
|
function validateApiKey(key) {
|
|
@@ -823,11 +823,15 @@ var McpProxy = class extends EventEmitter {
|
|
|
823
823
|
};
|
|
824
824
|
async function cli(args) {
|
|
825
825
|
const { PolicyEngine, DEFAULT_RULES } = await import("./engine-DWKHAJGE.mjs");
|
|
826
|
+
const { transformToolList, shouldIntercept, parseMode } = await import("./toolReplacement.mjs");
|
|
827
|
+
const { normalize: normalizeCommand, summarize: summarizeNormalization } = await import("./commandNormalizer.mjs");
|
|
826
828
|
let upstreamCmd = "";
|
|
827
829
|
let upstreamArgs = [];
|
|
828
830
|
let rulesFile = null;
|
|
829
831
|
let verbose = false;
|
|
830
832
|
let startupTimeoutMs = 3e4;
|
|
833
|
+
let mode = "enforce";
|
|
834
|
+
let whitelistPreset = null;
|
|
831
835
|
for (let i = 0; i < args.length; i++) {
|
|
832
836
|
switch (args[i]) {
|
|
833
837
|
case "--upstream":
|
|
@@ -849,14 +853,39 @@ async function cli(args) {
|
|
|
849
853
|
case "-t":
|
|
850
854
|
startupTimeoutMs = parseInt(args[++i] ?? "30000", 10);
|
|
851
855
|
break;
|
|
856
|
+
case "--mode":
|
|
857
|
+
case "-m":
|
|
858
|
+
mode = args[++i] ?? "enforce";
|
|
859
|
+
break;
|
|
860
|
+
case "--whitelist":
|
|
861
|
+
case "-w":
|
|
862
|
+
whitelistPreset = args[++i] ?? null;
|
|
863
|
+
break;
|
|
864
|
+
default: {
|
|
865
|
+
const modeMatch = args[i]?.match(/^--mode=(.+)$/);
|
|
866
|
+
if (modeMatch) {
|
|
867
|
+
mode = modeMatch[1];
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
const wlMatch = args[i]?.match(/^--whitelist=(.+)$/);
|
|
871
|
+
if (wlMatch) {
|
|
872
|
+
whitelistPreset = wlMatch[1];
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
852
876
|
}
|
|
853
877
|
}
|
|
854
878
|
if (!upstreamCmd) {
|
|
855
879
|
process.stderr.write(
|
|
856
|
-
'Usage: sovr-mcp-proxy --upstream "command args..." [--
|
|
880
|
+
'Usage: sovr-mcp-proxy --upstream "command args..." [--mode=exclusive|enforce|advisory|monitor] [--whitelist=preset|path] [--verbose]\n'
|
|
857
881
|
);
|
|
858
882
|
process.exit(1);
|
|
859
883
|
}
|
|
884
|
+
if (!["exclusive", "enforce", "advisory", "monitor"].includes(mode)) {
|
|
885
|
+
process.stderr.write(`[SOVR] Invalid mode: ${mode}. Must be: exclusive|enforce|advisory|monitor
|
|
886
|
+
`);
|
|
887
|
+
process.exit(1);
|
|
888
|
+
}
|
|
860
889
|
let rules = DEFAULT_RULES;
|
|
861
890
|
if (rulesFile) {
|
|
862
891
|
const fs = await import("fs");
|
|
@@ -864,6 +893,26 @@ async function cli(args) {
|
|
|
864
893
|
const parsed = JSON.parse(content);
|
|
865
894
|
rules = parsed.rules ?? parsed;
|
|
866
895
|
}
|
|
896
|
+
const { WhitelistEngine, PRESETS: WL_PRESETS } = await import("./whitelistEngine.mjs");
|
|
897
|
+
let whitelist = null;
|
|
898
|
+
if (whitelistPreset) {
|
|
899
|
+
const presetNames = Object.keys(WL_PRESETS);
|
|
900
|
+
if (presetNames.includes(whitelistPreset)) {
|
|
901
|
+
whitelist = new WhitelistEngine(WL_PRESETS[whitelistPreset]);
|
|
902
|
+
} else {
|
|
903
|
+
try {
|
|
904
|
+
const fs = await import("fs");
|
|
905
|
+
const content = fs.readFileSync(whitelistPreset, "utf-8");
|
|
906
|
+
const parsed = JSON.parse(content);
|
|
907
|
+
whitelist = new WhitelistEngine(parsed);
|
|
908
|
+
} catch (err) {
|
|
909
|
+
process.stderr.write(`[SOVR] Failed to load whitelist from ${whitelistPreset}: ${err.message}
|
|
910
|
+
`);
|
|
911
|
+
process.exit(1);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const validatedMode = parseMode(mode);
|
|
867
916
|
const engine = new PolicyEngine({
|
|
868
917
|
rules,
|
|
869
918
|
audit_log: true,
|
|
@@ -876,6 +925,24 @@ async function cli(args) {
|
|
|
876
925
|
}
|
|
877
926
|
}
|
|
878
927
|
});
|
|
928
|
+
process.stderr.write(`
|
|
929
|
+
`);
|
|
930
|
+
process.stderr.write(` \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
931
|
+
`);
|
|
932
|
+
process.stderr.write(` \u2551 SOVR MCP Proxy v${PROXY_VERSION} \u2551
|
|
933
|
+
`);
|
|
934
|
+
process.stderr.write(` \u2551 Mode: ${mode.toUpperCase().padEnd(40)}\u2551
|
|
935
|
+
`);
|
|
936
|
+
if (whitelist) {
|
|
937
|
+
process.stderr.write(` \u2551 Whitelist: ${(whitelistPreset ?? "custom").padEnd(35)}\u2551
|
|
938
|
+
`);
|
|
939
|
+
}
|
|
940
|
+
process.stderr.write(` \u2551 Upstream: ${upstreamCmd.substring(0, 36).padEnd(36)}\u2551
|
|
941
|
+
`);
|
|
942
|
+
process.stderr.write(` \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
943
|
+
`);
|
|
944
|
+
process.stderr.write(`
|
|
945
|
+
`);
|
|
879
946
|
const proxy = new McpProxy({
|
|
880
947
|
engine,
|
|
881
948
|
upstream: { command: upstreamCmd, args: upstreamArgs },
|
|
@@ -892,8 +959,37 @@ async function cli(args) {
|
|
|
892
959
|
`[ESCALATED] ${info.toolName}: ${info.decision.reason}
|
|
893
960
|
`
|
|
894
961
|
);
|
|
962
|
+
},
|
|
963
|
+
onIntercept: (info) => {
|
|
964
|
+
if (info.arguments?.command && typeof info.arguments.command === "string") {
|
|
965
|
+
const normalized = normalizeCommand(info.arguments.command);
|
|
966
|
+
if (verbose) {
|
|
967
|
+
process.stderr.write(`[SOVR] Normalized: ${summarizeNormalization(normalized)}
|
|
968
|
+
`);
|
|
969
|
+
if (normalized.suspicious) {
|
|
970
|
+
process.stderr.write(`[SOVR] \u26A0 Suspicious: ${normalized.suspicion_reasons.join(", ")}
|
|
971
|
+
`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
if (whitelist && info.arguments?.command && typeof info.arguments.command === "string") {
|
|
976
|
+
const wlResult = whitelist.evaluate(info.arguments.command);
|
|
977
|
+
if (!wlResult.allowed && (validatedMode === "enforce" || validatedMode === "exclusive")) {
|
|
978
|
+
if (verbose) {
|
|
979
|
+
process.stderr.write(`[SOVR] Whitelist DENIED: ${wlResult.reason}
|
|
980
|
+
`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
895
984
|
}
|
|
896
985
|
});
|
|
986
|
+
if (validatedMode === "exclusive") {
|
|
987
|
+
proxy.on("intercept", () => {
|
|
988
|
+
if (verbose) {
|
|
989
|
+
process.stderr.write("[SOVR] Exclusive mode: all tool calls routed through SOVR\n");
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
}
|
|
897
993
|
await proxy.start();
|
|
898
994
|
}
|
|
899
995
|
var index_default = McpProxy;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sovr/proxy-mcp — Tool Replacement Mode (--mode=exclusive)
|
|
3
|
+
*
|
|
4
|
+
* Solves the FATAL bypass problem identified in Reddit feedback:
|
|
5
|
+
* "The LLM isn't forced to route commands through your MCP server."
|
|
6
|
+
*
|
|
7
|
+
* Solution: Instead of adding SOVR as an *additional* MCP tool alongside
|
|
8
|
+
* the native Bash tool, SOVR *replaces* the native Bash tool entirely.
|
|
9
|
+
* The LLM has no choice — every command goes through SOVR.
|
|
10
|
+
*
|
|
11
|
+
* How it works:
|
|
12
|
+
* 1. SOVR proxy intercepts the MCP `tools/list` response from upstream
|
|
13
|
+
* 2. In exclusive mode, it REMOVES dangerous native tools (Bash, shell, etc.)
|
|
14
|
+
* 3. It INJECTS SOVR-wrapped equivalents that enforce policy checks
|
|
15
|
+
* 4. When the LLM calls the SOVR tool, the proxy evaluates the command,
|
|
16
|
+
* then either forwards to the real tool or blocks it
|
|
17
|
+
*
|
|
18
|
+
* Supported modes:
|
|
19
|
+
* - "monitor" (default) — Log all tool calls, allow everything
|
|
20
|
+
* - "advisory" — Log + warn on risky calls, but allow
|
|
21
|
+
* - "enforce" — Block risky calls, allow safe ones
|
|
22
|
+
* - "exclusive" — Replace native tools entirely, ALL calls go through SOVR
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```
|
|
26
|
+
* # Start proxy in exclusive mode (recommended for production)
|
|
27
|
+
* npx sovr-mcp-proxy --upstream "..." --mode=exclusive
|
|
28
|
+
*
|
|
29
|
+
* # Start proxy in enforce mode (less intrusive)
|
|
30
|
+
* npx sovr-mcp-proxy --upstream "..." --mode=enforce
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
type ProxyMode = 'monitor' | 'advisory' | 'enforce' | 'exclusive';
|
|
34
|
+
/** Tools that are considered dangerous and should be replaced in exclusive mode */
|
|
35
|
+
interface ToolReplacementConfig {
|
|
36
|
+
/** Proxy enforcement mode */
|
|
37
|
+
mode: ProxyMode;
|
|
38
|
+
/** Tool names to intercept/replace (glob patterns supported) */
|
|
39
|
+
targetTools: string[];
|
|
40
|
+
/** Custom tool descriptions for replaced tools */
|
|
41
|
+
toolDescriptions?: Record<string, string>;
|
|
42
|
+
/** Whether to add a SOVR status tool */
|
|
43
|
+
addStatusTool: boolean;
|
|
44
|
+
/** Whether to add a SOVR audit tool */
|
|
45
|
+
addAuditTool: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** MCP Tool definition (from MCP spec) */
|
|
48
|
+
interface McpToolDef {
|
|
49
|
+
name: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object';
|
|
53
|
+
properties?: Record<string, unknown>;
|
|
54
|
+
required?: string[];
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Result of tool list transformation */
|
|
59
|
+
interface ToolListTransformResult {
|
|
60
|
+
/** The transformed tool list */
|
|
61
|
+
tools: McpToolDef[];
|
|
62
|
+
/** Tools that were removed */
|
|
63
|
+
removedTools: string[];
|
|
64
|
+
/** Tools that were added by SOVR */
|
|
65
|
+
addedTools: string[];
|
|
66
|
+
/** Tools that were wrapped (kept but intercepted) */
|
|
67
|
+
wrappedTools: string[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Default dangerous tool patterns that should be replaced in exclusive mode.
|
|
71
|
+
* These are the native tools that AI coding agents typically have access to.
|
|
72
|
+
*/
|
|
73
|
+
declare const DEFAULT_TARGET_TOOLS: string[];
|
|
74
|
+
declare const DEFAULT_TOOL_REPLACEMENT_CONFIG: ToolReplacementConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Transform the upstream MCP server's tool list based on the proxy mode.
|
|
77
|
+
*
|
|
78
|
+
* In exclusive mode:
|
|
79
|
+
* - Removes ALL native execution tools (Bash, shell, etc.)
|
|
80
|
+
* - Injects SOVR-wrapped equivalents
|
|
81
|
+
* - The LLM literally cannot bypass SOVR because the native tools don't exist
|
|
82
|
+
*
|
|
83
|
+
* In enforce mode:
|
|
84
|
+
* - Keeps native tools but wraps them with SOVR interception
|
|
85
|
+
* - Adds SOVR tools alongside native ones
|
|
86
|
+
*
|
|
87
|
+
* In advisory/monitor mode:
|
|
88
|
+
* - Keeps all native tools unchanged
|
|
89
|
+
* - Adds SOVR tools for optional use
|
|
90
|
+
*/
|
|
91
|
+
declare function transformToolList(upstreamTools: McpToolDef[], config: ToolReplacementConfig): ToolListTransformResult;
|
|
92
|
+
/**
|
|
93
|
+
* Determine if a tool call should be intercepted based on the proxy mode.
|
|
94
|
+
*/
|
|
95
|
+
declare function shouldIntercept(toolName: string, config: ToolReplacementConfig): {
|
|
96
|
+
intercept: boolean;
|
|
97
|
+
reason: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Generate a human-readable summary of the tool replacement configuration.
|
|
101
|
+
*/
|
|
102
|
+
declare function describeMode(config: ToolReplacementConfig): string;
|
|
103
|
+
/**
|
|
104
|
+
* Parse mode from CLI argument string.
|
|
105
|
+
*/
|
|
106
|
+
declare function parseMode(input: string): ProxyMode;
|
|
107
|
+
|
|
108
|
+
export { DEFAULT_TARGET_TOOLS, DEFAULT_TOOL_REPLACEMENT_CONFIG, type McpToolDef, type ProxyMode, type ToolListTransformResult, type ToolReplacementConfig, describeMode, parseMode, shouldIntercept, transformToolList };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sovr/proxy-mcp — Tool Replacement Mode (--mode=exclusive)
|
|
3
|
+
*
|
|
4
|
+
* Solves the FATAL bypass problem identified in Reddit feedback:
|
|
5
|
+
* "The LLM isn't forced to route commands through your MCP server."
|
|
6
|
+
*
|
|
7
|
+
* Solution: Instead of adding SOVR as an *additional* MCP tool alongside
|
|
8
|
+
* the native Bash tool, SOVR *replaces* the native Bash tool entirely.
|
|
9
|
+
* The LLM has no choice — every command goes through SOVR.
|
|
10
|
+
*
|
|
11
|
+
* How it works:
|
|
12
|
+
* 1. SOVR proxy intercepts the MCP `tools/list` response from upstream
|
|
13
|
+
* 2. In exclusive mode, it REMOVES dangerous native tools (Bash, shell, etc.)
|
|
14
|
+
* 3. It INJECTS SOVR-wrapped equivalents that enforce policy checks
|
|
15
|
+
* 4. When the LLM calls the SOVR tool, the proxy evaluates the command,
|
|
16
|
+
* then either forwards to the real tool or blocks it
|
|
17
|
+
*
|
|
18
|
+
* Supported modes:
|
|
19
|
+
* - "monitor" (default) — Log all tool calls, allow everything
|
|
20
|
+
* - "advisory" — Log + warn on risky calls, but allow
|
|
21
|
+
* - "enforce" — Block risky calls, allow safe ones
|
|
22
|
+
* - "exclusive" — Replace native tools entirely, ALL calls go through SOVR
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```
|
|
26
|
+
* # Start proxy in exclusive mode (recommended for production)
|
|
27
|
+
* npx sovr-mcp-proxy --upstream "..." --mode=exclusive
|
|
28
|
+
*
|
|
29
|
+
* # Start proxy in enforce mode (less intrusive)
|
|
30
|
+
* npx sovr-mcp-proxy --upstream "..." --mode=enforce
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
type ProxyMode = 'monitor' | 'advisory' | 'enforce' | 'exclusive';
|
|
34
|
+
/** Tools that are considered dangerous and should be replaced in exclusive mode */
|
|
35
|
+
interface ToolReplacementConfig {
|
|
36
|
+
/** Proxy enforcement mode */
|
|
37
|
+
mode: ProxyMode;
|
|
38
|
+
/** Tool names to intercept/replace (glob patterns supported) */
|
|
39
|
+
targetTools: string[];
|
|
40
|
+
/** Custom tool descriptions for replaced tools */
|
|
41
|
+
toolDescriptions?: Record<string, string>;
|
|
42
|
+
/** Whether to add a SOVR status tool */
|
|
43
|
+
addStatusTool: boolean;
|
|
44
|
+
/** Whether to add a SOVR audit tool */
|
|
45
|
+
addAuditTool: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** MCP Tool definition (from MCP spec) */
|
|
48
|
+
interface McpToolDef {
|
|
49
|
+
name: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object';
|
|
53
|
+
properties?: Record<string, unknown>;
|
|
54
|
+
required?: string[];
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Result of tool list transformation */
|
|
59
|
+
interface ToolListTransformResult {
|
|
60
|
+
/** The transformed tool list */
|
|
61
|
+
tools: McpToolDef[];
|
|
62
|
+
/** Tools that were removed */
|
|
63
|
+
removedTools: string[];
|
|
64
|
+
/** Tools that were added by SOVR */
|
|
65
|
+
addedTools: string[];
|
|
66
|
+
/** Tools that were wrapped (kept but intercepted) */
|
|
67
|
+
wrappedTools: string[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Default dangerous tool patterns that should be replaced in exclusive mode.
|
|
71
|
+
* These are the native tools that AI coding agents typically have access to.
|
|
72
|
+
*/
|
|
73
|
+
declare const DEFAULT_TARGET_TOOLS: string[];
|
|
74
|
+
declare const DEFAULT_TOOL_REPLACEMENT_CONFIG: ToolReplacementConfig;
|
|
75
|
+
/**
|
|
76
|
+
* Transform the upstream MCP server's tool list based on the proxy mode.
|
|
77
|
+
*
|
|
78
|
+
* In exclusive mode:
|
|
79
|
+
* - Removes ALL native execution tools (Bash, shell, etc.)
|
|
80
|
+
* - Injects SOVR-wrapped equivalents
|
|
81
|
+
* - The LLM literally cannot bypass SOVR because the native tools don't exist
|
|
82
|
+
*
|
|
83
|
+
* In enforce mode:
|
|
84
|
+
* - Keeps native tools but wraps them with SOVR interception
|
|
85
|
+
* - Adds SOVR tools alongside native ones
|
|
86
|
+
*
|
|
87
|
+
* In advisory/monitor mode:
|
|
88
|
+
* - Keeps all native tools unchanged
|
|
89
|
+
* - Adds SOVR tools for optional use
|
|
90
|
+
*/
|
|
91
|
+
declare function transformToolList(upstreamTools: McpToolDef[], config: ToolReplacementConfig): ToolListTransformResult;
|
|
92
|
+
/**
|
|
93
|
+
* Determine if a tool call should be intercepted based on the proxy mode.
|
|
94
|
+
*/
|
|
95
|
+
declare function shouldIntercept(toolName: string, config: ToolReplacementConfig): {
|
|
96
|
+
intercept: boolean;
|
|
97
|
+
reason: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Generate a human-readable summary of the tool replacement configuration.
|
|
101
|
+
*/
|
|
102
|
+
declare function describeMode(config: ToolReplacementConfig): string;
|
|
103
|
+
/**
|
|
104
|
+
* Parse mode from CLI argument string.
|
|
105
|
+
*/
|
|
106
|
+
declare function parseMode(input: string): ProxyMode;
|
|
107
|
+
|
|
108
|
+
export { DEFAULT_TARGET_TOOLS, DEFAULT_TOOL_REPLACEMENT_CONFIG, type McpToolDef, type ProxyMode, type ToolListTransformResult, type ToolReplacementConfig, describeMode, parseMode, shouldIntercept, transformToolList };
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/toolReplacement.ts
|
|
21
|
+
var toolReplacement_exports = {};
|
|
22
|
+
__export(toolReplacement_exports, {
|
|
23
|
+
DEFAULT_TARGET_TOOLS: () => DEFAULT_TARGET_TOOLS,
|
|
24
|
+
DEFAULT_TOOL_REPLACEMENT_CONFIG: () => DEFAULT_TOOL_REPLACEMENT_CONFIG,
|
|
25
|
+
describeMode: () => describeMode,
|
|
26
|
+
parseMode: () => parseMode,
|
|
27
|
+
shouldIntercept: () => shouldIntercept,
|
|
28
|
+
transformToolList: () => transformToolList
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(toolReplacement_exports);
|
|
31
|
+
var DEFAULT_TARGET_TOOLS = [
|
|
32
|
+
// Claude Code native tools
|
|
33
|
+
"Bash",
|
|
34
|
+
"bash",
|
|
35
|
+
"shell",
|
|
36
|
+
"execute_command",
|
|
37
|
+
"run_command",
|
|
38
|
+
"terminal",
|
|
39
|
+
// Cursor / Windsurf / Continue
|
|
40
|
+
"run_terminal_command",
|
|
41
|
+
"execute_shell",
|
|
42
|
+
"shell_exec",
|
|
43
|
+
// Generic patterns
|
|
44
|
+
"exec",
|
|
45
|
+
"system",
|
|
46
|
+
"subprocess"
|
|
47
|
+
];
|
|
48
|
+
var SOVR_EXEC_TOOL = {
|
|
49
|
+
name: "sovr_exec",
|
|
50
|
+
description: "Execute a shell command through the SOVR Responsibility Layer. All commands are evaluated against security policies before execution. Dangerous commands (rm -rf, DROP TABLE, etc.) will be blocked. This is the ONLY way to execute commands \u2014 use this for ALL shell operations.",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
properties: {
|
|
54
|
+
command: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "The shell command to execute. Will be policy-checked before running."
|
|
57
|
+
},
|
|
58
|
+
workdir: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "Working directory for the command (optional)."
|
|
61
|
+
},
|
|
62
|
+
timeout: {
|
|
63
|
+
type: "number",
|
|
64
|
+
description: "Timeout in milliseconds (default: 300000 = 5 min)."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
required: ["command"]
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var SOVR_STATUS_TOOL = {
|
|
71
|
+
name: "sovr_status",
|
|
72
|
+
description: "Check the current SOVR security status, including active policies, recent decisions, and system health. Use this to understand what commands are allowed or blocked.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
verbose: {
|
|
77
|
+
type: "boolean",
|
|
78
|
+
description: "Include detailed policy information (default: false)."
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var SOVR_AUDIT_TOOL = {
|
|
84
|
+
name: "sovr_audit",
|
|
85
|
+
description: "View the SOVR audit log of recent command evaluations. Shows what was allowed, blocked, or escalated.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
limit: {
|
|
90
|
+
type: "number",
|
|
91
|
+
description: "Number of recent entries to show (default: 10)."
|
|
92
|
+
},
|
|
93
|
+
filter: {
|
|
94
|
+
type: "string",
|
|
95
|
+
enum: ["all", "allowed", "blocked", "escalated"],
|
|
96
|
+
description: "Filter by decision type (default: all)."
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
var DEFAULT_TOOL_REPLACEMENT_CONFIG = {
|
|
102
|
+
mode: "enforce",
|
|
103
|
+
targetTools: DEFAULT_TARGET_TOOLS,
|
|
104
|
+
addStatusTool: true,
|
|
105
|
+
addAuditTool: true
|
|
106
|
+
};
|
|
107
|
+
function transformToolList(upstreamTools, config) {
|
|
108
|
+
const removedTools = [];
|
|
109
|
+
const addedTools = [];
|
|
110
|
+
const wrappedTools = [];
|
|
111
|
+
let resultTools = [];
|
|
112
|
+
switch (config.mode) {
|
|
113
|
+
case "exclusive": {
|
|
114
|
+
for (const tool of upstreamTools) {
|
|
115
|
+
if (isTargetTool(tool.name, config.targetTools)) {
|
|
116
|
+
removedTools.push(tool.name);
|
|
117
|
+
} else {
|
|
118
|
+
resultTools.push(tool);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
resultTools.push(SOVR_EXEC_TOOL);
|
|
122
|
+
addedTools.push(SOVR_EXEC_TOOL.name);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "enforce": {
|
|
126
|
+
for (const tool of upstreamTools) {
|
|
127
|
+
if (isTargetTool(tool.name, config.targetTools)) {
|
|
128
|
+
resultTools.push({
|
|
129
|
+
...tool,
|
|
130
|
+
description: `[SOVR-ENFORCED] ${tool.description || ""} \u2014 All calls are policy-checked by SOVR before execution.`
|
|
131
|
+
});
|
|
132
|
+
wrappedTools.push(tool.name);
|
|
133
|
+
} else {
|
|
134
|
+
resultTools.push(tool);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
resultTools.push(SOVR_EXEC_TOOL);
|
|
138
|
+
addedTools.push(SOVR_EXEC_TOOL.name);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "advisory": {
|
|
142
|
+
resultTools = [...upstreamTools];
|
|
143
|
+
resultTools.push(SOVR_EXEC_TOOL);
|
|
144
|
+
addedTools.push(SOVR_EXEC_TOOL.name);
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "monitor":
|
|
148
|
+
default: {
|
|
149
|
+
resultTools = [...upstreamTools];
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (config.addStatusTool) {
|
|
154
|
+
resultTools.push(SOVR_STATUS_TOOL);
|
|
155
|
+
addedTools.push(SOVR_STATUS_TOOL.name);
|
|
156
|
+
}
|
|
157
|
+
if (config.addAuditTool) {
|
|
158
|
+
resultTools.push(SOVR_AUDIT_TOOL);
|
|
159
|
+
addedTools.push(SOVR_AUDIT_TOOL.name);
|
|
160
|
+
}
|
|
161
|
+
return { tools: resultTools, removedTools, addedTools, wrappedTools };
|
|
162
|
+
}
|
|
163
|
+
function isTargetTool(toolName, targets) {
|
|
164
|
+
const lowerName = toolName.toLowerCase();
|
|
165
|
+
return targets.some((target) => {
|
|
166
|
+
const lowerTarget = target.toLowerCase();
|
|
167
|
+
if (lowerName === lowerTarget) return true;
|
|
168
|
+
if (lowerTarget.includes("*")) {
|
|
169
|
+
const regex = new RegExp(
|
|
170
|
+
"^" + lowerTarget.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
171
|
+
);
|
|
172
|
+
return regex.test(lowerName);
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function shouldIntercept(toolName, config) {
|
|
178
|
+
switch (config.mode) {
|
|
179
|
+
case "exclusive":
|
|
180
|
+
if (toolName === "sovr_exec") {
|
|
181
|
+
return { intercept: true, reason: "exclusive-mode: all exec calls routed through SOVR" };
|
|
182
|
+
}
|
|
183
|
+
if (isTargetTool(toolName, config.targetTools)) {
|
|
184
|
+
return { intercept: true, reason: "exclusive-mode: native tool blocked, use sovr_exec" };
|
|
185
|
+
}
|
|
186
|
+
return { intercept: false, reason: "non-exec tool, passthrough" };
|
|
187
|
+
case "enforce":
|
|
188
|
+
if (toolName === "sovr_exec" || isTargetTool(toolName, config.targetTools)) {
|
|
189
|
+
return { intercept: true, reason: "enforce-mode: policy check required" };
|
|
190
|
+
}
|
|
191
|
+
return { intercept: false, reason: "non-exec tool, passthrough" };
|
|
192
|
+
case "advisory":
|
|
193
|
+
if (toolName === "sovr_exec") {
|
|
194
|
+
return { intercept: true, reason: "advisory-mode: SOVR exec call" };
|
|
195
|
+
}
|
|
196
|
+
if (isTargetTool(toolName, config.targetTools)) {
|
|
197
|
+
return { intercept: false, reason: "advisory-mode: native tool allowed with warning" };
|
|
198
|
+
}
|
|
199
|
+
return { intercept: false, reason: "non-exec tool, passthrough" };
|
|
200
|
+
case "monitor":
|
|
201
|
+
default:
|
|
202
|
+
return { intercept: false, reason: "monitor-mode: all tools passthrough" };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function describeMode(config) {
|
|
206
|
+
switch (config.mode) {
|
|
207
|
+
case "exclusive":
|
|
208
|
+
return `EXCLUSIVE MODE: Native execution tools (${config.targetTools.join(", ")}) are REMOVED. The LLM can ONLY execute commands through sovr_exec. Bypass is impossible.`;
|
|
209
|
+
case "enforce":
|
|
210
|
+
return `ENFORCE MODE: Native execution tools are wrapped with SOVR policy checks. All command executions are evaluated before running. Dangerous commands are blocked.`;
|
|
211
|
+
case "advisory":
|
|
212
|
+
return `ADVISORY MODE: Native tools remain available. SOVR tools are added alongside. Warnings are logged for risky commands but execution is not blocked.`;
|
|
213
|
+
case "monitor":
|
|
214
|
+
return `MONITOR MODE: All tools pass through unchanged. SOVR only logs activity. No enforcement or blocking.`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function parseMode(input) {
|
|
218
|
+
const normalized = input.toLowerCase().trim();
|
|
219
|
+
if (["exclusive", "enforce", "advisory", "monitor"].includes(normalized)) {
|
|
220
|
+
return normalized;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Invalid mode: "${input}". Valid modes: exclusive, enforce, advisory, monitor`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
227
|
+
0 && (module.exports = {
|
|
228
|
+
DEFAULT_TARGET_TOOLS,
|
|
229
|
+
DEFAULT_TOOL_REPLACEMENT_CONFIG,
|
|
230
|
+
describeMode,
|
|
231
|
+
parseMode,
|
|
232
|
+
shouldIntercept,
|
|
233
|
+
transformToolList
|
|
234
|
+
});
|