@dsh-cc/permission-rules 0.5.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 +201 -0
- package/README.i18n.yaml +6 -0
- package/README.md +86 -0
- package/README.zh.md +86 -0
- package/lib/approval-listener.d.ts +50 -0
- package/lib/approval-listener.d.ts.map +1 -0
- package/lib/approval-listener.js +58 -0
- package/lib/approval-listener.js.map +1 -0
- package/lib/auto-stage.d.ts +138 -0
- package/lib/auto-stage.d.ts.map +1 -0
- package/lib/auto-stage.js +284 -0
- package/lib/auto-stage.js.map +1 -0
- package/lib/classifier.d.ts +57 -0
- package/lib/classifier.d.ts.map +1 -0
- package/lib/classifier.js +129 -0
- package/lib/classifier.js.map +1 -0
- package/lib/decide.d.ts +80 -0
- package/lib/decide.d.ts.map +1 -0
- package/lib/decide.js +127 -0
- package/lib/decide.js.map +1 -0
- package/lib/domain.d.ts +46 -0
- package/lib/domain.d.ts.map +1 -0
- package/lib/domain.js +103 -0
- package/lib/domain.js.map +1 -0
- package/lib/evaluate.d.ts +32 -0
- package/lib/evaluate.d.ts.map +1 -0
- package/lib/evaluate.js +176 -0
- package/lib/evaluate.js.map +1 -0
- package/lib/index.d.ts +123 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +380 -0
- package/lib/index.js.map +1 -0
- package/lib/invariant.d.ts +28 -0
- package/lib/invariant.d.ts.map +1 -0
- package/lib/invariant.js +54 -0
- package/lib/invariant.js.map +1 -0
- package/lib/llm-classifier.d.ts +107 -0
- package/lib/llm-classifier.d.ts.map +1 -0
- package/lib/llm-classifier.js +231 -0
- package/lib/llm-classifier.js.map +1 -0
- package/lib/matchers.d.ts +18 -0
- package/lib/matchers.d.ts.map +1 -0
- package/lib/matchers.js +43 -0
- package/lib/matchers.js.map +1 -0
- package/lib/mode.d.ts +91 -0
- package/lib/mode.d.ts.map +1 -0
- package/lib/mode.js +133 -0
- package/lib/mode.js.map +1 -0
- package/lib/parser.d.ts +91 -0
- package/lib/parser.d.ts.map +1 -0
- package/lib/parser.js +282 -0
- package/lib/parser.js.map +1 -0
- package/lib/session-allowlist.d.ts +76 -0
- package/lib/session-allowlist.d.ts.map +1 -0
- package/lib/session-allowlist.js +122 -0
- package/lib/session-allowlist.js.map +1 -0
- package/lib/settings-schema.d.ts +99 -0
- package/lib/settings-schema.d.ts.map +1 -0
- package/lib/settings-schema.js +64 -0
- package/lib/settings-schema.js.map +1 -0
- package/lib/types.d.ts +150 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +33 -0
- package/lib/types.js.map +1 -0
- package/package.json +71 -0
package/lib/decide.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decision waterfall for one tool call, extracted from the service so the
|
|
3
|
+
* engine core stays modular. Pure functions over a structural dependency face
|
|
4
|
+
* (`DecideDeps`) that `PermissionRulesService` supplies in its constructor.
|
|
5
|
+
*
|
|
6
|
+
* Stage order: the risk-classifier escalation runs first (a hard-deny HIGH in
|
|
7
|
+
* every mode; an ask MEDIUM outside bypassPermissions, with session-scoped
|
|
8
|
+
* grants overriding the ask), then the normal mode-aware waterfall proceeds.
|
|
9
|
+
* Under `auto`, a classifier-LOW call whose waterfall decision is `ask` is
|
|
10
|
+
* auto-allowed (the classifier proxies the prompt); MEDIUM/HIGH already
|
|
11
|
+
* returned above.
|
|
12
|
+
*
|
|
13
|
+
* @module @dsh-cc/permission-rules/decide
|
|
14
|
+
*/
|
|
15
|
+
import { evaluatePermission } from "./evaluate.js";
|
|
16
|
+
import { assessBashCommand, assessFilePath } from "./classifier.js";
|
|
17
|
+
import { isBashToolName, subjectOf } from "./matchers.js";
|
|
18
|
+
import { foldPlanMode } from '@deepseek-ai/dsh-plan-mode';
|
|
19
|
+
import { foldPermissionMode } from "./mode.js";
|
|
20
|
+
/**
|
|
21
|
+
* The effective mode for one call: plan overlays, else the session override.
|
|
22
|
+
*/
|
|
23
|
+
function effectiveMode(deps, exec) {
|
|
24
|
+
const agent = exec.agent;
|
|
25
|
+
if (agent !== undefined && foldPlanMode(agent.session.events))
|
|
26
|
+
return 'plan';
|
|
27
|
+
const recorded = agent === undefined ? undefined : foldPermissionMode(agent.session.events);
|
|
28
|
+
return recorded ?? deps.defaultMode();
|
|
29
|
+
}
|
|
30
|
+
/** Whether a call is sandboxed bash for the whole-tool-ask exemption. */
|
|
31
|
+
function sandboxedBash(deps, exec) {
|
|
32
|
+
if (!deps.exemptSandboxedBashFromToolAsk)
|
|
33
|
+
return false;
|
|
34
|
+
if (!isBashToolName(exec.name, deps.bashToolName))
|
|
35
|
+
return false;
|
|
36
|
+
const mode = deps.shellMode();
|
|
37
|
+
return mode !== undefined && mode !== 'danger-full-access';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Classify the risk of one call for the escalation stage. Bash-like tools
|
|
41
|
+
* classify their command; file-edit tools classify their target path; other
|
|
42
|
+
* tools are LOW. Skipped entirely when `classifierEnabled` is false.
|
|
43
|
+
*/
|
|
44
|
+
function classify(deps, exec) {
|
|
45
|
+
if (!deps.classifierEnabled)
|
|
46
|
+
return { level: 'LOW', reasons: [] };
|
|
47
|
+
const args = exec.arguments;
|
|
48
|
+
const session = exec.agent?.session;
|
|
49
|
+
if (isBashToolName(exec.name, deps.bashToolName) && typeof args.command === 'string') {
|
|
50
|
+
return assessBashCommand(args.command, deps.settings().dangerousPatterns);
|
|
51
|
+
}
|
|
52
|
+
if (deps.fileEditTools.has(exec.name) && typeof args.file_path === 'string') {
|
|
53
|
+
const settings = deps.settings();
|
|
54
|
+
return assessFilePath(args.file_path, {
|
|
55
|
+
cwd: session?.header?.cwd ?? '',
|
|
56
|
+
...settings.additionalDirectories === undefined ? {} : { additionalDirectories: settings.additionalDirectories },
|
|
57
|
+
...settings.protectedFiles === undefined ? {} : { protectedFiles: settings.protectedFiles },
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return { level: 'LOW', reasons: [] };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The sync, pure waterfall WITHOUT the auto-proxy conversion. Under `auto`, a
|
|
64
|
+
* classifier-LOW call whose waterfall decision is `ask` is returned as `ask`
|
|
65
|
+
* here — `decideCall` applies the proxy on top.
|
|
66
|
+
*/
|
|
67
|
+
export function decideCallVerbose(deps, exec) {
|
|
68
|
+
const risk = classify(deps, exec);
|
|
69
|
+
const isReadOnly = deps.readOnlyTools.has(exec.name);
|
|
70
|
+
if (risk.level === 'HIGH') {
|
|
71
|
+
return {
|
|
72
|
+
decision: { kind: 'deny', reason: `blocked by risk classifier: ${risk.reasons.join('; ')}` },
|
|
73
|
+
risk,
|
|
74
|
+
mode: effectiveMode(deps, exec),
|
|
75
|
+
isReadOnly,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const mode = effectiveMode(deps, exec);
|
|
79
|
+
if (risk.level === 'MEDIUM') {
|
|
80
|
+
if (mode === 'bypassPermissions')
|
|
81
|
+
return { decision: { kind: 'allow' }, risk, mode, isReadOnly };
|
|
82
|
+
// Session-scoped approval memory (WS4-PR-B): a rule the user granted via
|
|
83
|
+
// "Allow for this session" overrides the MEDIUM early-return ask. Checked
|
|
84
|
+
// after the HIGH safety deny, before the MEDIUM ask. `plan` still asks —
|
|
85
|
+
// read-only confinement outranks a session grant.
|
|
86
|
+
if (mode !== 'plan' && deps.sessionAllowMatches(exec))
|
|
87
|
+
return { decision: { kind: 'allow' }, risk, mode, isReadOnly };
|
|
88
|
+
return {
|
|
89
|
+
decision: { kind: 'ask', reason: `requires approval by risk classifier: ${risk.reasons.join('; ')}` },
|
|
90
|
+
risk,
|
|
91
|
+
mode,
|
|
92
|
+
isReadOnly,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const subject = subjectOf(exec, deps.bashToolName);
|
|
96
|
+
const decision = evaluatePermission({
|
|
97
|
+
toolName: exec.name,
|
|
98
|
+
...subject === undefined ? {} : { subject },
|
|
99
|
+
// Bypass-immune rules are enforced by the monotonic guard layer, not the
|
|
100
|
+
// waterfall — pass an empty bypassImmune so the guard is authoritative.
|
|
101
|
+
rules: { ...deps.rules(), bypassImmune: [] },
|
|
102
|
+
mode,
|
|
103
|
+
...deps.bypassDisabled() ? { bypassDisabled: true } : {},
|
|
104
|
+
isFileEdit: deps.fileEditTools.has(exec.name),
|
|
105
|
+
isReadOnly,
|
|
106
|
+
sandboxedBashExempt: sandboxedBash(deps, exec),
|
|
107
|
+
});
|
|
108
|
+
return { decision, risk, mode, isReadOnly };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Fold the engine decision for one call. Bypass-immune matches fall to the
|
|
112
|
+
* guard layer, not here. The risk-classifier escalation runs first (a
|
|
113
|
+
* hard-deny HIGH in every mode; an ask MEDIUM outside bypassPermissions),
|
|
114
|
+
* then the normal waterfall proceeds unchanged. Under `auto`, a classifier-LOW
|
|
115
|
+
* call whose waterfall decision is `ask` is auto-allowed (the classifier
|
|
116
|
+
* proxies the prompt); MEDIUM/HIGH already returned above.
|
|
117
|
+
*/
|
|
118
|
+
export function decideCall(deps, exec) {
|
|
119
|
+
const { decision, risk, mode } = decideCallVerbose(deps, exec);
|
|
120
|
+
// auto proxies every LOW-risk ask: at this point the call is classifier-LOW
|
|
121
|
+
// (MEDIUM and HIGH returned above), so low-risk asks auto-allow.
|
|
122
|
+
if (mode === 'auto' && risk.level === 'LOW' && decision.kind === 'ask') {
|
|
123
|
+
return { kind: 'allow' };
|
|
124
|
+
}
|
|
125
|
+
return decision;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.js","sourceRoot":"","sources":["../src/decide.ts"],"names":[],"mappings":"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"}
|
package/lib/domain.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain permission rules for WebFetch: `WebFetch(domain:example.com)`
|
|
3
|
+
* content rules matched against the canonicalized URL hostname. Browser-safe
|
|
4
|
+
* (pure URL/string logic, no harness or alias imports) so the parser and the
|
|
5
|
+
* TUI can both import this module.
|
|
6
|
+
* @module @dsh-cc/permission-rules/domain
|
|
7
|
+
*/
|
|
8
|
+
import type { ContentMatcher } from './types.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Whether a rule tool name governs the WebFetch tool (either its CC spelling
|
|
11
|
+
* or the harness `web_fetch` spelling).
|
|
12
|
+
* @param name - the authored or harness tool name.
|
|
13
|
+
* @returns true for `WebFetch` and `web_fetch`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isWebFetchRuleTool(name: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Canonicalize the hostname of a URL for domain matching: lowercase, trailing
|
|
18
|
+
* dots stripped, IPv6 `[…]` wrapping removed. The port is ignored (Claude
|
|
19
|
+
* matches hostname only). An invalid URL yields `undefined` so the permission
|
|
20
|
+
* falls through to whole-tool / passthrough instead of inventing a host.
|
|
21
|
+
* @param url - the URL whose host to canonicalize.
|
|
22
|
+
* @returns the canonical hostname, or `undefined` when the URL is invalid.
|
|
23
|
+
*/
|
|
24
|
+
export declare function canonicalizeHostname(url: string): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Parse a `domain:<host>` rule content into a {@link ContentMatcher}. The
|
|
27
|
+
* captured text is treated as a bare hostname (never a URL): schemes, paths,
|
|
28
|
+
* ports, empty hosts, and `*` off a label boundary are rejected by throwing
|
|
29
|
+
* so an invalid rule fails loud at load time. A leading `*.` is stored as
|
|
30
|
+
* part of the hostname.
|
|
31
|
+
* @param content - the rule content, expected to start with `domain:`.
|
|
32
|
+
* @returns the domain matcher with the canonical hostname.
|
|
33
|
+
* @throws a `TypeError` when the content is not a valid domain rule.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseDomainContent(content: string): ContentMatcher;
|
|
36
|
+
/**
|
|
37
|
+
* Whether a call hostname matches a domain pattern, following Claude Code's
|
|
38
|
+
* WebFetch domain semantics: a plain pattern is exact-only; a leading `*.`
|
|
39
|
+
* matches the bare domain and any subdomain depth; a `*` in any other label
|
|
40
|
+
* position matches exactly one dot-separated label.
|
|
41
|
+
* @param pattern - the parsed rule hostname (may carry a leading `*.`).
|
|
42
|
+
* @param hostname - the canonical call hostname.
|
|
43
|
+
* @returns true on a match.
|
|
44
|
+
*/
|
|
45
|
+
export declare function domainMatches(pattern: string, hostname: string): boolean;
|
|
46
|
+
//# sourceMappingURL=domain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAyBlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAcxE"}
|
package/lib/domain.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain permission rules for WebFetch: `WebFetch(domain:example.com)`
|
|
3
|
+
* content rules matched against the canonicalized URL hostname. Browser-safe
|
|
4
|
+
* (pure URL/string logic, no harness or alias imports) so the parser and the
|
|
5
|
+
* TUI can both import this module.
|
|
6
|
+
* @module @dsh-cc/permission-rules/domain
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Whether a rule tool name governs the WebFetch tool (either its CC spelling
|
|
10
|
+
* or the harness `web_fetch` spelling).
|
|
11
|
+
* @param name - the authored or harness tool name.
|
|
12
|
+
* @returns true for `WebFetch` and `web_fetch`.
|
|
13
|
+
*/
|
|
14
|
+
export function isWebFetchRuleTool(name) {
|
|
15
|
+
return name === 'WebFetch' || name === 'web_fetch';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Canonicalize the hostname of a URL for domain matching: lowercase, trailing
|
|
19
|
+
* dots stripped, IPv6 `[…]` wrapping removed. The port is ignored (Claude
|
|
20
|
+
* matches hostname only). An invalid URL yields `undefined` so the permission
|
|
21
|
+
* falls through to whole-tool / passthrough instead of inventing a host.
|
|
22
|
+
* @param url - the URL whose host to canonicalize.
|
|
23
|
+
* @returns the canonical hostname, or `undefined` when the URL is invalid.
|
|
24
|
+
*/
|
|
25
|
+
export function canonicalizeHostname(url) {
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = new URL(url);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
let host = parsed.hostname.toLowerCase();
|
|
34
|
+
while (host.endsWith('.'))
|
|
35
|
+
host = host.slice(0, -1);
|
|
36
|
+
if (host.startsWith('[') && host.endsWith(']'))
|
|
37
|
+
host = host.slice(1, -1);
|
|
38
|
+
return host === '' ? undefined : host;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse a `domain:<host>` rule content into a {@link ContentMatcher}. The
|
|
42
|
+
* captured text is treated as a bare hostname (never a URL): schemes, paths,
|
|
43
|
+
* ports, empty hosts, and `*` off a label boundary are rejected by throwing
|
|
44
|
+
* so an invalid rule fails loud at load time. A leading `*.` is stored as
|
|
45
|
+
* part of the hostname.
|
|
46
|
+
* @param content - the rule content, expected to start with `domain:`.
|
|
47
|
+
* @returns the domain matcher with the canonical hostname.
|
|
48
|
+
* @throws a `TypeError` when the content is not a valid domain rule.
|
|
49
|
+
*/
|
|
50
|
+
export function parseDomainContent(content) {
|
|
51
|
+
const match = /^domain:\s*(.+)$/i.exec(content);
|
|
52
|
+
if (match === null || match[1] === undefined) {
|
|
53
|
+
throw new TypeError(`invalid WebFetch domain rule content "${content}"`);
|
|
54
|
+
}
|
|
55
|
+
let host = match[1].trim().toLowerCase();
|
|
56
|
+
// A hostname is never a URL: reject schemes, paths, ports, and fragments.
|
|
57
|
+
if (host.includes('://') || host.includes('/') || host.includes(':') || host.includes('?') || host.includes('#')) {
|
|
58
|
+
throw new TypeError(`WebFetch domain rule host "${host}" must be a bare hostname`);
|
|
59
|
+
}
|
|
60
|
+
while (host.endsWith('.'))
|
|
61
|
+
host = host.slice(0, -1);
|
|
62
|
+
if (host === '') {
|
|
63
|
+
throw new TypeError('WebFetch domain rule host cannot be empty');
|
|
64
|
+
}
|
|
65
|
+
const labels = host.split('.');
|
|
66
|
+
for (const label of labels) {
|
|
67
|
+
if (label !== '*' && label.includes('*')) {
|
|
68
|
+
throw new TypeError(`WebFetch domain rule host "${host}" has a "*" off a label boundary`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (labels.every(label => label === '*')) {
|
|
72
|
+
throw new TypeError(`WebFetch domain rule host "${host}" must name a concrete domain`);
|
|
73
|
+
}
|
|
74
|
+
if (host.startsWith('[') && host.endsWith(']'))
|
|
75
|
+
host = host.slice(1, -1);
|
|
76
|
+
return { kind: 'domain', hostname: host };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether a call hostname matches a domain pattern, following Claude Code's
|
|
80
|
+
* WebFetch domain semantics: a plain pattern is exact-only; a leading `*.`
|
|
81
|
+
* matches the bare domain and any subdomain depth; a `*` in any other label
|
|
82
|
+
* position matches exactly one dot-separated label.
|
|
83
|
+
* @param pattern - the parsed rule hostname (may carry a leading `*.`).
|
|
84
|
+
* @param hostname - the canonical call hostname.
|
|
85
|
+
* @returns true on a match.
|
|
86
|
+
*/
|
|
87
|
+
export function domainMatches(pattern, hostname) {
|
|
88
|
+
const patternHost = pattern.toLowerCase().replace(/\.+$/, '');
|
|
89
|
+
const callHost = hostname.toLowerCase().replace(/\.+$/, '');
|
|
90
|
+
const patternLabels = patternHost.split('.');
|
|
91
|
+
// Leading `*.` (with no other wildcards) matches the bare domain itself
|
|
92
|
+
// and any subdomain depth; a pattern with additional `*` labels falls to
|
|
93
|
+
// the per-label single-label wildcard comparison below.
|
|
94
|
+
if (patternLabels[0] === '*' && patternLabels.length >= 2 && !patternLabels.slice(1).includes('*')) {
|
|
95
|
+
const rest = patternLabels.slice(1).join('.');
|
|
96
|
+
return callHost === rest || callHost.endsWith(`.${rest}`);
|
|
97
|
+
}
|
|
98
|
+
const callLabels = callHost.split('.');
|
|
99
|
+
if (patternLabels.length !== callLabels.length)
|
|
100
|
+
return false;
|
|
101
|
+
return patternLabels.every((label, index) => label === '*' || label === callLabels[index]);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=domain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"domain.js","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure permission evaluation: given a tool call, a source-labelled rule set,
|
|
3
|
+
* and a mode, fold the decision. The same function backs the plugin's
|
|
4
|
+
* `tools/pre-execute` listener and the host UI's rule preview. Browser-safe.
|
|
5
|
+
*
|
|
6
|
+
* Order (spec): tool-wide deny → tool-wide ask (sandboxed-bash exempt) →
|
|
7
|
+
* source-priority content rules → mode rules (acceptEdits/plan/bypass) →
|
|
8
|
+
* whole-tool allow → passthrough. A final plan-mode wrap converts leftover
|
|
9
|
+
* `ask`/`passthrough` on a non-read-only call into a deny (allow and deny
|
|
10
|
+
* decisions stand). Bypass-immune rules are evaluated first and always deny;
|
|
11
|
+
* the plugin additionally enforces them through the monotonic guard layer.
|
|
12
|
+
* @module @dsh-cc/permission-rules/evaluate
|
|
13
|
+
*/
|
|
14
|
+
import { type EvaluationInput, type PermissionDecision, type PermissionRuleSet } from './types.ts';
|
|
15
|
+
/**
|
|
16
|
+
* Merge several rule sets into one, consulting rules by source priority. On a
|
|
17
|
+
* tie (same source), earlier rule-set entries win (earlier sets are treated as
|
|
18
|
+
* higher within a source). The result preserves each rule's original source
|
|
19
|
+
* for later priority decisions.
|
|
20
|
+
* @param sets - rule sets ordered from highest to lowest priority within each source.
|
|
21
|
+
* @returns a single merged rule set.
|
|
22
|
+
*/
|
|
23
|
+
export declare function mergeRuleSets(...sets: readonly PermissionRuleSet[]): PermissionRuleSet;
|
|
24
|
+
/**
|
|
25
|
+
* Fold the decision for one call. Pure: every mode/exemption input is passed
|
|
26
|
+
* in so hosts can resolve them (from plan state, shell sandbox, tool sets)
|
|
27
|
+
* themselves or let the plugin do so.
|
|
28
|
+
* @param input - the call, rule set, mode, and exemption flags.
|
|
29
|
+
* @returns the decision; `passthrough` means no rule matched and mode allowed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function evaluatePermission(input: EvaluationInput): PermissionDecision;
|
|
32
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EAEvB,KAAK,iBAAiB,EAEvB,MAAM,YAAY,CAAA;AAGnB;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,IAAI,EAAE,SAAS,iBAAiB,EAAE,GAAG,iBAAiB,CAOtF;AAcD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,kBAAkB,CAa7E"}
|
package/lib/evaluate.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure permission evaluation: given a tool call, a source-labelled rule set,
|
|
3
|
+
* and a mode, fold the decision. The same function backs the plugin's
|
|
4
|
+
* `tools/pre-execute` listener and the host UI's rule preview. Browser-safe.
|
|
5
|
+
*
|
|
6
|
+
* Order (spec): tool-wide deny → tool-wide ask (sandboxed-bash exempt) →
|
|
7
|
+
* source-priority content rules → mode rules (acceptEdits/plan/bypass) →
|
|
8
|
+
* whole-tool allow → passthrough. A final plan-mode wrap converts leftover
|
|
9
|
+
* `ask`/`passthrough` on a non-read-only call into a deny (allow and deny
|
|
10
|
+
* decisions stand). Bypass-immune rules are evaluated first and always deny;
|
|
11
|
+
* the plugin additionally enforces them through the monotonic guard layer.
|
|
12
|
+
* @module @dsh-cc/permission-rules/evaluate
|
|
13
|
+
*/
|
|
14
|
+
import { ccToolAliases } from '@dsh-cc/tools';
|
|
15
|
+
import { PLAN_READONLY_REASON, SOURCE_PRIORITY, } from "./types.js";
|
|
16
|
+
import { contentMatches } from "./parser.js";
|
|
17
|
+
/**
|
|
18
|
+
* Merge several rule sets into one, consulting rules by source priority. On a
|
|
19
|
+
* tie (same source), earlier rule-set entries win (earlier sets are treated as
|
|
20
|
+
* higher within a source). The result preserves each rule's original source
|
|
21
|
+
* for later priority decisions.
|
|
22
|
+
* @param sets - rule sets ordered from highest to lowest priority within each source.
|
|
23
|
+
* @returns a single merged rule set.
|
|
24
|
+
*/
|
|
25
|
+
export function mergeRuleSets(...sets) {
|
|
26
|
+
return {
|
|
27
|
+
allow: mergeByPriority(sets.map(set => set.allow)),
|
|
28
|
+
deny: mergeByPriority(sets.map(set => set.deny)),
|
|
29
|
+
ask: mergeByPriority(sets.map(set => set.ask)),
|
|
30
|
+
bypassImmune: mergeByPriority(sets.map(set => set.bypassImmune)),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** Concatenate each behavior's lists, then stable-sort by source priority (high first). */
|
|
34
|
+
function mergeByPriority(lists) {
|
|
35
|
+
const flat = lists.flat();
|
|
36
|
+
return flat.slice().sort((a, b) => rankOf(a.source) - rankOf(b.source));
|
|
37
|
+
}
|
|
38
|
+
/** The numeric rank of a source in {@link SOURCE_PRIORITY} (lower rank = higher priority). */
|
|
39
|
+
function rankOf(source) {
|
|
40
|
+
const index = SOURCE_PRIORITY.indexOf(source);
|
|
41
|
+
return index === -1 ? SOURCE_PRIORITY.length : index;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Fold the decision for one call. Pure: every mode/exemption input is passed
|
|
45
|
+
* in so hosts can resolve them (from plan state, shell sandbox, tool sets)
|
|
46
|
+
* themselves or let the plugin do so.
|
|
47
|
+
* @param input - the call, rule set, mode, and exemption flags.
|
|
48
|
+
* @returns the decision; `passthrough` means no rule matched and mode allowed.
|
|
49
|
+
*/
|
|
50
|
+
export function evaluatePermission(input) {
|
|
51
|
+
const { toolName, subject, rules, mode } = input;
|
|
52
|
+
const effectiveMode = (input.bypassDisabled ?? false) && mode === 'bypassPermissions' ? 'default' : mode;
|
|
53
|
+
const decision = foldDecision(input, effectiveMode, toolName, subject, rules);
|
|
54
|
+
// Plan is read-only: leftover ask/passthrough on a mutating call become a
|
|
55
|
+
// deny pointing at exit_plan_mode. Allow (including a matching allow rule)
|
|
56
|
+
// and deny (including deny rules) stand.
|
|
57
|
+
if (effectiveMode === 'plan' && input.isReadOnly !== true
|
|
58
|
+
&& (decision.kind === 'ask' || decision.kind === 'passthrough')) {
|
|
59
|
+
return { kind: 'deny', reason: PLAN_READONLY_REASON };
|
|
60
|
+
}
|
|
61
|
+
return decision;
|
|
62
|
+
}
|
|
63
|
+
/** The inner waterfall, before the plan-mode wrap. */
|
|
64
|
+
function foldDecision(input, effectiveMode, toolName, subject, rules) {
|
|
65
|
+
// Bypass-immune content rules always deny, regardless of mode — including
|
|
66
|
+
// bypassPermissions. The plugin also enforces these through the guard layer
|
|
67
|
+
// so a later (non-waterfall) override cannot flip the denial.
|
|
68
|
+
const immuneDeny = firstBypassImmune(rules.bypassImmune, toolName, subject);
|
|
69
|
+
if (immuneDeny !== undefined) {
|
|
70
|
+
return denyOf(immuneDeny);
|
|
71
|
+
}
|
|
72
|
+
// (a) whole-tool deny beats everything except bypass-immune.
|
|
73
|
+
const toolDeny = firstToolLevel(rules.deny, toolName);
|
|
74
|
+
if (toolDeny !== undefined) {
|
|
75
|
+
return denyOf(toolDeny);
|
|
76
|
+
}
|
|
77
|
+
// (e) bypassPermissions allows everything once a mode-level override applies.
|
|
78
|
+
if (effectiveMode === 'bypassPermissions') {
|
|
79
|
+
return { kind: 'allow' };
|
|
80
|
+
}
|
|
81
|
+
// (b) whole-tool ask, except an exempted sandboxed bash (which allows instead).
|
|
82
|
+
const toolAsk = firstToolLevel(rules.ask, toolName);
|
|
83
|
+
if (toolAsk !== undefined) {
|
|
84
|
+
if (ccToolAliases(toolName).includes('Bash') && input.sandboxedBashExempt === true) {
|
|
85
|
+
return { kind: 'allow' };
|
|
86
|
+
}
|
|
87
|
+
return askOf(toolAsk);
|
|
88
|
+
}
|
|
89
|
+
// (c) content-level allow/deny/ask by source priority. The first matching
|
|
90
|
+
// rule across all three behaviors (in source-priority order) decides.
|
|
91
|
+
for (const source of SOURCE_PRIORITY) {
|
|
92
|
+
for (const behavior of ['allow', 'deny', 'ask']) {
|
|
93
|
+
const matched = firstContentMatch(rules[behavior], toolName, subject, source);
|
|
94
|
+
if (matched !== undefined)
|
|
95
|
+
return decisionOf(behavior, matched);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// (e) acceptEdits auto-allows file-edit calls; plan auto-allows read-only calls.
|
|
99
|
+
if (effectiveMode === 'acceptEdits' && input.isFileEdit === true) {
|
|
100
|
+
return { kind: 'allow' };
|
|
101
|
+
}
|
|
102
|
+
if (effectiveMode === 'plan' && input.isReadOnly === true) {
|
|
103
|
+
return { kind: 'allow' };
|
|
104
|
+
}
|
|
105
|
+
// A whole-tool allow is the coarse default for that tool: no more-specific
|
|
106
|
+
// deny/ask matched, so a bare `Bash` allow admits the call.
|
|
107
|
+
const toolAllow = firstToolLevel(rules.allow, toolName);
|
|
108
|
+
if (toolAllow !== undefined) {
|
|
109
|
+
return { kind: 'allow' };
|
|
110
|
+
}
|
|
111
|
+
// (f) nothing matched — delegate downstream (ultimately the approval seam).
|
|
112
|
+
return { kind: 'passthrough' };
|
|
113
|
+
}
|
|
114
|
+
/** The first whole-tool rule for `toolName` in a behavior list. */
|
|
115
|
+
function firstToolLevel(list, toolName) {
|
|
116
|
+
return list.find(rule => rule.content === undefined && ruleMatchesTool(rule, toolName));
|
|
117
|
+
}
|
|
118
|
+
/** Whether an authored rule's tool name answers to the harness call's tool name. */
|
|
119
|
+
function ruleMatchesTool(rule, toolName) {
|
|
120
|
+
// The harness exec.name is lowercase; the rule preserves its authored CC
|
|
121
|
+
// spelling, so compare through the CC↔harness alias map.
|
|
122
|
+
return ccToolAliases(toolName).includes(rule.toolName);
|
|
123
|
+
}
|
|
124
|
+
/** The first content rule for `toolName`/`subject` at exactly one source, or undefined. */
|
|
125
|
+
function firstContentMatch(list, toolName, subject, source) {
|
|
126
|
+
if (subject === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
for (const rule of list) {
|
|
129
|
+
if (rule.source !== source)
|
|
130
|
+
continue;
|
|
131
|
+
if (rule.content === undefined || rule.matcher === undefined)
|
|
132
|
+
continue;
|
|
133
|
+
if (ruleMatchesTool(rule, toolName) && contentMatches(rule.matcher, subject))
|
|
134
|
+
return rule;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* The first bypass-immune content rule matching `toolName`/`subject`; bypass-
|
|
140
|
+
* immune rules deny regardless of source priority or mode.
|
|
141
|
+
*/
|
|
142
|
+
function firstBypassImmune(list, toolName, subject) {
|
|
143
|
+
if (subject === undefined)
|
|
144
|
+
return undefined;
|
|
145
|
+
for (const rule of list) {
|
|
146
|
+
if (rule.content === undefined || rule.matcher === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
if (!ruleMatchesTool(rule, toolName))
|
|
149
|
+
continue;
|
|
150
|
+
if (contentMatches(rule.matcher, subject))
|
|
151
|
+
return rule;
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
/** Map a matched rule's behavior to a decision. */
|
|
156
|
+
function decisionOf(behavior, match) {
|
|
157
|
+
if (behavior === 'allow')
|
|
158
|
+
return { kind: 'allow' };
|
|
159
|
+
if (behavior === 'deny')
|
|
160
|
+
return denyOf(match);
|
|
161
|
+
return askOf(match);
|
|
162
|
+
}
|
|
163
|
+
/** Deny decision for a matched rule. */
|
|
164
|
+
function denyOf(match) {
|
|
165
|
+
return { kind: 'deny', reason: `denied by permission rule ${ruleLabel(match)}` };
|
|
166
|
+
}
|
|
167
|
+
/** Ask decision for a matched rule. */
|
|
168
|
+
function askOf(match) {
|
|
169
|
+
return { kind: 'ask', reason: `requires approval by permission rule ${ruleLabel(match)}` };
|
|
170
|
+
}
|
|
171
|
+
/** Human-readable rule label including its source. */
|
|
172
|
+
function ruleLabel(match) {
|
|
173
|
+
const content = match.content === undefined ? '' : `(${match.content})`;
|
|
174
|
+
return `${match.toolName}${content} [${match.source}]`;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code-compatible permission-rule engine. Owns a source-labelled rule
|
|
3
|
+
* set (Config `rules` merged with the optional `permissions` settings section),
|
|
4
|
+
* a `tools/pre-execute` listener that folds a mode-aware decision, and the
|
|
5
|
+
* monotonic guard layer that enforces bypass-immune content rules so neither a
|
|
6
|
+
* mode switch nor `bypassPermissions` can override them. A risk-classifier
|
|
7
|
+
* escalation stage hard-denies catastrophic commands and asks on protected or
|
|
8
|
+
* out-of-scope file writes before the normal waterfall. Rules fail loud at
|
|
9
|
+
* load; settings hot-reloads by rebuilding merged state and re-registering
|
|
10
|
+
* guards.
|
|
11
|
+
*
|
|
12
|
+
* @module @dsh-cc/permission-rules
|
|
13
|
+
*/
|
|
14
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
15
|
+
import type z from '@deepseek-ai/schemastery';
|
|
16
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
17
|
+
import { type SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
18
|
+
import { type PermissionMode, type PermissionRuleSet } from './types.ts';
|
|
19
|
+
export { SESSION_ALLOW_EVENT, SessionAllowlist, appendSessionAllow, foldSessionAllows, type SessionAllowEventData, } from './session-allowlist.ts';
|
|
20
|
+
export { createSandboxApprovalListener, isSandboxEscalation, type SandboxApprovalListenerConfig, } from './approval-listener.ts';
|
|
21
|
+
export { foldPermissionMode, foldResumeSandbox, setPermissionMode, PERMISSION_MODE_EVENT, } from './mode.ts';
|
|
22
|
+
export { CLASSIFIER_EVENT, appendSessionClassifier, foldClassifiers, createAutoStage, type AutoModeSettings, type AutoModeClassifierSettings, type ClassifierAuditEventData, } from './auto-stage.ts';
|
|
23
|
+
export { createLlmClassifier, expandSoftDeny, DEFAULT_SOFT_DENY, type LlmVerdict, type LlmClassification, type ClassifierAuditEvent, type ClassifierFailure, } from './llm-classifier.ts';
|
|
24
|
+
export { PERMISSION_MODES, SWITCHABLE_PERMISSION_MODES, PLAN_READONLY_REASON, type PermissionMode, type SwitchablePermissionMode, } from './types.ts';
|
|
25
|
+
export { parseRuleString, ruleString, contentMatches, } from './parser.ts';
|
|
26
|
+
export { canonicalizeHostname, isWebFetchRuleTool } from './domain.ts';
|
|
27
|
+
declare module '@deepseek-ai/cordis' {
|
|
28
|
+
interface Context {
|
|
29
|
+
/** The mounted permission-rule engine, when this plugin is composed. */
|
|
30
|
+
permissionRules: PermissionRulesService;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** The settings namespace carrying `permissions.allow/deny/ask/defaultMode`. */
|
|
34
|
+
export declare const PERMISSION_SETTINGS_NAMESPACE: SettingsNamespace;
|
|
35
|
+
export { permissionSettingsSchema, ConfigSchema, DEFAULT_FILE_EDIT_TOOLS, DEFAULT_READ_ONLY_TOOLS, type PermissionSettings, type ConfigRules, type Config, } from './settings-schema.ts';
|
|
36
|
+
import { type Config } from './settings-schema.ts';
|
|
37
|
+
/** The engine's Service Definition plus the mode/rule write and read surface. */
|
|
38
|
+
export declare class PermissionRulesService extends Service {
|
|
39
|
+
config: Config;
|
|
40
|
+
static Config: z<Config>;
|
|
41
|
+
static inject: string[];
|
|
42
|
+
private readonly bashToolName;
|
|
43
|
+
private readonly fileEditTools;
|
|
44
|
+
private readonly readOnlyTools;
|
|
45
|
+
private readonly settingsSource;
|
|
46
|
+
private readonly rulesConfig;
|
|
47
|
+
private readonly bypassImmuneRules;
|
|
48
|
+
/** Reads the currently authoritative settings section (swapped by the settings hook). */
|
|
49
|
+
private settingsRead;
|
|
50
|
+
/** Live merged state; rebuilt on settings change so listeners read a fresh snapshot. */
|
|
51
|
+
private state;
|
|
52
|
+
/** Disposers for the currently registered monotonic guards. */
|
|
53
|
+
private guardDisposers;
|
|
54
|
+
/** Session-scoped approval memory (WS4-PR-B): rules granted via the UI's "Allow for this session". */
|
|
55
|
+
private readonly sessionAllowlist;
|
|
56
|
+
/** Session ids already seeded from their log's `permission/session-allow` audit events. */
|
|
57
|
+
private readonly allowlistSeeded;
|
|
58
|
+
/** The optional LLM classifier stage (armed per call from the live settings slice). */
|
|
59
|
+
private autoStage;
|
|
60
|
+
constructor(ctx: Context, config: Config);
|
|
61
|
+
/** The current settings-resolved section, defaulting to the schema default. */
|
|
62
|
+
private settingsSection;
|
|
63
|
+
/** Reject a settings section the engine could not act on — fail loud at the settings boundary. */
|
|
64
|
+
private validateSettings;
|
|
65
|
+
/** Rebuild merged state and re-register guards (mount and settings change). */
|
|
66
|
+
private reload;
|
|
67
|
+
/** Parse the Config `rules` block into a source-`config` rule set. */
|
|
68
|
+
private configRuleSet;
|
|
69
|
+
/** (Re)register monotonic guards for the bypass-immune rules, idempotent. */
|
|
70
|
+
private registerGuards;
|
|
71
|
+
/**
|
|
72
|
+
* Whether the session-scoped allowlist matches this call. The session's
|
|
73
|
+
* rules are seeded once from its log's `permission/session-allow` audit
|
|
74
|
+
* events, so a resumed session keeps its grants. Agent-less calls never
|
|
75
|
+
* match (there is no session to scope to).
|
|
76
|
+
*/
|
|
77
|
+
private sessionAllowMatches;
|
|
78
|
+
/**
|
|
79
|
+
* The session's workspace root: the durable `worktree/entered` fold
|
|
80
|
+
* (session-cwd, WS1), falling back to the session header cwd. Undefined
|
|
81
|
+
* when the session never recorded a cwd — the sandbox listener then cannot
|
|
82
|
+
* verify an escalation is in-scope and falls through to the normal ask.
|
|
83
|
+
*/
|
|
84
|
+
private sessionWorkspaceOf;
|
|
85
|
+
/**
|
|
86
|
+
* Grant a session-scoped allow rule on the agent's session: in-memory match
|
|
87
|
+
* for the rest of this session plus a `permission/session-allow` audit
|
|
88
|
+
* event. Never touches the `permissions` settings namespace.
|
|
89
|
+
* @param agent - the agent whose session is granted the rule.
|
|
90
|
+
* @param rule - the rule string (e.g. `Bash(npm )` or a whole-tool name).
|
|
91
|
+
*/
|
|
92
|
+
addSessionAllow(agent: Agent, rule: string): void;
|
|
93
|
+
/**
|
|
94
|
+
* Drop every session-scoped rule for the agent's session (audited clear
|
|
95
|
+
* record in the session log).
|
|
96
|
+
*/
|
|
97
|
+
clearSessionAllows(agent: Agent): void;
|
|
98
|
+
/**
|
|
99
|
+
* Whether switching to `bypassPermissions` is disabled by Config or the
|
|
100
|
+
* settings section.
|
|
101
|
+
*/
|
|
102
|
+
private bypassDisabled;
|
|
103
|
+
/**
|
|
104
|
+
* The LIVE merged settings default (`config.defaultMode`, overridden by the
|
|
105
|
+
* settings section): rebuilt on every settings reload, so display surfaces
|
|
106
|
+
* reading this always follow the currently authoritative default.
|
|
107
|
+
*/
|
|
108
|
+
get defaultMode(): PermissionMode;
|
|
109
|
+
/**
|
|
110
|
+
* Switch a session's permission mode durably. Semantics live in
|
|
111
|
+
* `switchSessionPermissionMode` (./mode.ts): `plan` is owned by plan-mode
|
|
112
|
+
* and throws; entering `bypassPermissions` pins the session sandbox to
|
|
113
|
+
* `danger-full-access` and records the prior mode for restore; unknown or
|
|
114
|
+
* disabled modes throw.
|
|
115
|
+
* @param agent - the live agent whose session mode is changing.
|
|
116
|
+
* @param mode - the new permission mode.
|
|
117
|
+
*/
|
|
118
|
+
setMode(agent: Agent, mode: PermissionMode): void;
|
|
119
|
+
/** The currently merged rule set (for introspection and host preview). */
|
|
120
|
+
get ruleSet(): PermissionRuleSet;
|
|
121
|
+
}
|
|
122
|
+
export default PermissionRulesService;
|
|
123
|
+
//# sourceMappingURL=index.d.ts.map
|