@vybestack/llxprt-code-policy 0.10.0-nightly.260613.1adad3b34
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/dist/.last_build +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/config.d.ts +25 -0
- package/dist/src/config.js +119 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/confirmation-bus/index.d.ts +2 -0
- package/dist/src/confirmation-bus/index.js +3 -0
- package/dist/src/confirmation-bus/index.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.d.ts +29 -0
- package/dist/src/confirmation-bus/message-bus.js +143 -0
- package/dist/src/confirmation-bus/message-bus.js.map +1 -0
- package/dist/src/confirmation-bus/types.d.ts +180 -0
- package/dist/src/confirmation-bus/types.js +37 -0
- package/dist/src/confirmation-bus/types.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/policies/discovered.toml +9 -0
- package/dist/src/policies/read-only.toml +96 -0
- package/dist/src/policies/write.toml +101 -0
- package/dist/src/policies/yolo.toml +10 -0
- package/dist/src/policy-engine.d.ts +72 -0
- package/dist/src/policy-engine.js +236 -0
- package/dist/src/policy-engine.js.map +1 -0
- package/dist/src/stable-stringify.d.ts +29 -0
- package/dist/src/stable-stringify.js +137 -0
- package/dist/src/stable-stringify.js.map +1 -0
- package/dist/src/toml-loader.d.ts +48 -0
- package/dist/src/toml-loader.js +453 -0
- package/dist/src/toml-loader.js.map +1 -0
- package/dist/src/types.d.ts +80 -0
- package/dist/src/types.js +18 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/shell-utils.d.ts +30 -0
- package/dist/src/utils/shell-utils.js +118 -0
- package/dist/src/utils/shell-utils.js.map +1 -0
- package/dist/src/utils.d.ts +26 -0
- package/dist/src/utils.js +68 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Priority system for policy rules:
|
|
2
|
+
# - Higher priority numbers win over lower priority numbers
|
|
3
|
+
# - When multiple rules match, the highest priority rule is applied
|
|
4
|
+
# - Rules are evaluated in order of priority (highest first)
|
|
5
|
+
#
|
|
6
|
+
# Priority bands (tiers):
|
|
7
|
+
# - Default policies (TOML): 1 + priority/1000 (e.g., priority 100 → 1.100)
|
|
8
|
+
# - User policies (TOML): 2 + priority/1000 (e.g., priority 100 → 2.100)
|
|
9
|
+
# - Admin policies (TOML): 3 + priority/1000 (e.g., priority 100 → 3.100)
|
|
10
|
+
#
|
|
11
|
+
# This ensures Admin > User > Default hierarchy is always preserved,
|
|
12
|
+
# while allowing user-specified priorities to work within each tier.
|
|
13
|
+
#
|
|
14
|
+
# Settings-based and dynamic rules (all in user tier 2.x):
|
|
15
|
+
# 2.95: Tools that the user has selected as "Always Allow" in the interactive UI
|
|
16
|
+
# 2.9: MCP servers excluded list (security: persistent server blocks)
|
|
17
|
+
# 2.4: Command line flag --exclude-tools (explicit temporary blocks)
|
|
18
|
+
# 2.3: Command line flag --allowed-tools (explicit temporary allows)
|
|
19
|
+
# 2.2: MCP servers with trust=true (persistent trusted servers)
|
|
20
|
+
# 2.1: MCP servers allowed list (persistent general server allows)
|
|
21
|
+
#
|
|
22
|
+
# TOML policy priorities (before transformation):
|
|
23
|
+
# 10: Write tools default to ASK_USER (becomes 1.010 in default tier)
|
|
24
|
+
# 15: Auto-edit tool override (becomes 1.015 in default tier)
|
|
25
|
+
# 50: Read-only tools (becomes 1.050 in default tier)
|
|
26
|
+
# 999: YOLO mode allow-all (becomes 1.999 in default tier)
|
|
27
|
+
|
|
28
|
+
[[rule]]
|
|
29
|
+
toolName = "glob"
|
|
30
|
+
decision = "allow"
|
|
31
|
+
priority = 50
|
|
32
|
+
|
|
33
|
+
[[rule]]
|
|
34
|
+
toolName = "search_file_content"
|
|
35
|
+
decision = "allow"
|
|
36
|
+
priority = 50
|
|
37
|
+
|
|
38
|
+
[[rule]]
|
|
39
|
+
toolName = "list_directory"
|
|
40
|
+
decision = "allow"
|
|
41
|
+
priority = 50
|
|
42
|
+
|
|
43
|
+
[[rule]]
|
|
44
|
+
toolName = "read_file"
|
|
45
|
+
decision = "allow"
|
|
46
|
+
priority = 50
|
|
47
|
+
|
|
48
|
+
[[rule]]
|
|
49
|
+
toolName = "read_many_files"
|
|
50
|
+
decision = "allow"
|
|
51
|
+
priority = 50
|
|
52
|
+
|
|
53
|
+
[[rule]]
|
|
54
|
+
toolName = "read_line_range"
|
|
55
|
+
decision = "allow"
|
|
56
|
+
priority = 50
|
|
57
|
+
|
|
58
|
+
[[rule]]
|
|
59
|
+
toolName = "google_web_search"
|
|
60
|
+
decision = "allow"
|
|
61
|
+
priority = 50
|
|
62
|
+
|
|
63
|
+
[[rule]]
|
|
64
|
+
toolName = "SubagentInvocation"
|
|
65
|
+
decision = "allow"
|
|
66
|
+
priority = 50
|
|
67
|
+
|
|
68
|
+
[[rule]]
|
|
69
|
+
toolName = "exa_web_search"
|
|
70
|
+
decision = "allow"
|
|
71
|
+
priority = 50
|
|
72
|
+
|
|
73
|
+
[[rule]]
|
|
74
|
+
toolName = "task"
|
|
75
|
+
decision = "allow"
|
|
76
|
+
priority = 50
|
|
77
|
+
|
|
78
|
+
[[rule]]
|
|
79
|
+
toolName = "todo_read"
|
|
80
|
+
decision = "allow"
|
|
81
|
+
priority = 50
|
|
82
|
+
|
|
83
|
+
[[rule]]
|
|
84
|
+
toolName = "todo_write"
|
|
85
|
+
decision = "allow"
|
|
86
|
+
priority = 50
|
|
87
|
+
|
|
88
|
+
[[rule]]
|
|
89
|
+
toolName = "todo_pause"
|
|
90
|
+
decision = "allow"
|
|
91
|
+
priority = 50
|
|
92
|
+
|
|
93
|
+
[[rule]]
|
|
94
|
+
toolName = "list_subagents"
|
|
95
|
+
decision = "allow"
|
|
96
|
+
priority = 50
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Priority system for policy rules:
|
|
2
|
+
# - Higher priority numbers win over lower priority numbers
|
|
3
|
+
# - When multiple rules match, the highest priority rule is applied
|
|
4
|
+
# - Rules are evaluated in order of priority (highest first)
|
|
5
|
+
#
|
|
6
|
+
# Priority bands (tiers):
|
|
7
|
+
# - Default policies (TOML): 1 + priority/1000 (e.g., priority 100 → 1.100)
|
|
8
|
+
# - User policies (TOML): 2 + priority/1000 (e.g., priority 100 → 2.100)
|
|
9
|
+
# - Admin policies (TOML): 3 + priority/1000 (e.g., priority 100 → 3.100)
|
|
10
|
+
#
|
|
11
|
+
# This ensures Admin > User > Default hierarchy is always preserved,
|
|
12
|
+
# while allowing user-specified priorities to work within each tier.
|
|
13
|
+
#
|
|
14
|
+
# Settings-based and dynamic rules (all in user tier 2.x):
|
|
15
|
+
# 2.95: Tools that the user has selected as "Always Allow" in the interactive UI
|
|
16
|
+
# 2.9: MCP servers excluded list (security: persistent server blocks)
|
|
17
|
+
# 2.4: Command line flag --exclude-tools (explicit temporary blocks)
|
|
18
|
+
# 2.3: Command line flag --allowed-tools (explicit temporary allows)
|
|
19
|
+
# 2.2: MCP servers with trust=true (persistent trusted servers)
|
|
20
|
+
# 2.1: MCP servers allowed list (persistent general server allows)
|
|
21
|
+
#
|
|
22
|
+
# TOML policy priorities (before transformation):
|
|
23
|
+
# 10: Write tools default to ASK_USER (becomes 1.010 in default tier)
|
|
24
|
+
# 15: Auto-edit tool override (becomes 1.015 in default tier)
|
|
25
|
+
# 50: Read-only tools (becomes 1.050 in default tier)
|
|
26
|
+
# 999: YOLO mode allow-all (becomes 1.999 in default tier)
|
|
27
|
+
|
|
28
|
+
[[rule]]
|
|
29
|
+
toolName = "replace"
|
|
30
|
+
decision = "ask_user"
|
|
31
|
+
priority = 10
|
|
32
|
+
|
|
33
|
+
[[rule]]
|
|
34
|
+
toolName = "replace"
|
|
35
|
+
decision = "allow"
|
|
36
|
+
priority = 15
|
|
37
|
+
modes = ["autoEdit"]
|
|
38
|
+
|
|
39
|
+
[[rule]]
|
|
40
|
+
toolName = "save_memory"
|
|
41
|
+
decision = "ask_user"
|
|
42
|
+
priority = 10
|
|
43
|
+
|
|
44
|
+
[[rule]]
|
|
45
|
+
toolName = "run_shell_command"
|
|
46
|
+
decision = "ask_user"
|
|
47
|
+
priority = 10
|
|
48
|
+
|
|
49
|
+
[[rule]]
|
|
50
|
+
toolName = "write_file"
|
|
51
|
+
decision = "ask_user"
|
|
52
|
+
priority = 10
|
|
53
|
+
|
|
54
|
+
[[rule]]
|
|
55
|
+
toolName = "activate_skill"
|
|
56
|
+
decision = "ask_user"
|
|
57
|
+
priority = 10
|
|
58
|
+
|
|
59
|
+
[[rule]]
|
|
60
|
+
toolName = "write_file"
|
|
61
|
+
decision = "allow"
|
|
62
|
+
priority = 15
|
|
63
|
+
modes = ["autoEdit"]
|
|
64
|
+
|
|
65
|
+
[[rule]]
|
|
66
|
+
toolName = "web_fetch"
|
|
67
|
+
decision = "ask_user"
|
|
68
|
+
priority = 10
|
|
69
|
+
|
|
70
|
+
[[rule]]
|
|
71
|
+
toolName = "insert_at_line"
|
|
72
|
+
decision = "ask_user"
|
|
73
|
+
priority = 10
|
|
74
|
+
|
|
75
|
+
[[rule]]
|
|
76
|
+
toolName = "insert_at_line"
|
|
77
|
+
decision = "allow"
|
|
78
|
+
priority = 15
|
|
79
|
+
modes = ["autoEdit"]
|
|
80
|
+
|
|
81
|
+
[[rule]]
|
|
82
|
+
toolName = "delete_line_range"
|
|
83
|
+
decision = "ask_user"
|
|
84
|
+
priority = 10
|
|
85
|
+
|
|
86
|
+
[[rule]]
|
|
87
|
+
toolName = "delete_line_range"
|
|
88
|
+
decision = "allow"
|
|
89
|
+
priority = 15
|
|
90
|
+
modes = ["autoEdit"]
|
|
91
|
+
|
|
92
|
+
[[rule]]
|
|
93
|
+
toolName = "apply_patch"
|
|
94
|
+
decision = "ask_user"
|
|
95
|
+
priority = 10
|
|
96
|
+
|
|
97
|
+
[[rule]]
|
|
98
|
+
toolName = "apply_patch"
|
|
99
|
+
decision = "allow"
|
|
100
|
+
priority = 15
|
|
101
|
+
modes = ["autoEdit"]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# YOLO mode policy - allow everything
|
|
2
|
+
# Priority band: 1.999 (Tier 1 - Default, just below user settings)
|
|
3
|
+
# WARNING: This disables all safety checks. Use only in trusted environments.
|
|
4
|
+
|
|
5
|
+
[[rule]]
|
|
6
|
+
# No toolName specified = wildcard (matches all tools)
|
|
7
|
+
decision = "allow"
|
|
8
|
+
allow_redirection = true
|
|
9
|
+
priority = 999
|
|
10
|
+
modes = ["yolo"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { PolicyDecision, type PolicyEngineConfig, type PolicyRule } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* PolicyEngine evaluates tool execution requests against configured rules.
|
|
4
|
+
* Rules are matched in priority order, with the highest priority rule winning.
|
|
5
|
+
*/
|
|
6
|
+
export declare class PolicyEngine {
|
|
7
|
+
private readonly rules;
|
|
8
|
+
private readonly defaultDecision;
|
|
9
|
+
private readonly nonInteractive;
|
|
10
|
+
constructor(config?: PolicyEngineConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Evaluates a tool execution request and returns a policy decision.
|
|
13
|
+
*
|
|
14
|
+
* @param toolName - The name of the tool being executed
|
|
15
|
+
* @param args - The arguments passed to the tool
|
|
16
|
+
* @param serverName - Optional MCP server name (for spoofing prevention)
|
|
17
|
+
* @returns PolicyDecision (ALLOW, DENY, or ASK_USER)
|
|
18
|
+
*/
|
|
19
|
+
evaluate(toolName: string, args: Record<string, unknown>, serverName?: string): PolicyDecision;
|
|
20
|
+
private evaluateMatchingRule;
|
|
21
|
+
/**
|
|
22
|
+
* Evaluates shell command sub-commands and redirections for an ALLOW rule.
|
|
23
|
+
* Returns a PolicyDecision if the shell-specific logic resolves, or undefined
|
|
24
|
+
* to fall through to normal decision handling.
|
|
25
|
+
*/
|
|
26
|
+
private evaluateShellCommand;
|
|
27
|
+
private evaluateCompoundCommand;
|
|
28
|
+
private evaluateDefault;
|
|
29
|
+
private validateDefaultShellSubcommands;
|
|
30
|
+
/**
|
|
31
|
+
* Finds the highest priority rule matching the tool and args.
|
|
32
|
+
*
|
|
33
|
+
* @param toolName - The name of the tool
|
|
34
|
+
* @param args - The tool arguments
|
|
35
|
+
* @returns The matching rule, or undefined if none match
|
|
36
|
+
*/
|
|
37
|
+
private findMatchingRule;
|
|
38
|
+
/**
|
|
39
|
+
* Validates that a tool name matches its claimed server name.
|
|
40
|
+
* Returns null if spoofing is detected, otherwise returns the tool name.
|
|
41
|
+
*
|
|
42
|
+
* @param toolName - The tool name (may include server prefix)
|
|
43
|
+
* @param serverName - The claimed server name
|
|
44
|
+
* @returns The validated tool name, or null if spoofing detected
|
|
45
|
+
*/
|
|
46
|
+
private validateServerName;
|
|
47
|
+
/**
|
|
48
|
+
* Returns all configured rules (for debugging/inspection).
|
|
49
|
+
*
|
|
50
|
+
* @returns Array of policy rules
|
|
51
|
+
*/
|
|
52
|
+
getRules(): readonly PolicyRule[];
|
|
53
|
+
/**
|
|
54
|
+
* Returns the default decision used when no rules match.
|
|
55
|
+
*
|
|
56
|
+
* @returns PolicyDecision
|
|
57
|
+
*/
|
|
58
|
+
getDefaultDecision(): PolicyDecision;
|
|
59
|
+
/**
|
|
60
|
+
* Returns whether the engine is in non-interactive mode.
|
|
61
|
+
*
|
|
62
|
+
* @returns boolean
|
|
63
|
+
*/
|
|
64
|
+
isNonInteractive(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Adds a new rule to the policy engine at runtime.
|
|
67
|
+
* The rule is inserted into the sorted rules list based on its priority.
|
|
68
|
+
*
|
|
69
|
+
* @param rule - The policy rule to add
|
|
70
|
+
*/
|
|
71
|
+
addRule(rule: PolicyRule): void;
|
|
72
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { PolicyDecision, } from './types.js';
|
|
2
|
+
import { stableStringify } from './stable-stringify.js';
|
|
3
|
+
import { SHELL_TOOL_NAMES, splitCommands, hasRedirection, } from './utils/shell-utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* PolicyEngine evaluates tool execution requests against configured rules.
|
|
6
|
+
* Rules are matched in priority order, with the highest priority rule winning.
|
|
7
|
+
*/
|
|
8
|
+
export class PolicyEngine {
|
|
9
|
+
rules;
|
|
10
|
+
defaultDecision;
|
|
11
|
+
nonInteractive;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.rules = config?.rules ?? [];
|
|
14
|
+
this.defaultDecision = config?.defaultDecision ?? PolicyDecision.ASK_USER;
|
|
15
|
+
this.nonInteractive = config?.nonInteractive ?? false;
|
|
16
|
+
// Sort rules by priority (highest first)
|
|
17
|
+
this.rules.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Evaluates a tool execution request and returns a policy decision.
|
|
21
|
+
*
|
|
22
|
+
* @param toolName - The name of the tool being executed
|
|
23
|
+
* @param args - The arguments passed to the tool
|
|
24
|
+
* @param serverName - Optional MCP server name (for spoofing prevention)
|
|
25
|
+
* @returns PolicyDecision (ALLOW, DENY, or ASK_USER)
|
|
26
|
+
*/
|
|
27
|
+
evaluate(toolName, args, serverName) {
|
|
28
|
+
// Validate serverName to prevent spoofing
|
|
29
|
+
if (serverName) {
|
|
30
|
+
const validatedToolName = this.validateServerName(toolName, serverName);
|
|
31
|
+
if (validatedToolName === null) {
|
|
32
|
+
return PolicyDecision.DENY;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Find the highest priority matching rule
|
|
36
|
+
const matchingRule = this.findMatchingRule(toolName, args);
|
|
37
|
+
if (matchingRule) {
|
|
38
|
+
return this.evaluateMatchingRule(toolName, args, serverName, matchingRule);
|
|
39
|
+
}
|
|
40
|
+
return this.evaluateDefault(toolName, args, serverName);
|
|
41
|
+
}
|
|
42
|
+
evaluateMatchingRule(toolName, args, serverName, matchingRule) {
|
|
43
|
+
const decision = matchingRule.decision;
|
|
44
|
+
// Special handling for shell commands: validate sub-commands if ALLOW rule
|
|
45
|
+
if (toolName &&
|
|
46
|
+
SHELL_TOOL_NAMES.includes(toolName) &&
|
|
47
|
+
decision === PolicyDecision.ALLOW) {
|
|
48
|
+
const command = args.command;
|
|
49
|
+
if (command) {
|
|
50
|
+
const shellResult = this.evaluateShellCommand(toolName, args, serverName, command, matchingRule);
|
|
51
|
+
if (shellResult !== undefined) {
|
|
52
|
+
return shellResult;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// In non-interactive mode, ASK_USER becomes DENY
|
|
57
|
+
if (this.nonInteractive && decision === PolicyDecision.ASK_USER) {
|
|
58
|
+
return PolicyDecision.DENY;
|
|
59
|
+
}
|
|
60
|
+
return decision;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Evaluates shell command sub-commands and redirections for an ALLOW rule.
|
|
64
|
+
* Returns a PolicyDecision if the shell-specific logic resolves, or undefined
|
|
65
|
+
* to fall through to normal decision handling.
|
|
66
|
+
*/
|
|
67
|
+
evaluateShellCommand(toolName, args, serverName, command, matchingRule) {
|
|
68
|
+
const subCommands = splitCommands(command);
|
|
69
|
+
// Parse failure: empty array for non-empty command → fail-safe to ASK_USER
|
|
70
|
+
if (subCommands.length === 0 && command.trim().length > 0) {
|
|
71
|
+
return this.nonInteractive
|
|
72
|
+
? PolicyDecision.DENY
|
|
73
|
+
: PolicyDecision.ASK_USER;
|
|
74
|
+
}
|
|
75
|
+
// Compound command: recursively validate each sub-command
|
|
76
|
+
if (subCommands.length > 1) {
|
|
77
|
+
return this.evaluateCompoundCommand(toolName, args, serverName, command, subCommands);
|
|
78
|
+
}
|
|
79
|
+
// Check for redirections in allowed commands
|
|
80
|
+
if (matchingRule.allowRedirection !== true && hasRedirection(command)) {
|
|
81
|
+
return this.nonInteractive
|
|
82
|
+
? PolicyDecision.DENY
|
|
83
|
+
: PolicyDecision.ASK_USER;
|
|
84
|
+
}
|
|
85
|
+
// Single command: rule match is valid, fall through to normal return
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
evaluateCompoundCommand(toolName, args, serverName, command, subCommands) {
|
|
89
|
+
let aggregateDecision = PolicyDecision.ALLOW;
|
|
90
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
91
|
+
for (const rawSubCmd of subCommands) {
|
|
92
|
+
const subCmd = rawSubCmd.trim();
|
|
93
|
+
// Prevent infinite recursion
|
|
94
|
+
if (subCmd === command)
|
|
95
|
+
continue;
|
|
96
|
+
// Preserve dir_path from original args
|
|
97
|
+
const subResult = this.evaluate(toolName, { ...args, command: subCmd }, serverName);
|
|
98
|
+
if (subResult === PolicyDecision.DENY) {
|
|
99
|
+
aggregateDecision = PolicyDecision.DENY;
|
|
100
|
+
break; // Fail fast: DENY overrides everything
|
|
101
|
+
}
|
|
102
|
+
else if (subResult === PolicyDecision.ASK_USER) {
|
|
103
|
+
aggregateDecision = PolicyDecision.ASK_USER;
|
|
104
|
+
// Continue checking for DENY (don't short-circuit)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return this.nonInteractive && aggregateDecision === PolicyDecision.ASK_USER
|
|
108
|
+
? PolicyDecision.DENY
|
|
109
|
+
: aggregateDecision;
|
|
110
|
+
}
|
|
111
|
+
evaluateDefault(toolName, args, serverName) {
|
|
112
|
+
let defaultResult = this.defaultDecision;
|
|
113
|
+
// Security: even with no matching rule, still validate shell subcommands
|
|
114
|
+
// to catch compound commands like "git commit && git push" where a subcommand
|
|
115
|
+
// may match a DENY rule
|
|
116
|
+
if (toolName &&
|
|
117
|
+
SHELL_TOOL_NAMES.includes(toolName) &&
|
|
118
|
+
defaultResult !== PolicyDecision.DENY) {
|
|
119
|
+
defaultResult = this.validateDefaultShellSubcommands(toolName, args, serverName, defaultResult);
|
|
120
|
+
}
|
|
121
|
+
if (this.nonInteractive && defaultResult === PolicyDecision.ASK_USER) {
|
|
122
|
+
return PolicyDecision.DENY;
|
|
123
|
+
}
|
|
124
|
+
return defaultResult;
|
|
125
|
+
}
|
|
126
|
+
validateDefaultShellSubcommands(toolName, args, serverName, currentResult) {
|
|
127
|
+
const command = args.command;
|
|
128
|
+
if (!command) {
|
|
129
|
+
return currentResult;
|
|
130
|
+
}
|
|
131
|
+
const subCommands = splitCommands(command);
|
|
132
|
+
if (subCommands.length <= 1) {
|
|
133
|
+
return currentResult;
|
|
134
|
+
}
|
|
135
|
+
let result = currentResult;
|
|
136
|
+
for (const rawSubCmd of subCommands) {
|
|
137
|
+
const subCmd = rawSubCmd.trim();
|
|
138
|
+
if (subCmd === command)
|
|
139
|
+
continue;
|
|
140
|
+
const subResult = this.evaluate(toolName, { ...args, command: subCmd }, serverName);
|
|
141
|
+
if (subResult === PolicyDecision.DENY) {
|
|
142
|
+
return PolicyDecision.DENY;
|
|
143
|
+
}
|
|
144
|
+
else if (subResult === PolicyDecision.ASK_USER) {
|
|
145
|
+
result = PolicyDecision.ASK_USER;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Finds the highest priority rule matching the tool and args.
|
|
152
|
+
*
|
|
153
|
+
* @param toolName - The name of the tool
|
|
154
|
+
* @param args - The tool arguments
|
|
155
|
+
* @returns The matching rule, or undefined if none match
|
|
156
|
+
*/
|
|
157
|
+
findMatchingRule(toolName, args) {
|
|
158
|
+
const argsString = stableStringify(args);
|
|
159
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
160
|
+
for (const rule of this.rules) {
|
|
161
|
+
// Check tool name match
|
|
162
|
+
const toolMatches = !rule.toolName || rule.toolName === toolName;
|
|
163
|
+
if (!toolMatches) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
// Check args pattern match
|
|
167
|
+
const argsMatch = !rule.argsPattern || rule.argsPattern.test(argsString);
|
|
168
|
+
if (!argsMatch) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Both match - return this rule
|
|
172
|
+
return rule;
|
|
173
|
+
}
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Validates that a tool name matches its claimed server name.
|
|
178
|
+
* Returns null if spoofing is detected, otherwise returns the tool name.
|
|
179
|
+
*
|
|
180
|
+
* @param toolName - The tool name (may include server prefix)
|
|
181
|
+
* @param serverName - The claimed server name
|
|
182
|
+
* @returns The validated tool name, or null if spoofing detected
|
|
183
|
+
*/
|
|
184
|
+
validateServerName(toolName, serverName) {
|
|
185
|
+
// For MCP tools, expect format: "serverName__toolName"
|
|
186
|
+
const expectedPrefix = `${serverName}__`;
|
|
187
|
+
if (toolName.startsWith(expectedPrefix)) {
|
|
188
|
+
return toolName;
|
|
189
|
+
}
|
|
190
|
+
// If tool name doesn't have the expected prefix, check if it's a non-MCP tool
|
|
191
|
+
// Non-MCP tools don't have a server prefix, so if a serverName is provided
|
|
192
|
+
// but the tool doesn't have the prefix, it's likely spoofing
|
|
193
|
+
if (!toolName.includes('__')) {
|
|
194
|
+
// This is a built-in tool, serverName should not be set
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// Tool has a different server prefix - spoofing attempt
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Returns all configured rules (for debugging/inspection).
|
|
202
|
+
*
|
|
203
|
+
* @returns Array of policy rules
|
|
204
|
+
*/
|
|
205
|
+
getRules() {
|
|
206
|
+
return [...this.rules];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Returns the default decision used when no rules match.
|
|
210
|
+
*
|
|
211
|
+
* @returns PolicyDecision
|
|
212
|
+
*/
|
|
213
|
+
getDefaultDecision() {
|
|
214
|
+
return this.defaultDecision;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Returns whether the engine is in non-interactive mode.
|
|
218
|
+
*
|
|
219
|
+
* @returns boolean
|
|
220
|
+
*/
|
|
221
|
+
isNonInteractive() {
|
|
222
|
+
return this.nonInteractive;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Adds a new rule to the policy engine at runtime.
|
|
226
|
+
* The rule is inserted into the sorted rules list based on its priority.
|
|
227
|
+
*
|
|
228
|
+
* @param rule - The policy rule to add
|
|
229
|
+
*/
|
|
230
|
+
addRule(rule) {
|
|
231
|
+
this.rules.push(rule);
|
|
232
|
+
// Re-sort rules by priority (highest first)
|
|
233
|
+
this.rules.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=policy-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-engine.js","sourceRoot":"","sources":["../../src/policy-engine.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,GACf,MAAM,wBAAwB,CAAC;AAEhC;;;GAGG;AACH,MAAM,OAAO,YAAY;IACN,KAAK,CAAe;IACpB,eAAe,CAAiB;IAChC,cAAc,CAAU;IAEzC,YAAY,MAA2B;QACrC,IAAI,CAAC,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,MAAM,EAAE,eAAe,IAAI,cAAc,CAAC,QAAQ,CAAC;QAC1E,IAAI,CAAC,cAAc,GAAG,MAAM,EAAE,cAAc,IAAI,KAAK,CAAC;QAEtD,yCAAyC;QACzC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CACN,QAAgB,EAChB,IAA6B,EAC7B,UAAmB;QAEnB,0CAA0C;QAC1C,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,iBAAiB,GAAG,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YACxE,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAC/B,OAAO,cAAc,CAAC,IAAI,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,0CAA0C;QAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAE3D,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO,IAAI,CAAC,oBAAoB,CAC9B,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,YAAY,CACb,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAC1D,CAAC;IAEO,oBAAoB,CAC1B,QAAgB,EAChB,IAA6B,EAC7B,UAA8B,EAC9B,YAAwB;QAExB,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC;QAEvC,2EAA2E;QAC3E,IACE,QAAQ;YACR,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnC,QAAQ,KAAK,cAAc,CAAC,KAAK,EACjC,CAAC;YACD,MAAM,OAAO,GAAI,IAA6B,CAAC,OAAO,CAAC;YACvD,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,WAAW,GAAG,IAAI,CAAC,oBAAoB,CAC3C,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,OAAO,EACP,YAAY,CACb,CAAC;gBACF,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;oBAC9B,OAAO,WAAW,CAAC;gBACrB,CAAC;YACH,CAAC;QACH,CAAC;QAED,iDAAiD;QACjD,IAAI,IAAI,CAAC,cAAc,IAAI,QAAQ,KAAK,cAAc,CAAC,QAAQ,EAAE,CAAC;YAChE,OAAO,cAAc,CAAC,IAAI,CAAC;QAC7B,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;OAIG;IACK,oBAAoB,CAC1B,QAAgB,EAChB,IAA6B,EAC7B,UAA8B,EAC9B,OAAe,EACf,YAAwB;QAExB,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QAE3C,2EAA2E;QAC3E,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,OAAO,IAAI,CAAC,cAAc;gBACxB,CAAC,CAAC,cAAc,CAAC,IAAI;gBACrB,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC;QAC9B,CAAC;QAED,0DAA0D;QAC1D,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC,uBAAuB,CACjC,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,OAAO,EACP,WAAW,CACZ,CAAC;QACJ,CAAC;QAED,6CAA6C;QAC7C,IAAI,YAAY,CAAC,gBAAgB,KAAK,IAAI,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YACtE,OAAO,IAAI,CAAC,cAAc;gBACxB,CAAC,CAAC,cAAc,CAAC,IAAI;gBACrB,CAAC,CAAC,cAAc,CAAC,QAAQ,CAAC;QAC9B,CAAC;QAED,qEAAqE;QACrE,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,uBAAuB,CAC7B,QAAgB,EAChB,IAA6B,EAC7B,UAA8B,EAC9B,OAAe,EACf,WAAqB;QAErB,IAAI,iBAAiB,GAAG,cAAc,CAAC,KAAK,CAAC;QAE7C,6KAA6K;QAC7K,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;YAChC,6BAA6B;YAC7B,IAAI,MAAM,KAAK,OAAO;gBAAE,SAAS;YAEjC,uCAAuC;YACvC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAC7B,QAAQ,EACR,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAC5B,UAAU,CACX,CAAC;YAEF,IAAI,SAAS,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;gBACtC,iBAAiB,GAAG,cAAc,CAAC,IAAI,CAAC;gBACxC,MAAM,CAAC,uCAAuC;YAChD,CAAC;iBAAM,IAAI,SAAS,KAAK,cAAc,CAAC,QAAQ,EAAE,CAAC;gBACjD,iBAAiB,GAAG,cAAc,CAAC,QAAQ,CAAC;gBAC5C,mDAAmD;YACrD,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC,cAAc,IAAI,iBAAiB,KAAK,cAAc,CAAC,QAAQ;YACzE,CAAC,CAAC,cAAc,CAAC,IAAI;YACrB,CAAC,CAAC,iBAAiB,CAAC;IACxB,CAAC;IAEO,eAAe,CACrB,QAAgB,EAChB,IAA6B,EAC7B,UAA8B;QAE9B,IAAI,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC;QAEzC,yEAAyE;QACzE,8EAA8E;QAC9E,wBAAwB;QACxB,IACE,QAAQ;YACR,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnC,aAAa,KAAK,cAAc,CAAC,IAAI,EACrC,CAAC;YACD,aAAa,GAAG,IAAI,CAAC,+BAA+B,CAClD,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,aAAa,CACd,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,IAAI,aAAa,KAAK,cAAc,CAAC,QAAQ,EAAE,CAAC;YACrE,OAAO,cAAc,CAAC,IAAI,CAAC;QAC7B,CAAC;QAED,OAAO,aAAa,CAAC;IACvB,CAAC;IAEO,+BAA+B,CACrC,QAAgB,EAChB,IAA6B,EAC7B,UAA8B,EAC9B,aAA6B;QAE7B,MAAM,OAAO,GAAI,IAA6B,CAAC,OAAO,CAAC;QACvD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QAC3C,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,IAAI,MAAM,GAAG,aAAa,CAAC;QAC3B,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,MAAM,KAAK,OAAO;gBAAE,SAAS;YAEjC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAC7B,QAAQ,EACR,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,EAC5B,UAAU,CACX,CAAC;YAEF,IAAI,SAAS,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;gBACtC,OAAO,cAAc,CAAC,IAAI,CAAC;YAC7B,CAAC;iBAAM,IAAI,SAAS,KAAK,cAAc,CAAC,QAAQ,EAAE,CAAC;gBACjD,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC;YACnC,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;;;;;OAMG;IACK,gBAAgB,CACtB,QAAgB,EAChB,IAA6B;QAE7B,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;QAEzC,6KAA6K;QAC7K,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,wBAAwB;YACxB,MAAM,WAAW,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC;YACjE,IAAI,CAAC,WAAW,EAAE,CAAC;gBACjB,SAAS;YACX,CAAC;YAED,2BAA2B;YAC3B,MAAM,SAAS,GAAG,CAAC,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzE,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS;YACX,CAAC;YAED,gCAAgC;YAChC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;;OAOG;IACK,kBAAkB,CACxB,QAAgB,EAChB,UAAkB;QAElB,uDAAuD;QACvD,MAAM,cAAc,GAAG,GAAG,UAAU,IAAI,CAAC;QAEzC,IAAI,QAAQ,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YACxC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,8EAA8E;QAC9E,2EAA2E;QAC3E,6DAA6D;QAC7D,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,wDAAwD;YACxD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,wDAAwD;QACxD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACH,kBAAkB;QAChB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,OAAO,CAAC,IAAgB;QACtB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,4CAA4C;QAC5C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides deterministic JSON stringification for pattern matching in policy rules.
|
|
3
|
+
* Ensures consistent ordering of object keys and handling of special values.
|
|
4
|
+
*/
|
|
5
|
+
type JSONValue = string | number | boolean | null | JSONValue[] | {
|
|
6
|
+
[key: string]: JSONValue;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Deterministically stringifies a value for use in pattern matching.
|
|
10
|
+
* - Object keys are sorted alphabetically
|
|
11
|
+
* - Arrays maintain their order
|
|
12
|
+
* - undefined values are omitted
|
|
13
|
+
* - Functions and symbols are converted to null
|
|
14
|
+
* - Circular references throw an error
|
|
15
|
+
*
|
|
16
|
+
* @param value - The value to stringify
|
|
17
|
+
* @param space - Optional spacing for readability (default: none)
|
|
18
|
+
* @returns Deterministic JSON string
|
|
19
|
+
*/
|
|
20
|
+
export declare function stableStringify(value: unknown, space?: string | number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Parses a stable-stringified JSON string back into a value.
|
|
23
|
+
* This is just a wrapper around JSON.parse for consistency.
|
|
24
|
+
*
|
|
25
|
+
* @param text - The JSON string to parse
|
|
26
|
+
* @returns Parsed value
|
|
27
|
+
*/
|
|
28
|
+
export declare function stableParse(text: string): JSONValue;
|
|
29
|
+
export {};
|