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.js
CHANGED
|
@@ -5208,6 +5208,973 @@ var init_engine = __esm({
|
|
|
5208
5208
|
}
|
|
5209
5209
|
});
|
|
5210
5210
|
|
|
5211
|
+
// src/toolReplacement.ts
|
|
5212
|
+
var toolReplacement_exports = {};
|
|
5213
|
+
__export(toolReplacement_exports, {
|
|
5214
|
+
DEFAULT_TARGET_TOOLS: () => DEFAULT_TARGET_TOOLS,
|
|
5215
|
+
DEFAULT_TOOL_REPLACEMENT_CONFIG: () => DEFAULT_TOOL_REPLACEMENT_CONFIG,
|
|
5216
|
+
describeMode: () => describeMode,
|
|
5217
|
+
parseMode: () => parseMode,
|
|
5218
|
+
shouldIntercept: () => shouldIntercept,
|
|
5219
|
+
transformToolList: () => transformToolList
|
|
5220
|
+
});
|
|
5221
|
+
function transformToolList(upstreamTools, config) {
|
|
5222
|
+
const removedTools = [];
|
|
5223
|
+
const addedTools = [];
|
|
5224
|
+
const wrappedTools = [];
|
|
5225
|
+
let resultTools = [];
|
|
5226
|
+
switch (config.mode) {
|
|
5227
|
+
case "exclusive": {
|
|
5228
|
+
for (const tool of upstreamTools) {
|
|
5229
|
+
if (isTargetTool(tool.name, config.targetTools)) {
|
|
5230
|
+
removedTools.push(tool.name);
|
|
5231
|
+
} else {
|
|
5232
|
+
resultTools.push(tool);
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
resultTools.push(SOVR_EXEC_TOOL);
|
|
5236
|
+
addedTools.push(SOVR_EXEC_TOOL.name);
|
|
5237
|
+
break;
|
|
5238
|
+
}
|
|
5239
|
+
case "enforce": {
|
|
5240
|
+
for (const tool of upstreamTools) {
|
|
5241
|
+
if (isTargetTool(tool.name, config.targetTools)) {
|
|
5242
|
+
resultTools.push({
|
|
5243
|
+
...tool,
|
|
5244
|
+
description: `[SOVR-ENFORCED] ${tool.description || ""} \u2014 All calls are policy-checked by SOVR before execution.`
|
|
5245
|
+
});
|
|
5246
|
+
wrappedTools.push(tool.name);
|
|
5247
|
+
} else {
|
|
5248
|
+
resultTools.push(tool);
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
resultTools.push(SOVR_EXEC_TOOL);
|
|
5252
|
+
addedTools.push(SOVR_EXEC_TOOL.name);
|
|
5253
|
+
break;
|
|
5254
|
+
}
|
|
5255
|
+
case "advisory": {
|
|
5256
|
+
resultTools = [...upstreamTools];
|
|
5257
|
+
resultTools.push(SOVR_EXEC_TOOL);
|
|
5258
|
+
addedTools.push(SOVR_EXEC_TOOL.name);
|
|
5259
|
+
break;
|
|
5260
|
+
}
|
|
5261
|
+
case "monitor":
|
|
5262
|
+
default: {
|
|
5263
|
+
resultTools = [...upstreamTools];
|
|
5264
|
+
break;
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
if (config.addStatusTool) {
|
|
5268
|
+
resultTools.push(SOVR_STATUS_TOOL);
|
|
5269
|
+
addedTools.push(SOVR_STATUS_TOOL.name);
|
|
5270
|
+
}
|
|
5271
|
+
if (config.addAuditTool) {
|
|
5272
|
+
resultTools.push(SOVR_AUDIT_TOOL);
|
|
5273
|
+
addedTools.push(SOVR_AUDIT_TOOL.name);
|
|
5274
|
+
}
|
|
5275
|
+
return { tools: resultTools, removedTools, addedTools, wrappedTools };
|
|
5276
|
+
}
|
|
5277
|
+
function isTargetTool(toolName, targets) {
|
|
5278
|
+
const lowerName = toolName.toLowerCase();
|
|
5279
|
+
return targets.some((target) => {
|
|
5280
|
+
const lowerTarget = target.toLowerCase();
|
|
5281
|
+
if (lowerName === lowerTarget) return true;
|
|
5282
|
+
if (lowerTarget.includes("*")) {
|
|
5283
|
+
const regex = new RegExp(
|
|
5284
|
+
"^" + lowerTarget.replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
5285
|
+
);
|
|
5286
|
+
return regex.test(lowerName);
|
|
5287
|
+
}
|
|
5288
|
+
return false;
|
|
5289
|
+
});
|
|
5290
|
+
}
|
|
5291
|
+
function shouldIntercept(toolName, config) {
|
|
5292
|
+
switch (config.mode) {
|
|
5293
|
+
case "exclusive":
|
|
5294
|
+
if (toolName === "sovr_exec") {
|
|
5295
|
+
return { intercept: true, reason: "exclusive-mode: all exec calls routed through SOVR" };
|
|
5296
|
+
}
|
|
5297
|
+
if (isTargetTool(toolName, config.targetTools)) {
|
|
5298
|
+
return { intercept: true, reason: "exclusive-mode: native tool blocked, use sovr_exec" };
|
|
5299
|
+
}
|
|
5300
|
+
return { intercept: false, reason: "non-exec tool, passthrough" };
|
|
5301
|
+
case "enforce":
|
|
5302
|
+
if (toolName === "sovr_exec" || isTargetTool(toolName, config.targetTools)) {
|
|
5303
|
+
return { intercept: true, reason: "enforce-mode: policy check required" };
|
|
5304
|
+
}
|
|
5305
|
+
return { intercept: false, reason: "non-exec tool, passthrough" };
|
|
5306
|
+
case "advisory":
|
|
5307
|
+
if (toolName === "sovr_exec") {
|
|
5308
|
+
return { intercept: true, reason: "advisory-mode: SOVR exec call" };
|
|
5309
|
+
}
|
|
5310
|
+
if (isTargetTool(toolName, config.targetTools)) {
|
|
5311
|
+
return { intercept: false, reason: "advisory-mode: native tool allowed with warning" };
|
|
5312
|
+
}
|
|
5313
|
+
return { intercept: false, reason: "non-exec tool, passthrough" };
|
|
5314
|
+
case "monitor":
|
|
5315
|
+
default:
|
|
5316
|
+
return { intercept: false, reason: "monitor-mode: all tools passthrough" };
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
function describeMode(config) {
|
|
5320
|
+
switch (config.mode) {
|
|
5321
|
+
case "exclusive":
|
|
5322
|
+
return `EXCLUSIVE MODE: Native execution tools (${config.targetTools.join(", ")}) are REMOVED. The LLM can ONLY execute commands through sovr_exec. Bypass is impossible.`;
|
|
5323
|
+
case "enforce":
|
|
5324
|
+
return `ENFORCE MODE: Native execution tools are wrapped with SOVR policy checks. All command executions are evaluated before running. Dangerous commands are blocked.`;
|
|
5325
|
+
case "advisory":
|
|
5326
|
+
return `ADVISORY MODE: Native tools remain available. SOVR tools are added alongside. Warnings are logged for risky commands but execution is not blocked.`;
|
|
5327
|
+
case "monitor":
|
|
5328
|
+
return `MONITOR MODE: All tools pass through unchanged. SOVR only logs activity. No enforcement or blocking.`;
|
|
5329
|
+
}
|
|
5330
|
+
}
|
|
5331
|
+
function parseMode(input) {
|
|
5332
|
+
const normalized = input.toLowerCase().trim();
|
|
5333
|
+
if (["exclusive", "enforce", "advisory", "monitor"].includes(normalized)) {
|
|
5334
|
+
return normalized;
|
|
5335
|
+
}
|
|
5336
|
+
throw new Error(
|
|
5337
|
+
`Invalid mode: "${input}". Valid modes: exclusive, enforce, advisory, monitor`
|
|
5338
|
+
);
|
|
5339
|
+
}
|
|
5340
|
+
var DEFAULT_TARGET_TOOLS, SOVR_EXEC_TOOL, SOVR_STATUS_TOOL, SOVR_AUDIT_TOOL, DEFAULT_TOOL_REPLACEMENT_CONFIG;
|
|
5341
|
+
var init_toolReplacement = __esm({
|
|
5342
|
+
"src/toolReplacement.ts"() {
|
|
5343
|
+
"use strict";
|
|
5344
|
+
DEFAULT_TARGET_TOOLS = [
|
|
5345
|
+
// Claude Code native tools
|
|
5346
|
+
"Bash",
|
|
5347
|
+
"bash",
|
|
5348
|
+
"shell",
|
|
5349
|
+
"execute_command",
|
|
5350
|
+
"run_command",
|
|
5351
|
+
"terminal",
|
|
5352
|
+
// Cursor / Windsurf / Continue
|
|
5353
|
+
"run_terminal_command",
|
|
5354
|
+
"execute_shell",
|
|
5355
|
+
"shell_exec",
|
|
5356
|
+
// Generic patterns
|
|
5357
|
+
"exec",
|
|
5358
|
+
"system",
|
|
5359
|
+
"subprocess"
|
|
5360
|
+
];
|
|
5361
|
+
SOVR_EXEC_TOOL = {
|
|
5362
|
+
name: "sovr_exec",
|
|
5363
|
+
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.",
|
|
5364
|
+
inputSchema: {
|
|
5365
|
+
type: "object",
|
|
5366
|
+
properties: {
|
|
5367
|
+
command: {
|
|
5368
|
+
type: "string",
|
|
5369
|
+
description: "The shell command to execute. Will be policy-checked before running."
|
|
5370
|
+
},
|
|
5371
|
+
workdir: {
|
|
5372
|
+
type: "string",
|
|
5373
|
+
description: "Working directory for the command (optional)."
|
|
5374
|
+
},
|
|
5375
|
+
timeout: {
|
|
5376
|
+
type: "number",
|
|
5377
|
+
description: "Timeout in milliseconds (default: 300000 = 5 min)."
|
|
5378
|
+
}
|
|
5379
|
+
},
|
|
5380
|
+
required: ["command"]
|
|
5381
|
+
}
|
|
5382
|
+
};
|
|
5383
|
+
SOVR_STATUS_TOOL = {
|
|
5384
|
+
name: "sovr_status",
|
|
5385
|
+
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.",
|
|
5386
|
+
inputSchema: {
|
|
5387
|
+
type: "object",
|
|
5388
|
+
properties: {
|
|
5389
|
+
verbose: {
|
|
5390
|
+
type: "boolean",
|
|
5391
|
+
description: "Include detailed policy information (default: false)."
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
};
|
|
5396
|
+
SOVR_AUDIT_TOOL = {
|
|
5397
|
+
name: "sovr_audit",
|
|
5398
|
+
description: "View the SOVR audit log of recent command evaluations. Shows what was allowed, blocked, or escalated.",
|
|
5399
|
+
inputSchema: {
|
|
5400
|
+
type: "object",
|
|
5401
|
+
properties: {
|
|
5402
|
+
limit: {
|
|
5403
|
+
type: "number",
|
|
5404
|
+
description: "Number of recent entries to show (default: 10)."
|
|
5405
|
+
},
|
|
5406
|
+
filter: {
|
|
5407
|
+
type: "string",
|
|
5408
|
+
enum: ["all", "allowed", "blocked", "escalated"],
|
|
5409
|
+
description: "Filter by decision type (default: all)."
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
};
|
|
5414
|
+
DEFAULT_TOOL_REPLACEMENT_CONFIG = {
|
|
5415
|
+
mode: "enforce",
|
|
5416
|
+
targetTools: DEFAULT_TARGET_TOOLS,
|
|
5417
|
+
addStatusTool: true,
|
|
5418
|
+
addAuditTool: true
|
|
5419
|
+
};
|
|
5420
|
+
}
|
|
5421
|
+
});
|
|
5422
|
+
|
|
5423
|
+
// src/commandNormalizer.ts
|
|
5424
|
+
var commandNormalizer_exports = {};
|
|
5425
|
+
__export(commandNormalizer_exports, {
|
|
5426
|
+
getBaseCommand: () => getBaseCommand,
|
|
5427
|
+
hasDestructiveEquivalent: () => hasDestructiveEquivalent,
|
|
5428
|
+
hasEncodingTricks: () => hasEncodingTricks,
|
|
5429
|
+
normalize: () => normalize,
|
|
5430
|
+
summarize: () => summarize
|
|
5431
|
+
});
|
|
5432
|
+
function normalize(command) {
|
|
5433
|
+
const original = command;
|
|
5434
|
+
const segments = [];
|
|
5435
|
+
const suspicion_reasons = [];
|
|
5436
|
+
const trimmed = command.trim();
|
|
5437
|
+
if (!trimmed) {
|
|
5438
|
+
return { segments: [], suspicious: false, suspicion_reasons: [], original };
|
|
5439
|
+
}
|
|
5440
|
+
for (const enc of ENCODING_PATTERNS) {
|
|
5441
|
+
if (enc.pattern.test(trimmed)) {
|
|
5442
|
+
suspicion_reasons.push(`${enc.name}: ${enc.risk}`);
|
|
5443
|
+
}
|
|
5444
|
+
}
|
|
5445
|
+
for (const deq of DESTRUCTIVE_EQUIVALENTS) {
|
|
5446
|
+
if (deq.pattern.test(trimmed)) {
|
|
5447
|
+
suspicion_reasons.push(`Destructive equivalent detected: ${deq.description}`);
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
const chainSegments = splitChains(trimmed);
|
|
5451
|
+
for (const chainSeg of chainSegments) {
|
|
5452
|
+
const pipeSegments = splitPipes(chainSeg);
|
|
5453
|
+
for (const pipeSeg of pipeSegments) {
|
|
5454
|
+
const unwrapped = unwrapCommand(pipeSeg.trim());
|
|
5455
|
+
if (unwrapped.unwrapped) {
|
|
5456
|
+
segments.push({
|
|
5457
|
+
raw: pipeSeg.trim(),
|
|
5458
|
+
effective: unwrapped.effective,
|
|
5459
|
+
type: "unwrapped",
|
|
5460
|
+
warnings: unwrapped.warnings
|
|
5461
|
+
});
|
|
5462
|
+
const inner = normalize(unwrapped.effective);
|
|
5463
|
+
for (const innerSeg of inner.segments) {
|
|
5464
|
+
if (innerSeg.effective !== unwrapped.effective) {
|
|
5465
|
+
segments.push(innerSeg);
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
suspicion_reasons.push(...inner.suspicion_reasons);
|
|
5469
|
+
} else {
|
|
5470
|
+
const type = pipeSegments.length > 1 ? "pipe-segment" : chainSegments.length > 1 ? "chain-segment" : "direct";
|
|
5471
|
+
segments.push({
|
|
5472
|
+
raw: pipeSeg.trim(),
|
|
5473
|
+
effective: pipeSeg.trim(),
|
|
5474
|
+
type,
|
|
5475
|
+
warnings: []
|
|
5476
|
+
});
|
|
5477
|
+
}
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
const subshells = extractSubshells(trimmed);
|
|
5481
|
+
for (const sub of subshells) {
|
|
5482
|
+
segments.push({
|
|
5483
|
+
raw: trimmed,
|
|
5484
|
+
effective: sub,
|
|
5485
|
+
type: "subshell",
|
|
5486
|
+
warnings: ["Subshell command extracted for evaluation"]
|
|
5487
|
+
});
|
|
5488
|
+
}
|
|
5489
|
+
return {
|
|
5490
|
+
segments,
|
|
5491
|
+
suspicious: suspicion_reasons.length > 0,
|
|
5492
|
+
suspicion_reasons,
|
|
5493
|
+
original
|
|
5494
|
+
};
|
|
5495
|
+
}
|
|
5496
|
+
function splitChains(command) {
|
|
5497
|
+
return splitRespectingQuotes(command, /\s*(?:&&|\|\||;)\s*/);
|
|
5498
|
+
}
|
|
5499
|
+
function splitPipes(command) {
|
|
5500
|
+
return splitRespectingQuotes(command, /\s*\|(?!\|)\s*/);
|
|
5501
|
+
}
|
|
5502
|
+
function splitRespectingQuotes(input, delimiter) {
|
|
5503
|
+
const segments = [];
|
|
5504
|
+
let current = "";
|
|
5505
|
+
let inSingleQuote = false;
|
|
5506
|
+
let inDoubleQuote = false;
|
|
5507
|
+
let escaped = false;
|
|
5508
|
+
let i = 0;
|
|
5509
|
+
while (i < input.length) {
|
|
5510
|
+
const char = input[i];
|
|
5511
|
+
if (escaped) {
|
|
5512
|
+
current += char;
|
|
5513
|
+
escaped = false;
|
|
5514
|
+
i++;
|
|
5515
|
+
continue;
|
|
5516
|
+
}
|
|
5517
|
+
if (char === "\\") {
|
|
5518
|
+
escaped = true;
|
|
5519
|
+
current += char;
|
|
5520
|
+
i++;
|
|
5521
|
+
continue;
|
|
5522
|
+
}
|
|
5523
|
+
if (char === "'" && !inDoubleQuote) {
|
|
5524
|
+
inSingleQuote = !inSingleQuote;
|
|
5525
|
+
current += char;
|
|
5526
|
+
i++;
|
|
5527
|
+
continue;
|
|
5528
|
+
}
|
|
5529
|
+
if (char === '"' && !inSingleQuote) {
|
|
5530
|
+
inDoubleQuote = !inDoubleQuote;
|
|
5531
|
+
current += char;
|
|
5532
|
+
i++;
|
|
5533
|
+
continue;
|
|
5534
|
+
}
|
|
5535
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
5536
|
+
const remaining = input.slice(i);
|
|
5537
|
+
const match = remaining.match(delimiter);
|
|
5538
|
+
if (match && match.index === 0) {
|
|
5539
|
+
if (current.trim()) {
|
|
5540
|
+
segments.push(current.trim());
|
|
5541
|
+
}
|
|
5542
|
+
current = "";
|
|
5543
|
+
i += match[0].length;
|
|
5544
|
+
continue;
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
current += char;
|
|
5548
|
+
i++;
|
|
5549
|
+
}
|
|
5550
|
+
if (current.trim()) {
|
|
5551
|
+
segments.push(current.trim());
|
|
5552
|
+
}
|
|
5553
|
+
return segments.length > 0 ? segments : [input];
|
|
5554
|
+
}
|
|
5555
|
+
function unwrapCommand(command) {
|
|
5556
|
+
for (const wrapper of SHELL_WRAPPERS) {
|
|
5557
|
+
const match = command.match(wrapper.pattern);
|
|
5558
|
+
if (match) {
|
|
5559
|
+
const inner = wrapper.extract(match);
|
|
5560
|
+
if (inner) {
|
|
5561
|
+
return {
|
|
5562
|
+
unwrapped: true,
|
|
5563
|
+
effective: inner.trim(),
|
|
5564
|
+
wrapper: wrapper.name,
|
|
5565
|
+
warnings: [`Unwrapped from ${wrapper.name}: "${command}" \u2192 "${inner.trim()}"`]
|
|
5566
|
+
};
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
return { unwrapped: false, effective: command, warnings: [] };
|
|
5571
|
+
}
|
|
5572
|
+
function extractSubshells(command) {
|
|
5573
|
+
const subshells = [];
|
|
5574
|
+
const dollarParen = /\$\(([^)]+)\)/g;
|
|
5575
|
+
let match;
|
|
5576
|
+
while ((match = dollarParen.exec(command)) !== null) {
|
|
5577
|
+
if (match[1]) subshells.push(match[1].trim());
|
|
5578
|
+
}
|
|
5579
|
+
const backtick = /`([^`]+)`/g;
|
|
5580
|
+
while ((match = backtick.exec(command)) !== null) {
|
|
5581
|
+
if (match[1]) subshells.push(match[1].trim());
|
|
5582
|
+
}
|
|
5583
|
+
return subshells;
|
|
5584
|
+
}
|
|
5585
|
+
function getBaseCommand(command) {
|
|
5586
|
+
const trimmed = command.trim();
|
|
5587
|
+
const firstSpace = trimmed.indexOf(" ");
|
|
5588
|
+
return firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace);
|
|
5589
|
+
}
|
|
5590
|
+
function hasDestructiveEquivalent(command) {
|
|
5591
|
+
const matches = [];
|
|
5592
|
+
for (const deq of DESTRUCTIVE_EQUIVALENTS) {
|
|
5593
|
+
if (deq.pattern.test(command)) {
|
|
5594
|
+
matches.push({ equivalent: deq.equivalent, description: deq.description });
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5597
|
+
return { found: matches.length > 0, matches };
|
|
5598
|
+
}
|
|
5599
|
+
function hasEncodingTricks(command) {
|
|
5600
|
+
const tricks = [];
|
|
5601
|
+
for (const enc of ENCODING_PATTERNS) {
|
|
5602
|
+
if (enc.pattern.test(command)) {
|
|
5603
|
+
tricks.push({ name: enc.name, risk: enc.risk });
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
return { found: tricks.length > 0, tricks };
|
|
5607
|
+
}
|
|
5608
|
+
function summarize(result) {
|
|
5609
|
+
const lines = [];
|
|
5610
|
+
lines.push(`Original: ${result.original}`);
|
|
5611
|
+
lines.push(`Segments: ${result.segments.length}`);
|
|
5612
|
+
lines.push(`Suspicious: ${result.suspicious}`);
|
|
5613
|
+
if (result.suspicion_reasons.length > 0) {
|
|
5614
|
+
lines.push(`Suspicion reasons:`);
|
|
5615
|
+
for (const reason of result.suspicion_reasons) {
|
|
5616
|
+
lines.push(` - ${reason}`);
|
|
5617
|
+
}
|
|
5618
|
+
}
|
|
5619
|
+
lines.push(`Segments:`);
|
|
5620
|
+
for (const seg of result.segments) {
|
|
5621
|
+
lines.push(` [${seg.type}] ${seg.effective}`);
|
|
5622
|
+
for (const w of seg.warnings) {
|
|
5623
|
+
lines.push(` \u26A0 ${w}`);
|
|
5624
|
+
}
|
|
5625
|
+
}
|
|
5626
|
+
return lines.join("\n");
|
|
5627
|
+
}
|
|
5628
|
+
var SHELL_WRAPPERS, ENCODING_PATTERNS, DESTRUCTIVE_EQUIVALENTS;
|
|
5629
|
+
var init_commandNormalizer = __esm({
|
|
5630
|
+
"src/commandNormalizer.ts"() {
|
|
5631
|
+
"use strict";
|
|
5632
|
+
SHELL_WRAPPERS = [
|
|
5633
|
+
// bash -c "cmd" / sh -c "cmd"
|
|
5634
|
+
{
|
|
5635
|
+
pattern: /^(?:bash|sh|zsh|dash|ksh)\s+-c\s+(?:"([^"]+)"|'([^']+)'|(\S+))/,
|
|
5636
|
+
extract: (m) => m[1] || m[2] || m[3] || "",
|
|
5637
|
+
name: "shell -c"
|
|
5638
|
+
},
|
|
5639
|
+
// env cmd args...
|
|
5640
|
+
{
|
|
5641
|
+
pattern: /^env\s+(?:-\S+\s+)*(?:\S+=\S+\s+)*(.+)/,
|
|
5642
|
+
extract: (m) => m[1] || "",
|
|
5643
|
+
name: "env"
|
|
5644
|
+
},
|
|
5645
|
+
// xargs cmd
|
|
5646
|
+
{
|
|
5647
|
+
pattern: /^xargs\s+(?:-\S+\s+)*(.+)/,
|
|
5648
|
+
extract: (m) => m[1] || "",
|
|
5649
|
+
name: "xargs"
|
|
5650
|
+
},
|
|
5651
|
+
// sudo cmd
|
|
5652
|
+
{
|
|
5653
|
+
pattern: /^sudo\s+(?:-\S+\s+)*(.+)/,
|
|
5654
|
+
extract: (m) => m[1] || "",
|
|
5655
|
+
name: "sudo"
|
|
5656
|
+
},
|
|
5657
|
+
// nohup cmd
|
|
5658
|
+
{
|
|
5659
|
+
pattern: /^nohup\s+(.+?)(?:\s*&\s*)?$/,
|
|
5660
|
+
extract: (m) => m[1] || "",
|
|
5661
|
+
name: "nohup"
|
|
5662
|
+
},
|
|
5663
|
+
// timeout N cmd
|
|
5664
|
+
{
|
|
5665
|
+
pattern: /^timeout\s+\d+[smhd]?\s+(.+)/,
|
|
5666
|
+
extract: (m) => m[1] || "",
|
|
5667
|
+
name: "timeout"
|
|
5668
|
+
},
|
|
5669
|
+
// nice -n N cmd
|
|
5670
|
+
{
|
|
5671
|
+
pattern: /^nice\s+(?:-n\s+\d+\s+)?(.+)/,
|
|
5672
|
+
extract: (m) => m[1] || "",
|
|
5673
|
+
name: "nice"
|
|
5674
|
+
},
|
|
5675
|
+
// strace / ltrace cmd
|
|
5676
|
+
{
|
|
5677
|
+
pattern: /^(?:strace|ltrace)\s+(?:-\S+\s+)*(.+)/,
|
|
5678
|
+
extract: (m) => m[1] || "",
|
|
5679
|
+
name: "trace"
|
|
5680
|
+
},
|
|
5681
|
+
// watch -n N cmd
|
|
5682
|
+
{
|
|
5683
|
+
pattern: /^watch\s+(?:-n\s+\d+\s+)?(.+)/,
|
|
5684
|
+
extract: (m) => m[1] || "",
|
|
5685
|
+
name: "watch"
|
|
5686
|
+
}
|
|
5687
|
+
];
|
|
5688
|
+
ENCODING_PATTERNS = [
|
|
5689
|
+
// echo "base64" | base64 -d | bash
|
|
5690
|
+
{
|
|
5691
|
+
pattern: /base64\s+(?:-d|--decode)/,
|
|
5692
|
+
name: "base64-decode",
|
|
5693
|
+
risk: "Command may be hidden via base64 encoding"
|
|
5694
|
+
},
|
|
5695
|
+
// printf '\x72\x6d' (hex encoding)
|
|
5696
|
+
{
|
|
5697
|
+
pattern: /printf\s+.*\\x[0-9a-fA-F]{2}/,
|
|
5698
|
+
name: "hex-printf",
|
|
5699
|
+
risk: "Command may be hidden via hex encoding in printf"
|
|
5700
|
+
},
|
|
5701
|
+
// $'\x72\x6d' (ANSI-C quoting)
|
|
5702
|
+
{
|
|
5703
|
+
pattern: /\$'[^']*\\x[0-9a-fA-F]{2}/,
|
|
5704
|
+
name: "ansi-c-quoting",
|
|
5705
|
+
risk: "Command may be hidden via ANSI-C quoting"
|
|
5706
|
+
},
|
|
5707
|
+
// python -c "import os; os.system('rm -rf /')"
|
|
5708
|
+
{
|
|
5709
|
+
pattern: /python[23]?\s+-c\s+/,
|
|
5710
|
+
name: "python-exec",
|
|
5711
|
+
risk: "Arbitrary code execution via Python"
|
|
5712
|
+
},
|
|
5713
|
+
// perl -e "system('rm -rf /')"
|
|
5714
|
+
{
|
|
5715
|
+
pattern: /perl\s+-e\s+/,
|
|
5716
|
+
name: "perl-exec",
|
|
5717
|
+
risk: "Arbitrary code execution via Perl"
|
|
5718
|
+
},
|
|
5719
|
+
// ruby -e "system('rm -rf /')"
|
|
5720
|
+
{
|
|
5721
|
+
pattern: /ruby\s+-e\s+/,
|
|
5722
|
+
name: "ruby-exec",
|
|
5723
|
+
risk: "Arbitrary code execution via Ruby"
|
|
5724
|
+
},
|
|
5725
|
+
// eval "cmd"
|
|
5726
|
+
{
|
|
5727
|
+
pattern: /\beval\s+/,
|
|
5728
|
+
name: "eval",
|
|
5729
|
+
risk: "Dynamic command evaluation"
|
|
5730
|
+
},
|
|
5731
|
+
// curl ... | bash
|
|
5732
|
+
{
|
|
5733
|
+
pattern: /curl\s+.*\|\s*(?:bash|sh|zsh)/,
|
|
5734
|
+
name: "curl-pipe-shell",
|
|
5735
|
+
risk: "Remote code execution via curl | bash"
|
|
5736
|
+
},
|
|
5737
|
+
// wget ... -O - | bash
|
|
5738
|
+
{
|
|
5739
|
+
pattern: /wget\s+.*\|\s*(?:bash|sh|zsh)/,
|
|
5740
|
+
name: "wget-pipe-shell",
|
|
5741
|
+
risk: "Remote code execution via wget | bash"
|
|
5742
|
+
}
|
|
5743
|
+
];
|
|
5744
|
+
DESTRUCTIVE_EQUIVALENTS = [
|
|
5745
|
+
// find / -delete (equivalent to rm -rf)
|
|
5746
|
+
{ pattern: /find\s+.*-delete/, equivalent: "rm -rf", description: "find -delete is equivalent to rm -rf" },
|
|
5747
|
+
// find / -exec rm {} (equivalent to rm -rf)
|
|
5748
|
+
{ pattern: /find\s+.*-exec\s+rm/, equivalent: "rm -rf", description: "find -exec rm is equivalent to rm -rf" },
|
|
5749
|
+
// TRUNCATE TABLE (equivalent to DELETE FROM)
|
|
5750
|
+
{ pattern: /TRUNCATE\s+TABLE/i, equivalent: "DELETE FROM", description: "TRUNCATE TABLE is equivalent to DELETE FROM" },
|
|
5751
|
+
// dd if=/dev/zero of=/ (disk wipe)
|
|
5752
|
+
{ pattern: /dd\s+.*of=\//, equivalent: "disk-wipe", description: "dd writing to root is destructive" },
|
|
5753
|
+
// mkfs (format disk)
|
|
5754
|
+
{ pattern: /mkfs/, equivalent: "disk-format", description: "mkfs formats a disk" },
|
|
5755
|
+
// shred (secure delete)
|
|
5756
|
+
{ pattern: /shred\s+/, equivalent: "secure-delete", description: "shred securely deletes files" },
|
|
5757
|
+
// chmod 777 / (open permissions)
|
|
5758
|
+
{ pattern: /chmod\s+777\s+\//, equivalent: "open-permissions", description: "chmod 777 / opens all permissions" },
|
|
5759
|
+
// chown root (change ownership)
|
|
5760
|
+
{ pattern: /chown\s+.*\//, equivalent: "change-ownership", description: "chown on root paths is dangerous" },
|
|
5761
|
+
// iptables -F (flush firewall)
|
|
5762
|
+
{ pattern: /iptables\s+-F/, equivalent: "flush-firewall", description: "iptables -F flushes all firewall rules" }
|
|
5763
|
+
];
|
|
5764
|
+
}
|
|
5765
|
+
});
|
|
5766
|
+
|
|
5767
|
+
// src/whitelistEngine.ts
|
|
5768
|
+
var whitelistEngine_exports = {};
|
|
5769
|
+
__export(whitelistEngine_exports, {
|
|
5770
|
+
PRESETS: () => PRESETS,
|
|
5771
|
+
PRESET_DEVELOPER: () => PRESET_DEVELOPER,
|
|
5772
|
+
PRESET_PRODUCTION: () => PRESET_PRODUCTION,
|
|
5773
|
+
PRESET_READONLY: () => PRESET_READONLY,
|
|
5774
|
+
WhitelistEngine: () => WhitelistEngine,
|
|
5775
|
+
autoLoadPolicy: () => autoLoadPolicy,
|
|
5776
|
+
generatePolicyFile: () => generatePolicyFile,
|
|
5777
|
+
loadPolicy: () => loadPolicy
|
|
5778
|
+
});
|
|
5779
|
+
function matchPattern(command, pattern) {
|
|
5780
|
+
const normalizedCmd = command.trim().replace(/\s+/g, " ");
|
|
5781
|
+
const normalizedPattern = pattern.trim().replace(/\s+/g, " ");
|
|
5782
|
+
const regexStr = "^" + normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$";
|
|
5783
|
+
try {
|
|
5784
|
+
return new RegExp(regexStr).test(normalizedCmd);
|
|
5785
|
+
} catch {
|
|
5786
|
+
return false;
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
function loadPolicy(filePath) {
|
|
5790
|
+
if (!(0, import_node_fs2.existsSync)(filePath)) {
|
|
5791
|
+
throw new Error(`Policy file not found: ${filePath}`);
|
|
5792
|
+
}
|
|
5793
|
+
const raw = (0, import_node_fs2.readFileSync)(filePath, "utf-8");
|
|
5794
|
+
try {
|
|
5795
|
+
return JSON.parse(raw);
|
|
5796
|
+
} catch {
|
|
5797
|
+
}
|
|
5798
|
+
return parseSimpleYaml(raw);
|
|
5799
|
+
}
|
|
5800
|
+
function autoLoadPolicy(projectDir) {
|
|
5801
|
+
const candidates = [
|
|
5802
|
+
(0, import_node_path2.join)(projectDir, ".sovr", "policy.json"),
|
|
5803
|
+
(0, import_node_path2.join)(projectDir, ".sovr", "policy.yaml"),
|
|
5804
|
+
(0, import_node_path2.join)(projectDir, ".sovr", "policy.yml"),
|
|
5805
|
+
(0, import_node_path2.join)(projectDir, "sovr.policy.json")
|
|
5806
|
+
];
|
|
5807
|
+
for (const candidate of candidates) {
|
|
5808
|
+
if ((0, import_node_fs2.existsSync)(candidate)) {
|
|
5809
|
+
return loadPolicy(candidate);
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
return null;
|
|
5813
|
+
}
|
|
5814
|
+
function parseSimpleYaml(raw) {
|
|
5815
|
+
const lines = raw.split("\n");
|
|
5816
|
+
const policy = {
|
|
5817
|
+
version: "1.0",
|
|
5818
|
+
mode: "whitelist",
|
|
5819
|
+
rules: []
|
|
5820
|
+
};
|
|
5821
|
+
let currentRule = null;
|
|
5822
|
+
let inRules = false;
|
|
5823
|
+
for (const line of lines) {
|
|
5824
|
+
const trimmed = line.trim();
|
|
5825
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
5826
|
+
if (trimmed.startsWith("version:")) {
|
|
5827
|
+
policy.version = trimmed.split(":").slice(1).join(":").trim().replace(/['"]/g, "");
|
|
5828
|
+
} else if (trimmed.startsWith("mode:")) {
|
|
5829
|
+
policy.mode = trimmed.split(":").slice(1).join(":").trim().replace(/['"]/g, "");
|
|
5830
|
+
} else if (trimmed === "rules:") {
|
|
5831
|
+
inRules = true;
|
|
5832
|
+
} else if (inRules && trimmed.startsWith("- pattern:")) {
|
|
5833
|
+
if (currentRule?.pattern) {
|
|
5834
|
+
policy.rules.push(currentRule);
|
|
5835
|
+
}
|
|
5836
|
+
currentRule = {
|
|
5837
|
+
pattern: trimmed.replace("- pattern:", "").trim().replace(/['"]/g, ""),
|
|
5838
|
+
allow: true,
|
|
5839
|
+
enabled: true
|
|
5840
|
+
};
|
|
5841
|
+
} else if (inRules && currentRule) {
|
|
5842
|
+
if (trimmed.startsWith("allow:")) {
|
|
5843
|
+
currentRule.allow = trimmed.includes("true");
|
|
5844
|
+
} else if (trimmed.startsWith("description:")) {
|
|
5845
|
+
currentRule.description = trimmed.split(":").slice(1).join(":").trim().replace(/['"]/g, "");
|
|
5846
|
+
} else if (trimmed.startsWith("require_approval:")) {
|
|
5847
|
+
currentRule.require_approval = trimmed.includes("true");
|
|
5848
|
+
} else if (trimmed.startsWith("max_args:")) {
|
|
5849
|
+
currentRule.max_args = parseInt(trimmed.split(":")[1].trim());
|
|
5850
|
+
} else if (trimmed.startsWith("priority:")) {
|
|
5851
|
+
currentRule.priority = parseInt(trimmed.split(":")[1].trim());
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
if (currentRule?.pattern) {
|
|
5856
|
+
policy.rules.push(currentRule);
|
|
5857
|
+
}
|
|
5858
|
+
return policy;
|
|
5859
|
+
}
|
|
5860
|
+
function generatePolicyFile(preset, format = "yaml") {
|
|
5861
|
+
const policy = PRESETS[preset];
|
|
5862
|
+
if (!policy) {
|
|
5863
|
+
throw new Error(`Unknown preset: ${preset}. Available: ${Object.keys(PRESETS).join(", ")}`);
|
|
5864
|
+
}
|
|
5865
|
+
if (format === "json") {
|
|
5866
|
+
return JSON.stringify(policy, null, 2);
|
|
5867
|
+
}
|
|
5868
|
+
let yaml = `# SOVR Whitelist Policy
|
|
5869
|
+
`;
|
|
5870
|
+
yaml += `# Preset: ${preset}
|
|
5871
|
+
`;
|
|
5872
|
+
yaml += `# Generated: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
5873
|
+
|
|
5874
|
+
`;
|
|
5875
|
+
yaml += `version: "${policy.version}"
|
|
5876
|
+
`;
|
|
5877
|
+
yaml += `mode: ${policy.mode}
|
|
5878
|
+
|
|
5879
|
+
`;
|
|
5880
|
+
yaml += `rules:
|
|
5881
|
+
`;
|
|
5882
|
+
for (const rule of policy.rules) {
|
|
5883
|
+
yaml += ` - pattern: "${rule.pattern}"
|
|
5884
|
+
`;
|
|
5885
|
+
yaml += ` allow: ${rule.allow}
|
|
5886
|
+
`;
|
|
5887
|
+
if (rule.description) yaml += ` description: "${rule.description}"
|
|
5888
|
+
`;
|
|
5889
|
+
if (rule.require_approval) yaml += ` require_approval: true
|
|
5890
|
+
`;
|
|
5891
|
+
if (rule.max_args !== void 0) yaml += ` max_args: ${rule.max_args}
|
|
5892
|
+
`;
|
|
5893
|
+
yaml += `
|
|
5894
|
+
`;
|
|
5895
|
+
}
|
|
5896
|
+
if (policy.settings) {
|
|
5897
|
+
yaml += `settings:
|
|
5898
|
+
`;
|
|
5899
|
+
for (const [key, value] of Object.entries(policy.settings)) {
|
|
5900
|
+
yaml += ` ${key}: ${value}
|
|
5901
|
+
`;
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
return yaml;
|
|
5905
|
+
}
|
|
5906
|
+
var import_node_fs2, import_node_path2, PRESET_READONLY, PRESET_DEVELOPER, PRESET_PRODUCTION, PRESETS, WhitelistEngine;
|
|
5907
|
+
var init_whitelistEngine = __esm({
|
|
5908
|
+
"src/whitelistEngine.ts"() {
|
|
5909
|
+
"use strict";
|
|
5910
|
+
import_node_fs2 = require("fs");
|
|
5911
|
+
import_node_path2 = require("path");
|
|
5912
|
+
PRESET_READONLY = {
|
|
5913
|
+
version: "1.0",
|
|
5914
|
+
mode: "whitelist",
|
|
5915
|
+
rules: [
|
|
5916
|
+
{ pattern: "ls *", allow: true, description: "List directory" },
|
|
5917
|
+
{ pattern: "cat *", allow: true, description: "Read file" },
|
|
5918
|
+
{ pattern: "head *", allow: true, description: "Read file head" },
|
|
5919
|
+
{ pattern: "tail *", allow: true, description: "Read file tail" },
|
|
5920
|
+
{ pattern: "find *", allow: true, description: "Find files", max_args: 10 },
|
|
5921
|
+
{ pattern: "grep *", allow: true, description: "Search in files" },
|
|
5922
|
+
{ pattern: "wc *", allow: true, description: "Word count" },
|
|
5923
|
+
{ pattern: "file *", allow: true, description: "File type" },
|
|
5924
|
+
{ pattern: "pwd", allow: true, description: "Print working directory" },
|
|
5925
|
+
{ pattern: "whoami", allow: true, description: "Current user" },
|
|
5926
|
+
{ pattern: "date", allow: true, description: "Current date" },
|
|
5927
|
+
{ pattern: "echo *", allow: true, description: "Echo text" }
|
|
5928
|
+
],
|
|
5929
|
+
settings: {
|
|
5930
|
+
max_command_length: 500,
|
|
5931
|
+
allow_env_expansion: false,
|
|
5932
|
+
allow_subshell: false,
|
|
5933
|
+
allow_pipes: true,
|
|
5934
|
+
allow_chains: false,
|
|
5935
|
+
allow_redirects: false
|
|
5936
|
+
}
|
|
5937
|
+
};
|
|
5938
|
+
PRESET_DEVELOPER = {
|
|
5939
|
+
version: "1.0",
|
|
5940
|
+
mode: "whitelist",
|
|
5941
|
+
rules: [
|
|
5942
|
+
// Read operations
|
|
5943
|
+
...PRESET_READONLY.rules,
|
|
5944
|
+
// Git (safe operations)
|
|
5945
|
+
{ pattern: "git status", allow: true, description: "Git status" },
|
|
5946
|
+
{ pattern: "git diff *", allow: true, description: "Git diff" },
|
|
5947
|
+
{ pattern: "git log *", allow: true, description: "Git log" },
|
|
5948
|
+
{ pattern: "git branch *", allow: true, description: "Git branch" },
|
|
5949
|
+
{ pattern: "git add *", allow: true, description: "Git add" },
|
|
5950
|
+
{ pattern: "git commit *", allow: true, description: "Git commit" },
|
|
5951
|
+
{ pattern: "git push *", allow: true, require_approval: true, description: "Git push (requires approval)" },
|
|
5952
|
+
{ pattern: "git pull *", allow: true, description: "Git pull" },
|
|
5953
|
+
{ pattern: "git checkout *", allow: true, description: "Git checkout" },
|
|
5954
|
+
{ pattern: "git stash *", allow: true, description: "Git stash" },
|
|
5955
|
+
// Node.js / npm
|
|
5956
|
+
{ pattern: "node *", allow: true, description: "Run Node.js" },
|
|
5957
|
+
{ pattern: "npm run *", allow: true, description: "Run npm script" },
|
|
5958
|
+
{ pattern: "npm test", allow: true, description: "Run tests" },
|
|
5959
|
+
{ pattern: "npm install *", allow: true, description: "Install packages" },
|
|
5960
|
+
{ pattern: "npx *", allow: true, description: "Run npx" },
|
|
5961
|
+
{ pattern: "pnpm *", allow: true, description: "Run pnpm" },
|
|
5962
|
+
{ pattern: "yarn *", allow: true, description: "Run yarn" },
|
|
5963
|
+
// Python
|
|
5964
|
+
{ pattern: "python3 *", allow: true, description: "Run Python", max_args: 20 },
|
|
5965
|
+
{ pattern: "pip install *", allow: true, description: "Install Python packages" },
|
|
5966
|
+
{ pattern: "pip3 install *", allow: true, description: "Install Python packages" },
|
|
5967
|
+
// Build tools
|
|
5968
|
+
{ pattern: "make *", allow: true, description: "Run make" },
|
|
5969
|
+
{ pattern: "cargo *", allow: true, description: "Run cargo" },
|
|
5970
|
+
// File operations (limited)
|
|
5971
|
+
{ pattern: "mkdir *", allow: true, description: "Create directory" },
|
|
5972
|
+
{ pattern: "cp *", allow: true, description: "Copy files" },
|
|
5973
|
+
{ pattern: "mv *", allow: true, description: "Move files" },
|
|
5974
|
+
{ pattern: "touch *", allow: true, description: "Create empty file" },
|
|
5975
|
+
// Dangerous operations — require approval
|
|
5976
|
+
{ pattern: "rm *", allow: true, require_approval: true, description: "Delete files (requires approval)" },
|
|
5977
|
+
{ pattern: "npm publish *", allow: true, require_approval: true, description: "Publish package (requires approval)" },
|
|
5978
|
+
{ pattern: "docker *", allow: true, require_approval: true, description: "Docker operations (requires approval)" }
|
|
5979
|
+
],
|
|
5980
|
+
settings: {
|
|
5981
|
+
max_command_length: 2e3,
|
|
5982
|
+
allow_env_expansion: true,
|
|
5983
|
+
allow_subshell: false,
|
|
5984
|
+
allow_pipes: true,
|
|
5985
|
+
allow_chains: true,
|
|
5986
|
+
allow_redirects: true
|
|
5987
|
+
}
|
|
5988
|
+
};
|
|
5989
|
+
PRESET_PRODUCTION = {
|
|
5990
|
+
version: "1.0",
|
|
5991
|
+
mode: "whitelist",
|
|
5992
|
+
rules: [
|
|
5993
|
+
{ pattern: "echo *", allow: true, description: "Echo" },
|
|
5994
|
+
{ pattern: "date", allow: true, description: "Date" },
|
|
5995
|
+
{ pattern: "uptime", allow: true, description: "Uptime" },
|
|
5996
|
+
{ pattern: "df *", allow: true, description: "Disk usage" },
|
|
5997
|
+
{ pattern: "free *", allow: true, description: "Memory usage" },
|
|
5998
|
+
{ pattern: "ps *", allow: true, description: "Process list" },
|
|
5999
|
+
{ pattern: "curl *", allow: true, require_approval: true, description: "HTTP request (requires approval)" }
|
|
6000
|
+
],
|
|
6001
|
+
default_action: "deny",
|
|
6002
|
+
settings: {
|
|
6003
|
+
max_command_length: 500,
|
|
6004
|
+
allow_env_expansion: false,
|
|
6005
|
+
allow_subshell: false,
|
|
6006
|
+
allow_pipes: false,
|
|
6007
|
+
allow_chains: false,
|
|
6008
|
+
allow_redirects: false
|
|
6009
|
+
}
|
|
6010
|
+
};
|
|
6011
|
+
PRESETS = {
|
|
6012
|
+
readonly: PRESET_READONLY,
|
|
6013
|
+
developer: PRESET_DEVELOPER,
|
|
6014
|
+
production: PRESET_PRODUCTION
|
|
6015
|
+
};
|
|
6016
|
+
WhitelistEngine = class {
|
|
6017
|
+
policy;
|
|
6018
|
+
constructor(policy) {
|
|
6019
|
+
this.policy = policy;
|
|
6020
|
+
this.policy.rules.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6021
|
+
}
|
|
6022
|
+
/**
|
|
6023
|
+
* Evaluate a command against the whitelist policy.
|
|
6024
|
+
*/
|
|
6025
|
+
evaluate(command) {
|
|
6026
|
+
const violations = [];
|
|
6027
|
+
const structuralCheck = this.checkStructural(command);
|
|
6028
|
+
violations.push(...structuralCheck.violations);
|
|
6029
|
+
if (structuralCheck.blocked) {
|
|
6030
|
+
return {
|
|
6031
|
+
allowed: false,
|
|
6032
|
+
require_approval: false,
|
|
6033
|
+
reason: `Structural violation: ${violations.join(", ")}`,
|
|
6034
|
+
risk: "critical",
|
|
6035
|
+
violations
|
|
6036
|
+
};
|
|
6037
|
+
}
|
|
6038
|
+
const enabledRules = this.policy.rules.filter((r) => r.enabled !== false);
|
|
6039
|
+
for (const rule of enabledRules) {
|
|
6040
|
+
if (matchPattern(command, rule.pattern)) {
|
|
6041
|
+
if (rule.max_args !== void 0) {
|
|
6042
|
+
const argCount = command.split(/\s+/).length - 1;
|
|
6043
|
+
if (argCount > rule.max_args) {
|
|
6044
|
+
violations.push(`Too many arguments: ${argCount} > ${rule.max_args}`);
|
|
6045
|
+
return {
|
|
6046
|
+
allowed: false,
|
|
6047
|
+
require_approval: false,
|
|
6048
|
+
matched_rule: rule,
|
|
6049
|
+
reason: `Argument limit exceeded for pattern "${rule.pattern}"`,
|
|
6050
|
+
risk: "high",
|
|
6051
|
+
violations
|
|
6052
|
+
};
|
|
6053
|
+
}
|
|
6054
|
+
}
|
|
6055
|
+
if (rule.allow) {
|
|
6056
|
+
return {
|
|
6057
|
+
allowed: true,
|
|
6058
|
+
require_approval: rule.require_approval ?? false,
|
|
6059
|
+
matched_rule: rule,
|
|
6060
|
+
reason: rule.description || `Matched whitelist pattern: ${rule.pattern}`,
|
|
6061
|
+
risk: rule.require_approval ? "medium" : "low",
|
|
6062
|
+
violations
|
|
6063
|
+
};
|
|
6064
|
+
} else {
|
|
6065
|
+
return {
|
|
6066
|
+
allowed: false,
|
|
6067
|
+
require_approval: false,
|
|
6068
|
+
matched_rule: rule,
|
|
6069
|
+
reason: rule.description || `Matched blacklist pattern: ${rule.pattern}`,
|
|
6070
|
+
risk: "high",
|
|
6071
|
+
violations
|
|
6072
|
+
};
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
}
|
|
6076
|
+
switch (this.policy.mode) {
|
|
6077
|
+
case "whitelist":
|
|
6078
|
+
return {
|
|
6079
|
+
allowed: false,
|
|
6080
|
+
require_approval: false,
|
|
6081
|
+
reason: `No whitelist rule matches command. In whitelist mode, unlisted commands are DENIED.`,
|
|
6082
|
+
risk: "high",
|
|
6083
|
+
violations
|
|
6084
|
+
};
|
|
6085
|
+
case "blacklist":
|
|
6086
|
+
return {
|
|
6087
|
+
allowed: true,
|
|
6088
|
+
require_approval: false,
|
|
6089
|
+
reason: "No blacklist rule matches. Command allowed by default.",
|
|
6090
|
+
risk: "medium",
|
|
6091
|
+
violations
|
|
6092
|
+
};
|
|
6093
|
+
case "hybrid": {
|
|
6094
|
+
const defaultAction = this.policy.default_action || "deny";
|
|
6095
|
+
return {
|
|
6096
|
+
allowed: defaultAction === "allow",
|
|
6097
|
+
require_approval: defaultAction === "escalate",
|
|
6098
|
+
reason: `No rule matches. Hybrid mode default: ${defaultAction}`,
|
|
6099
|
+
risk: defaultAction === "allow" ? "medium" : "high",
|
|
6100
|
+
violations
|
|
6101
|
+
};
|
|
6102
|
+
}
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
/**
|
|
6106
|
+
* Check structural constraints (pipes, chains, subshells, etc.)
|
|
6107
|
+
*/
|
|
6108
|
+
checkStructural(command) {
|
|
6109
|
+
const violations = [];
|
|
6110
|
+
const settings = this.policy.settings || {};
|
|
6111
|
+
if (settings.max_command_length && command.length > settings.max_command_length) {
|
|
6112
|
+
violations.push(`Command too long: ${command.length} > ${settings.max_command_length}`);
|
|
6113
|
+
}
|
|
6114
|
+
if (settings.allow_subshell === false) {
|
|
6115
|
+
if (/\$\(/.test(command) || /`[^`]+`/.test(command)) {
|
|
6116
|
+
violations.push("Subshell execution not allowed");
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
6119
|
+
if (settings.allow_pipes === false) {
|
|
6120
|
+
if (/\|(?!\|)/.test(command)) {
|
|
6121
|
+
violations.push("Pipe chains not allowed");
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
if (settings.allow_chains === false) {
|
|
6125
|
+
if (/[;&]|&&|\|\|/.test(command)) {
|
|
6126
|
+
violations.push("Command chaining not allowed");
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
if (settings.allow_redirects === false) {
|
|
6130
|
+
if (/[<>]|>>/.test(command)) {
|
|
6131
|
+
violations.push("Output redirection not allowed");
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
6134
|
+
if (settings.allow_env_expansion === false) {
|
|
6135
|
+
if (/\$[A-Za-z_]/.test(command) || /\$\{/.test(command)) {
|
|
6136
|
+
violations.push("Environment variable expansion not allowed");
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
return {
|
|
6140
|
+
blocked: violations.length > 0,
|
|
6141
|
+
violations
|
|
6142
|
+
};
|
|
6143
|
+
}
|
|
6144
|
+
/**
|
|
6145
|
+
* Get the current policy.
|
|
6146
|
+
*/
|
|
6147
|
+
getPolicy() {
|
|
6148
|
+
return { ...this.policy };
|
|
6149
|
+
}
|
|
6150
|
+
/**
|
|
6151
|
+
* Add a rule dynamically.
|
|
6152
|
+
*/
|
|
6153
|
+
addRule(rule) {
|
|
6154
|
+
this.policy.rules.push(rule);
|
|
6155
|
+
this.policy.rules.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6156
|
+
}
|
|
6157
|
+
/**
|
|
6158
|
+
* Remove a rule by pattern.
|
|
6159
|
+
*/
|
|
6160
|
+
removeRule(pattern) {
|
|
6161
|
+
const idx = this.policy.rules.findIndex((r) => r.pattern === pattern);
|
|
6162
|
+
if (idx >= 0) {
|
|
6163
|
+
this.policy.rules.splice(idx, 1);
|
|
6164
|
+
return true;
|
|
6165
|
+
}
|
|
6166
|
+
return false;
|
|
6167
|
+
}
|
|
6168
|
+
/**
|
|
6169
|
+
* List all rules.
|
|
6170
|
+
*/
|
|
6171
|
+
listRules() {
|
|
6172
|
+
return [...this.policy.rules];
|
|
6173
|
+
}
|
|
6174
|
+
};
|
|
6175
|
+
}
|
|
6176
|
+
});
|
|
6177
|
+
|
|
5211
6178
|
// src/index.ts
|
|
5212
6179
|
var index_exports = {};
|
|
5213
6180
|
__export(index_exports, {
|
|
@@ -5529,7 +6496,7 @@ var UsageTracker = class {
|
|
|
5529
6496
|
};
|
|
5530
6497
|
|
|
5531
6498
|
// src/index.ts
|
|
5532
|
-
var PROXY_VERSION = "
|
|
6499
|
+
var PROXY_VERSION = "7.0.0";
|
|
5533
6500
|
var SOVR_MIN_VERSION_URL = "https://api.sovr.inc/api/sovr/v1/version/check";
|
|
5534
6501
|
var SOVR_DASHBOARD_URL = "https://sovr.inc/dashboard/api-keys";
|
|
5535
6502
|
function validateApiKey(key) {
|
|
@@ -6347,11 +7314,15 @@ var McpProxy = class extends import_node_events.EventEmitter {
|
|
|
6347
7314
|
};
|
|
6348
7315
|
async function cli(args) {
|
|
6349
7316
|
const { PolicyEngine: PolicyEngine2, DEFAULT_RULES: DEFAULT_RULES2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
7317
|
+
const { transformToolList: transformToolList2, shouldIntercept: shouldIntercept2, parseMode: parseMode2 } = await Promise.resolve().then(() => (init_toolReplacement(), toolReplacement_exports));
|
|
7318
|
+
const { normalize: normalizeCommand, summarize: summarizeNormalization } = await Promise.resolve().then(() => (init_commandNormalizer(), commandNormalizer_exports));
|
|
6350
7319
|
let upstreamCmd = "";
|
|
6351
7320
|
let upstreamArgs = [];
|
|
6352
7321
|
let rulesFile = null;
|
|
6353
7322
|
let verbose = false;
|
|
6354
7323
|
let startupTimeoutMs = 3e4;
|
|
7324
|
+
let mode = "enforce";
|
|
7325
|
+
let whitelistPreset = null;
|
|
6355
7326
|
for (let i = 0; i < args.length; i++) {
|
|
6356
7327
|
switch (args[i]) {
|
|
6357
7328
|
case "--upstream":
|
|
@@ -6373,14 +7344,39 @@ async function cli(args) {
|
|
|
6373
7344
|
case "-t":
|
|
6374
7345
|
startupTimeoutMs = parseInt(args[++i] ?? "30000", 10);
|
|
6375
7346
|
break;
|
|
7347
|
+
case "--mode":
|
|
7348
|
+
case "-m":
|
|
7349
|
+
mode = args[++i] ?? "enforce";
|
|
7350
|
+
break;
|
|
7351
|
+
case "--whitelist":
|
|
7352
|
+
case "-w":
|
|
7353
|
+
whitelistPreset = args[++i] ?? null;
|
|
7354
|
+
break;
|
|
7355
|
+
default: {
|
|
7356
|
+
const modeMatch = args[i]?.match(/^--mode=(.+)$/);
|
|
7357
|
+
if (modeMatch) {
|
|
7358
|
+
mode = modeMatch[1];
|
|
7359
|
+
break;
|
|
7360
|
+
}
|
|
7361
|
+
const wlMatch = args[i]?.match(/^--whitelist=(.+)$/);
|
|
7362
|
+
if (wlMatch) {
|
|
7363
|
+
whitelistPreset = wlMatch[1];
|
|
7364
|
+
break;
|
|
7365
|
+
}
|
|
7366
|
+
}
|
|
6376
7367
|
}
|
|
6377
7368
|
}
|
|
6378
7369
|
if (!upstreamCmd) {
|
|
6379
7370
|
process.stderr.write(
|
|
6380
|
-
'Usage: sovr-mcp-proxy --upstream "command args..." [--
|
|
7371
|
+
'Usage: sovr-mcp-proxy --upstream "command args..." [--mode=exclusive|enforce|advisory|monitor] [--whitelist=preset|path] [--verbose]\n'
|
|
6381
7372
|
);
|
|
6382
7373
|
process.exit(1);
|
|
6383
7374
|
}
|
|
7375
|
+
if (!["exclusive", "enforce", "advisory", "monitor"].includes(mode)) {
|
|
7376
|
+
process.stderr.write(`[SOVR] Invalid mode: ${mode}. Must be: exclusive|enforce|advisory|monitor
|
|
7377
|
+
`);
|
|
7378
|
+
process.exit(1);
|
|
7379
|
+
}
|
|
6384
7380
|
let rules = DEFAULT_RULES2;
|
|
6385
7381
|
if (rulesFile) {
|
|
6386
7382
|
const fs = await import("fs");
|
|
@@ -6388,6 +7384,26 @@ async function cli(args) {
|
|
|
6388
7384
|
const parsed = JSON.parse(content);
|
|
6389
7385
|
rules = parsed.rules ?? parsed;
|
|
6390
7386
|
}
|
|
7387
|
+
const { WhitelistEngine: WhitelistEngine2, PRESETS: WL_PRESETS } = await Promise.resolve().then(() => (init_whitelistEngine(), whitelistEngine_exports));
|
|
7388
|
+
let whitelist = null;
|
|
7389
|
+
if (whitelistPreset) {
|
|
7390
|
+
const presetNames = Object.keys(WL_PRESETS);
|
|
7391
|
+
if (presetNames.includes(whitelistPreset)) {
|
|
7392
|
+
whitelist = new WhitelistEngine2(WL_PRESETS[whitelistPreset]);
|
|
7393
|
+
} else {
|
|
7394
|
+
try {
|
|
7395
|
+
const fs = await import("fs");
|
|
7396
|
+
const content = fs.readFileSync(whitelistPreset, "utf-8");
|
|
7397
|
+
const parsed = JSON.parse(content);
|
|
7398
|
+
whitelist = new WhitelistEngine2(parsed);
|
|
7399
|
+
} catch (err) {
|
|
7400
|
+
process.stderr.write(`[SOVR] Failed to load whitelist from ${whitelistPreset}: ${err.message}
|
|
7401
|
+
`);
|
|
7402
|
+
process.exit(1);
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
const validatedMode = parseMode2(mode);
|
|
6391
7407
|
const engine = new PolicyEngine2({
|
|
6392
7408
|
rules,
|
|
6393
7409
|
audit_log: true,
|
|
@@ -6400,6 +7416,24 @@ async function cli(args) {
|
|
|
6400
7416
|
}
|
|
6401
7417
|
}
|
|
6402
7418
|
});
|
|
7419
|
+
process.stderr.write(`
|
|
7420
|
+
`);
|
|
7421
|
+
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
|
|
7422
|
+
`);
|
|
7423
|
+
process.stderr.write(` \u2551 SOVR MCP Proxy v${PROXY_VERSION} \u2551
|
|
7424
|
+
`);
|
|
7425
|
+
process.stderr.write(` \u2551 Mode: ${mode.toUpperCase().padEnd(40)}\u2551
|
|
7426
|
+
`);
|
|
7427
|
+
if (whitelist) {
|
|
7428
|
+
process.stderr.write(` \u2551 Whitelist: ${(whitelistPreset ?? "custom").padEnd(35)}\u2551
|
|
7429
|
+
`);
|
|
7430
|
+
}
|
|
7431
|
+
process.stderr.write(` \u2551 Upstream: ${upstreamCmd.substring(0, 36).padEnd(36)}\u2551
|
|
7432
|
+
`);
|
|
7433
|
+
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
|
|
7434
|
+
`);
|
|
7435
|
+
process.stderr.write(`
|
|
7436
|
+
`);
|
|
6403
7437
|
const proxy = new McpProxy({
|
|
6404
7438
|
engine,
|
|
6405
7439
|
upstream: { command: upstreamCmd, args: upstreamArgs },
|
|
@@ -6416,8 +7450,37 @@ async function cli(args) {
|
|
|
6416
7450
|
`[ESCALATED] ${info.toolName}: ${info.decision.reason}
|
|
6417
7451
|
`
|
|
6418
7452
|
);
|
|
7453
|
+
},
|
|
7454
|
+
onIntercept: (info) => {
|
|
7455
|
+
if (info.arguments?.command && typeof info.arguments.command === "string") {
|
|
7456
|
+
const normalized = normalizeCommand(info.arguments.command);
|
|
7457
|
+
if (verbose) {
|
|
7458
|
+
process.stderr.write(`[SOVR] Normalized: ${summarizeNormalization(normalized)}
|
|
7459
|
+
`);
|
|
7460
|
+
if (normalized.suspicious) {
|
|
7461
|
+
process.stderr.write(`[SOVR] \u26A0 Suspicious: ${normalized.suspicion_reasons.join(", ")}
|
|
7462
|
+
`);
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
if (whitelist && info.arguments?.command && typeof info.arguments.command === "string") {
|
|
7467
|
+
const wlResult = whitelist.evaluate(info.arguments.command);
|
|
7468
|
+
if (!wlResult.allowed && (validatedMode === "enforce" || validatedMode === "exclusive")) {
|
|
7469
|
+
if (verbose) {
|
|
7470
|
+
process.stderr.write(`[SOVR] Whitelist DENIED: ${wlResult.reason}
|
|
7471
|
+
`);
|
|
7472
|
+
}
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
6419
7475
|
}
|
|
6420
7476
|
});
|
|
7477
|
+
if (validatedMode === "exclusive") {
|
|
7478
|
+
proxy.on("intercept", () => {
|
|
7479
|
+
if (verbose) {
|
|
7480
|
+
process.stderr.write("[SOVR] Exclusive mode: all tool calls routed through SOVR\n");
|
|
7481
|
+
}
|
|
7482
|
+
});
|
|
7483
|
+
}
|
|
6421
7484
|
await proxy.start();
|
|
6422
7485
|
}
|
|
6423
7486
|
var index_default = McpProxy;
|