@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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +86 -0
  4. package/README.zh.md +86 -0
  5. package/lib/approval-listener.d.ts +50 -0
  6. package/lib/approval-listener.d.ts.map +1 -0
  7. package/lib/approval-listener.js +58 -0
  8. package/lib/approval-listener.js.map +1 -0
  9. package/lib/auto-stage.d.ts +138 -0
  10. package/lib/auto-stage.d.ts.map +1 -0
  11. package/lib/auto-stage.js +284 -0
  12. package/lib/auto-stage.js.map +1 -0
  13. package/lib/classifier.d.ts +57 -0
  14. package/lib/classifier.d.ts.map +1 -0
  15. package/lib/classifier.js +129 -0
  16. package/lib/classifier.js.map +1 -0
  17. package/lib/decide.d.ts +80 -0
  18. package/lib/decide.d.ts.map +1 -0
  19. package/lib/decide.js +127 -0
  20. package/lib/decide.js.map +1 -0
  21. package/lib/domain.d.ts +46 -0
  22. package/lib/domain.d.ts.map +1 -0
  23. package/lib/domain.js +103 -0
  24. package/lib/domain.js.map +1 -0
  25. package/lib/evaluate.d.ts +32 -0
  26. package/lib/evaluate.d.ts.map +1 -0
  27. package/lib/evaluate.js +176 -0
  28. package/lib/evaluate.js.map +1 -0
  29. package/lib/index.d.ts +123 -0
  30. package/lib/index.d.ts.map +1 -0
  31. package/lib/index.js +380 -0
  32. package/lib/index.js.map +1 -0
  33. package/lib/invariant.d.ts +28 -0
  34. package/lib/invariant.d.ts.map +1 -0
  35. package/lib/invariant.js +54 -0
  36. package/lib/invariant.js.map +1 -0
  37. package/lib/llm-classifier.d.ts +107 -0
  38. package/lib/llm-classifier.d.ts.map +1 -0
  39. package/lib/llm-classifier.js +231 -0
  40. package/lib/llm-classifier.js.map +1 -0
  41. package/lib/matchers.d.ts +18 -0
  42. package/lib/matchers.d.ts.map +1 -0
  43. package/lib/matchers.js +43 -0
  44. package/lib/matchers.js.map +1 -0
  45. package/lib/mode.d.ts +91 -0
  46. package/lib/mode.d.ts.map +1 -0
  47. package/lib/mode.js +133 -0
  48. package/lib/mode.js.map +1 -0
  49. package/lib/parser.d.ts +91 -0
  50. package/lib/parser.d.ts.map +1 -0
  51. package/lib/parser.js +282 -0
  52. package/lib/parser.js.map +1 -0
  53. package/lib/session-allowlist.d.ts +76 -0
  54. package/lib/session-allowlist.d.ts.map +1 -0
  55. package/lib/session-allowlist.js +122 -0
  56. package/lib/session-allowlist.js.map +1 -0
  57. package/lib/settings-schema.d.ts +99 -0
  58. package/lib/settings-schema.d.ts.map +1 -0
  59. package/lib/settings-schema.js +64 -0
  60. package/lib/settings-schema.js.map +1 -0
  61. package/lib/types.d.ts +150 -0
  62. package/lib/types.d.ts.map +1 -0
  63. package/lib/types.js +33 -0
  64. package/lib/types.js.map +1 -0
  65. package/package.json +71 -0
package/lib/parser.js ADDED
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Rule-string syntax: `ToolName` or `ToolName(content)`, where `content` may
3
+ * escape `(`/`)`/`\` with a backslash, use `*` as a wildcard, or end in `:*`
4
+ * to declare a prefix rule. Parsing failures THROW so an invalid rule is
5
+ * reported at load time rather than silently mis-matching.
6
+ *
7
+ * Matching follows Claude Code shell-rule semantics on the CALL subject:
8
+ * `Bash(npm install)` is a prefix rule (any command starting with the text);
9
+ * `Bash(npm publish:*)` declares the prefix `npm publish:`; a `*` anywhere
10
+ * makes the content a wildcard glob. `\*` matches a literal asterisk.
11
+ *
12
+ * The module is browser-safe (pure string logic) so the host UI that previews
13
+ * rule hits can import it directly.
14
+ * @module @dsh-cc/permission-rules/parser
15
+ */
16
+ import { domainMatches, isWebFetchRuleTool, parseDomainContent } from "./domain.js";
17
+ /**
18
+ * Parse one rule string into a {@link ContentMatcher} for the given content.
19
+ * A content ending in `:*` yields a `prefix` matcher on the stem; otherwise
20
+ * an unescaped `*` yields a `wildcard` matcher; otherwise a `prefix` matcher
21
+ * on the whole content (the shell-rule convention).
22
+ * @param content - the unescaped rule content (may be empty for a whole-tool rule).
23
+ * @returns the matcher, or `undefined` for empty content (whole-tool rule).
24
+ */
25
+ export function matchContent(content) {
26
+ if (content === '')
27
+ return undefined;
28
+ if (content.endsWith(':*')) {
29
+ // Legacy Claude Code prefix form: "npm publish:*" matches any command
30
+ // starting with "npm publish:" (the colon is part of the prefix).
31
+ return { kind: 'prefix', prefix: content.slice(0, -1) };
32
+ }
33
+ if (!hasUnescapedWildcard(content)) {
34
+ return { kind: 'prefix', prefix: content };
35
+ }
36
+ return { kind: 'wildcard', pattern: content };
37
+ }
38
+ /**
39
+ * Whether a content string holds an unescaped `*` (not `\*`).
40
+ * @param content - the string to inspect.
41
+ * @returns true when an asterisk is preceded by an even number of backslashes.
42
+ */
43
+ export function hasUnescapedWildcard(content) {
44
+ for (let index = 0; index < content.length; index += 1) {
45
+ if (content[index] !== '*')
46
+ continue;
47
+ let backslashes = 0;
48
+ for (let j = index - 1; j >= 0 && content[j] === '\\'; j -= 1)
49
+ backslashes += 1;
50
+ if (backslashes % 2 === 0)
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ /** Whether an index is an unescaped occurrence of `char` (preceded by an even number of backslashes). */
56
+ function isUnescapedAt(content, index, char) {
57
+ if (content[index] !== char)
58
+ return false;
59
+ let backslashes = 0;
60
+ for (let j = index - 1; j >= 0 && content[j] === '\\'; j -= 1)
61
+ backslashes += 1;
62
+ return backslashes % 2 === 0;
63
+ }
64
+ /** The first unescaped index of `char`, or -1. */
65
+ function firstUnescaped(content, char) {
66
+ for (let index = 0; index < content.length; index += 1) {
67
+ if (isUnescapedAt(content, index, char))
68
+ return index;
69
+ }
70
+ return -1;
71
+ }
72
+ /** The last unescaped index of `char`, or -1. */
73
+ function lastUnescaped(content, char) {
74
+ for (let index = content.length - 1; index >= 0; index -= 1) {
75
+ if (isUnescapedAt(content, index, char))
76
+ return index;
77
+ }
78
+ return -1;
79
+ }
80
+ /**
81
+ * Unescape rule content after parsing: `\(`→`(`, `\)`→`)`, then `\\`→`\`.
82
+ * Reverse of {@link escapeRuleContent}.
83
+ * @param content - escaped content, possibly containing `\(`, `\)`, `\\`.
84
+ * @returns the literal content.
85
+ */
86
+ export function unescapeRuleContent(content) {
87
+ return content
88
+ .replace(/\\\(/g, '(')
89
+ .replace(/\\\)/g, ')')
90
+ .replace(/\\\\/g, '\\');
91
+ }
92
+ /**
93
+ * Escape rule content for safe storage in a `ToolName(content)` rule:
94
+ * `\`→`\\` first, then `(`→`\(` and `)`→`\)`.
95
+ * @param content - the literal content.
96
+ * @returns content with its parens and backslashes escaped.
97
+ */
98
+ export function escapeRuleContent(content) {
99
+ return content
100
+ .replace(/\\/g, '\\\\')
101
+ .replace(/\(/g, '\\(')
102
+ .replace(/\)/g, '\\)');
103
+ }
104
+ /**
105
+ * Parse one rule string into a {@link RuleString}. Accepts `ToolName` or
106
+ * `ToolName(content)`. Rejects malformed input (empty tool name, unbalanced
107
+ * parens, trailing text after the closing paren) by throwing — the engine's
108
+ * fail-loud contract.
109
+ * @param rule - the rule string to parse.
110
+ * @returns the parsed tool name, optional content (unescaped), and optional matcher.
111
+ * @throws a `TypeError` describing the malformed rule.
112
+ */
113
+ export function parseRuleString(rule) {
114
+ if (rule.trim() === '')
115
+ throw new TypeError('permission rule cannot be empty');
116
+ const open = firstUnescaped(rule, '(');
117
+ if (open === -1) {
118
+ return { toolName: rule.trim() };
119
+ }
120
+ const close = lastUnescaped(rule, ')');
121
+ if (close === -1) {
122
+ throw new TypeError(`permission rule "${rule}" has an opening "(" with no unescaped ")"`);
123
+ }
124
+ if (close !== rule.length - 1) {
125
+ throw new TypeError(`permission rule "${rule}" has content after its closing ")"`);
126
+ }
127
+ const toolName = rule.slice(0, open).trim();
128
+ if (toolName === '') {
129
+ throw new TypeError(`permission rule "${rule}" has content but no tool name`);
130
+ }
131
+ const rawContent = rule.slice(open + 1, close);
132
+ const content = unescapeRuleContent(rawContent);
133
+ if (content === '' || content === '*') {
134
+ // Empty or single-wildcard content is a whole-tool rule, as in Claude Code.
135
+ return { toolName };
136
+ }
137
+ // WebFetch domain rules take their own matcher shape; other tools (e.g.
138
+ // `Bash(domain:example.com)`) keep the plain prefix convention.
139
+ if (isWebFetchRuleTool(toolName) && /^domain:/i.test(content)) {
140
+ const matcher = parseDomainContent(content);
141
+ return { toolName, content, matcher };
142
+ }
143
+ const matcher = matchContent(content);
144
+ // Content is non-empty and not a bare `*`, so a matcher is always derived.
145
+ if (matcher === undefined)
146
+ return { toolName };
147
+ return { toolName, content, matcher };
148
+ }
149
+ /**
150
+ * Render a rule back to its canonical string form with content escaped.
151
+ * @param toolName - the tool name.
152
+ * @param content - optional content.
153
+ * @returns the round-trippable rule string.
154
+ */
155
+ export function ruleString(toolName, content) {
156
+ return content === undefined || content === '' ? toolName : `${toolName}(${escapeRuleContent(content)})`;
157
+ }
158
+ /**
159
+ * Whether a call subject matches a content matcher.
160
+ * @param matcher - the rule's content matcher.
161
+ * @param matcher - subject to test.
162
+ * @returns true on a match.
163
+ */
164
+ export function contentMatches(matcher, subject) {
165
+ if (matcher.kind === 'prefix')
166
+ return subject.startsWith(matcher.prefix);
167
+ if (matcher.kind === 'domain')
168
+ return domainMatches(matcher.hostname, subject);
169
+ return wildcardMatches(matcher.pattern, subject);
170
+ }
171
+ /**
172
+ * Whether a subject matches a `*` wildcard pattern; `\*` matches a literal
173
+ * asterisk and `\\` a literal backslash. `*` matches any run of characters.
174
+ * @param pattern - the wildcard pattern.
175
+ * @param subject - the string to test.
176
+ * @returns true when the pattern matches.
177
+ */
178
+ export function wildcardMatches(pattern, subject) {
179
+ const tokens = tokenizeWildcard(pattern);
180
+ const hasStar = tokens.some(token => token.kind === 'star');
181
+ if (!hasStar) {
182
+ return literalSegments(tokens).reduce((acc, token) => acc + token.value, '') === subject;
183
+ }
184
+ const segments = literalSegments(tokens);
185
+ if (segments.length === 0)
186
+ return true;
187
+ const isStart = tokens[0]?.kind === 'literal';
188
+ const isEnd = tokens[tokens.length - 1]?.kind === 'literal';
189
+ let position = 0;
190
+ if (isStart) {
191
+ const first = segments[0];
192
+ if (first === undefined)
193
+ return false;
194
+ if (!subject.startsWith(first.value))
195
+ return false;
196
+ position = first.value.length;
197
+ }
198
+ const lastIndex = segments.length - 1;
199
+ const firstMiddle = isStart ? 1 : 0;
200
+ const lastMiddle = isEnd ? lastIndex - 1 : lastIndex;
201
+ for (let index = firstMiddle; index <= lastMiddle; index += 1) {
202
+ const segment = segments[index];
203
+ if (segment === undefined)
204
+ return false;
205
+ const at = subject.indexOf(segment.value, position);
206
+ if (at === -1)
207
+ return false;
208
+ position = at + segment.value.length;
209
+ }
210
+ if (isEnd) {
211
+ const endSegment = segments[lastIndex];
212
+ if (endSegment === undefined)
213
+ return false;
214
+ return subject.endsWith(endSegment.value) && position <= subject.length - endSegment.value.length;
215
+ }
216
+ return true;
217
+ }
218
+ /** The literal tokens of a token array, in order (star gaps removed). */
219
+ function literalSegments(tokens) {
220
+ return tokens.filter((token) => token.kind === 'literal');
221
+ }
222
+ /** Split a wildcard pattern into star/literal tokens, expanding `\*` and `\\`. */
223
+ function tokenizeWildcard(pattern) {
224
+ const tokens = [];
225
+ let buffer = '';
226
+ const flush = () => {
227
+ if (buffer !== '') {
228
+ tokens.push({ kind: 'literal', value: buffer });
229
+ buffer = '';
230
+ }
231
+ };
232
+ for (let index = 0; index < pattern.length; index += 1) {
233
+ const char = pattern[index];
234
+ if (char === '\\' && index + 1 < pattern.length) {
235
+ const next = pattern[index + 1];
236
+ if (next === '*') {
237
+ buffer += '*';
238
+ index += 1;
239
+ continue;
240
+ }
241
+ if (next === '\\') {
242
+ buffer += '\\';
243
+ index += 1;
244
+ continue;
245
+ }
246
+ buffer += char;
247
+ continue;
248
+ }
249
+ if (char === '*') {
250
+ flush();
251
+ tokens.push({ kind: 'star' });
252
+ continue;
253
+ }
254
+ buffer += char;
255
+ }
256
+ flush();
257
+ return tokens;
258
+ }
259
+ /**
260
+ * Build a parsed, source-labelled rule from a rule string.
261
+ * @param rule - the rule string (`ToolName` or `ToolName(content)`).
262
+ * @param behavior - the behavior this rule prescribes.
263
+ * @param source - the rule's provenance, used for evaluation priority.
264
+ * @returns the parsed rule.
265
+ * @throws a `TypeError` when the rule string is malformed.
266
+ */
267
+ export function parseRule(rule, behavior, source) {
268
+ const parsed = parseRuleString(rule);
269
+ if (parsed.content === undefined) {
270
+ return { toolName: parsed.toolName, behavior, source };
271
+ }
272
+ // parseRuleString derives a matcher for every non-empty content, so the
273
+ // optional types are co-present by construction.
274
+ return {
275
+ toolName: parsed.toolName,
276
+ content: parsed.content,
277
+ matcher: parsed.matcher,
278
+ behavior,
279
+ source,
280
+ };
281
+ }
282
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Session-scoped approval memory (WS4-PR-B). A per-session allowlist of
3
+ * permission rules the user granted through the "Allow for this session"
4
+ * approval option. Rules live in an in-memory `SessionAllowlist` map keyed by
5
+ * session id, and every grant (and clear) is also appended to the session log
6
+ * as a `permission/session-allow` audit event carrying a timestamp — the
7
+ * durable audit trail required by WS4. Session-scoped approvals never touch
8
+ * the `permissions` settings namespace, so they do not persist across
9
+ * sessions.
10
+ *
11
+ * Cross-repo event registration: this module adds `permission/session-allow`
12
+ * to the upstream `KNOWN_SESSION_EVENT_TYPES` set at load (same pattern as
13
+ * `permission/mode` in `./mode.ts`) so the persistence layer resumes logs
14
+ * containing it.
15
+ *
16
+ * @module @dsh-cc/permission-rules/session-allowlist
17
+ */
18
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
19
+ import type { PermissionRule } from './types.ts';
20
+ /** The session event type carrying a session-scoped approval audit record. */
21
+ export declare const SESSION_ALLOW_EVENT = "permission/session-allow";
22
+ /**
23
+ * The `permission/session-allow` payload as written by this plugin. `scope`
24
+ * distinguishes a user-granted session rule (`session`) from a sandbox
25
+ * escalation auto-approved by the WS3 approval-seam listener (`sandbox-auto`,
26
+ * which grants nothing — it only audits the auto-approval). `cleared` marks a
27
+ * session-allowlist wipe and folds by emptying the accumulated set.
28
+ */
29
+ export interface SessionAllowEventData {
30
+ /** The permission rule granted, in `Bash(prefix )` / `Tool` string form. */
31
+ rule?: string;
32
+ /** What produced the record. */
33
+ scope: 'session' | 'sandbox-auto';
34
+ /** The tool the record is about (audit context). */
35
+ toolName: string;
36
+ /** Wall-clock time of the record (audit context). */
37
+ timestamp: number;
38
+ /** The asker's reason string, when the approval carried one. */
39
+ reason?: string;
40
+ /** Set on a clear record: everything before it is discarded by the fold. */
41
+ cleared?: boolean;
42
+ }
43
+ /**
44
+ * Append one `permission/session-allow` audit record to the session log.
45
+ * Goes through a widened append face (same cross-pin strategy as
46
+ * `./mode.ts`): the event type postdates the upstream session catalog.
47
+ */
48
+ export declare function appendSessionAllow(session: Session, data: SessionAllowEventData): void;
49
+ /**
50
+ * Fold a session log into the session-scoped allow rules it grants, in grant
51
+ * order. A `cleared` record empties the accumulated set (last-wins semantics
52
+ * for the clear). Sandbox-auto records carry no rule and contribute nothing.
53
+ * @param events - session events in log order (other event types are skipped).
54
+ * @returns the live session-scoped rules, source-labelled `session`.
55
+ */
56
+ export declare function foldSessionAllows(events: readonly SessionEvent[]): PermissionRule[];
57
+ /**
58
+ * The in-memory session-scoped allowlist: rules keyed by session id, checked
59
+ * by `decide()` before the MEDIUM early-return. Purely in-memory — a restart
60
+ * re-seeds from the session log's audit events (see {@link foldSessionAllows}),
61
+ * and a different session id sees nothing.
62
+ */
63
+ export declare class SessionAllowlist {
64
+ private readonly bySession;
65
+ /** Grant one rule to a session (appends the audit event as a side effect). */
66
+ add(session: Session, rule: string): void;
67
+ /** Whether any session-scoped rule matches this call (tool name + subject). */
68
+ matches(sessionId: string, toolName: string, subject: string | undefined): boolean;
69
+ /** Drop every session-scoped rule for one session (appends a clear record). */
70
+ clear(session: Session): void;
71
+ /** Seed (or replace) one session's rules from its folded audit events. */
72
+ seed(sessionId: string, rules: readonly PermissionRule[]): void;
73
+ /** The parsed rules currently granted to one session (introspection). */
74
+ rulesOf(sessionId: string): readonly PermissionRule[];
75
+ }
76
+ //# sourceMappingURL=session-allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-allowlist.d.ts","sourceRoot":"","sources":["../src/session-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,6BAA6B,CAE5D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gCAAgC;IAChC,KAAK,EAAE,SAAS,GAAG,cAAc,CAAA;IACjC,oDAAoD;IACpD,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,4EAA4E;IAC5E,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAaD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,qBAAqB,GAAG,IAAI,CAGtF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,cAAc,EAAE,CAYnF;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;IAEhE,8EAA8E;IAC9E,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAqBzC,+EAA+E;IAC/E,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO;IAYlF,+EAA+E;IAC/E,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAY7B,0EAA0E;IAC1E,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI;IAQ/D,yEAAyE;IACzE,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE;CAGtD"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Session-scoped approval memory (WS4-PR-B). A per-session allowlist of
3
+ * permission rules the user granted through the "Allow for this session"
4
+ * approval option. Rules live in an in-memory `SessionAllowlist` map keyed by
5
+ * session id, and every grant (and clear) is also appended to the session log
6
+ * as a `permission/session-allow` audit event carrying a timestamp — the
7
+ * durable audit trail required by WS4. Session-scoped approvals never touch
8
+ * the `permissions` settings namespace, so they do not persist across
9
+ * sessions.
10
+ *
11
+ * Cross-repo event registration: this module adds `permission/session-allow`
12
+ * to the upstream `KNOWN_SESSION_EVENT_TYPES` set at load (same pattern as
13
+ * `permission/mode` in `./mode.ts`) so the persistence layer resumes logs
14
+ * containing it.
15
+ *
16
+ * @module @dsh-cc/permission-rules/session-allowlist
17
+ */
18
+ import { KNOWN_SESSION_EVENT_TYPES } from '@deepseek-ai/dsh-session';
19
+ import { parseRule } from "./parser.js";
20
+ import { ruleMatches, ruleMatchesTool } from "./matchers.js";
21
+ /** The session event type carrying a session-scoped approval audit record. */
22
+ export const SESSION_ALLOW_EVENT = 'permission/session-allow';
23
+ KNOWN_SESSION_EVENT_TYPES.add(SESSION_ALLOW_EVENT);
24
+ /** Read a log event through the extended `permission/session-allow` face. */
25
+ function asAllowEvent(event) {
26
+ return event;
27
+ }
28
+ /**
29
+ * Append one `permission/session-allow` audit record to the session log.
30
+ * Goes through a widened append face (same cross-pin strategy as
31
+ * `./mode.ts`): the event type postdates the upstream session catalog.
32
+ */
33
+ export function appendSessionAllow(session, data) {
34
+ session.append(SESSION_ALLOW_EVENT, data);
35
+ }
36
+ /**
37
+ * Fold a session log into the session-scoped allow rules it grants, in grant
38
+ * order. A `cleared` record empties the accumulated set (last-wins semantics
39
+ * for the clear). Sandbox-auto records carry no rule and contribute nothing.
40
+ * @param events - session events in log order (other event types are skipped).
41
+ * @returns the live session-scoped rules, source-labelled `session`.
42
+ */
43
+ export function foldSessionAllows(events) {
44
+ const raw = [];
45
+ for (const event of events) {
46
+ const wire = asAllowEvent(event);
47
+ if (wire.type !== SESSION_ALLOW_EVENT)
48
+ continue;
49
+ if (wire.data?.cleared === true) {
50
+ raw.length = 0;
51
+ continue;
52
+ }
53
+ if (typeof wire.data?.rule === 'string' && wire.data.rule !== '')
54
+ raw.push(wire.data.rule);
55
+ }
56
+ return raw.map(ruleRaw => parseRule(ruleRaw, 'allow', 'session'));
57
+ }
58
+ /**
59
+ * The in-memory session-scoped allowlist: rules keyed by session id, checked
60
+ * by `decide()` before the MEDIUM early-return. Purely in-memory — a restart
61
+ * re-seeds from the session log's audit events (see {@link foldSessionAllows}),
62
+ * and a different session id sees nothing.
63
+ */
64
+ export class SessionAllowlist {
65
+ bySession = new Map();
66
+ /** Grant one rule to a session (appends the audit event as a side effect). */
67
+ add(session, rule) {
68
+ const id = String(session.id);
69
+ const parsed = parseRule(rule, 'allow', 'session');
70
+ const rules = this.bySession.get(id) ?? [];
71
+ if (!rules.some(existing => existing.toolName === parsed.toolName
72
+ && existing.content === parsed.content
73
+ && existing.matcher === parsed.matcher
74
+ && existing.behavior === parsed.behavior)) {
75
+ rules.push(parsed);
76
+ this.bySession.set(id, rules);
77
+ }
78
+ appendSessionAllow(session, {
79
+ rule,
80
+ scope: 'session',
81
+ toolName: parsed.toolName,
82
+ timestamp: Date.now(),
83
+ });
84
+ }
85
+ /** Whether any session-scoped rule matches this call (tool name + subject). */
86
+ matches(sessionId, toolName, subject) {
87
+ const rules = this.bySession.get(sessionId);
88
+ if (rules === undefined)
89
+ return false;
90
+ // `ruleMatches` is content-only (a whole-tool rule never matches it), so
91
+ // whole-tool session grants match through `ruleMatchesTool` directly.
92
+ return rules.some(rule => rule.content === undefined
93
+ ? ruleMatchesTool(rule, toolName)
94
+ : subject !== undefined && ruleMatches(rule, toolName, subject));
95
+ }
96
+ /** Drop every session-scoped rule for one session (appends a clear record). */
97
+ clear(session) {
98
+ const id = String(session.id);
99
+ if (!this.bySession.has(id))
100
+ return;
101
+ this.bySession.delete(id);
102
+ appendSessionAllow(session, {
103
+ scope: 'session',
104
+ toolName: '*',
105
+ timestamp: Date.now(),
106
+ cleared: true,
107
+ });
108
+ }
109
+ /** Seed (or replace) one session's rules from its folded audit events. */
110
+ seed(sessionId, rules) {
111
+ if (rules.length === 0) {
112
+ this.bySession.delete(sessionId);
113
+ return;
114
+ }
115
+ this.bySession.set(sessionId, [...rules]);
116
+ }
117
+ /** The parsed rules currently granted to one session (introspection). */
118
+ rulesOf(sessionId) {
119
+ return this.bySession.get(sessionId) ?? [];
120
+ }
121
+ }
122
+ //# sourceMappingURL=session-allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-allowlist.js","sourceRoot":"","sources":["../src/session-allowlist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAA;AAEpE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAG5D,8EAA8E;AAC9E,MAAM,CAAC,MAAM,mBAAmB,GAAG,0BAA0B,CAE5D;AAAC,yBAAyC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;AA8BpE,6EAA6E;AAC7E,SAAS,YAAY,CAAC,KAAmB;IACvC,OAAO,KAAoC,CAAA;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAgB,EAAE,IAA2B;IAE5E,OAAiC,CAAC,MAAM,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAA;AACvE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAA+B;IAC/D,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,IAAI,CAAC,IAAI,KAAK,mBAAmB;YAAE,SAAQ;QAC/C,IAAI,IAAI,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;YAChC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA;YACd,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC5F,CAAC;IACD,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAA;AACnE,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,gBAAgB;IACV,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAA;IAEhE,8EAA8E;IAC9E,GAAG,CAAC,OAAgB,EAAE,IAAY;QAChC,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC7B,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAA;QAC1C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CACzB,QAAQ,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;eAClC,QAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO;eACnC,QAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO;eACnC,QAAQ,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CACzC,EAAE,CAAC;YACF,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAClB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;QAC/B,CAAC;QACD,kBAAkB,CAAC,OAAO,EAAE;YAC1B,IAAI;YACJ,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAA;IACJ,CAAC;IAED,+EAA+E;IAC/E,OAAO,CAAC,SAAiB,EAAE,QAAgB,EAAE,OAA2B;QACtE,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC3C,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;QACrC,yEAAyE;QACzE,sEAAsE;QACtE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvB,IAAI,CAAC,OAAO,KAAK,SAAS;YACxB,CAAC,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC;YACjC,CAAC,CAAC,OAAO,KAAK,SAAS,IAAI,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAClE,CAAA;IACH,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,OAAgB;QACpB,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAC7B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YAAE,OAAM;QACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACzB,kBAAkB,CAAC,OAAO,EAAE;YAC1B,KAAK,EAAE,SAAS;YAChB,QAAQ,EAAE,GAAG;YACb,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,OAAO,EAAE,IAAI;SACd,CAAC,CAAA;IACJ,CAAC;IAED,0EAA0E;IAC1E,IAAI,CAAC,SAAiB,EAAE,KAAgC;QACtD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;YAChC,OAAM;QACR,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAA;IAC3C,CAAC;IAED,yEAAyE;IACzE,OAAO,CAAC,SAAiB;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;IAC5C,CAAC;CACF"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Plugin-facing settings/config shapes for the permission-rule engine: the
3
+ * local `permissions` section schema (hand-mirroring the shared cascade
4
+ * schemas — no cross-package dependency), the Config-provided rule set, and
5
+ * the plugin Config schema. Kept separate from the service so the plugin
6
+ * entry stays under the file-size gate.
7
+ *
8
+ * @module @dsh-cc/permission-rules/settings-schema
9
+ */
10
+ import z from '@deepseek-ai/schemastery';
11
+ import { type PermissionMode, type PermissionRuleSource } from './types.ts';
12
+ import type { AutoModeSettings } from './auto-stage.ts';
13
+ /** The settings section resolved from the settings document. */
14
+ export interface PermissionSettings {
15
+ /** Whole-tool or content rules that allow matching calls. */
16
+ allow?: string[];
17
+ /** Whole-tool or content rules that deny matching calls. */
18
+ deny?: string[];
19
+ /** Whole-tool or content rules that route matching calls to approval. */
20
+ ask?: string[];
21
+ /** Default permission mode for sessions without a recorded override. */
22
+ defaultMode?: PermissionMode;
23
+ /** `'disable'` turns off the ability to switch to `bypassPermissions`. */
24
+ disableBypassPermissionsMode?: 'disable';
25
+ /** Additional directories included in the permission scope (escape-check base). */
26
+ additionalDirectories?: string[];
27
+ /** Protected file wildcard patterns — writes to them are high risk. */
28
+ protectedFiles?: string[];
29
+ /** Raw dangerous-command regex sources replacing the curated defaults. */
30
+ dangerousPatterns?: string[];
31
+ /**
32
+ * Optional LLM risk-classifier configuration for `auto` mode (hand-mirrors
33
+ * the shared `AutoModeSchema`): `soft_deny` prose list plus a `classifier`
34
+ * sub-object. Absent ⇒ the stage stays disarmed (no defaults materialized).
35
+ */
36
+ autoMode?: AutoModeSettings;
37
+ }
38
+ /** The Config-provided rule set: strings parsed as source-`config` rules. */
39
+ export interface ConfigRules {
40
+ /** Allow rules. */
41
+ allow?: string[];
42
+ /** Deny rules. */
43
+ deny?: string[];
44
+ /** Ask rules. */
45
+ ask?: string[];
46
+ /**
47
+ * Bypass-immune deny rules (e.g. `.git` internals, shell-config paths):
48
+ * enforced through the monotonic guard layer, never overridable by a mode
49
+ * switch or `bypassPermissions`.
50
+ */
51
+ bypassImmune?: string[];
52
+ }
53
+ /** Plugin config. All optional; the schema applies the defaults shown. */
54
+ export interface Config {
55
+ /**
56
+ * The rule set provided directly by composition, parsed with source
57
+ * `config`. Merged with the optional settings section by source priority
58
+ * (settings rules win).
59
+ */
60
+ rules?: ConfigRules;
61
+ /** Settings namespace holding allow/deny/ask/defaultMode; defaults to `permissions`. */
62
+ settingsNamespace?: string;
63
+ /**
64
+ * The source label applied to settings-resolved rules; defaults to
65
+ * `userSettings`. Lets a deployment attribute settings rules to a different
66
+ * settings layer (project/local/…).
67
+ */
68
+ settingsSource?: PermissionRuleSource;
69
+ /** Default mode for sessions without an in-memory mode override; defaults to `default`. */
70
+ defaultMode?: PermissionMode;
71
+ /** Tool name treated as the shell-command tool for content extraction; defaults to `Bash`. */
72
+ bashToolName?: string;
73
+ /** File-edit tool names auto-allowed under `acceptEdits` mode. */
74
+ fileEditTools?: string[];
75
+ /** Read-only tool names auto-allowed under `plan` mode. */
76
+ readOnlyTools?: string[];
77
+ /**
78
+ * Skip a whole-tool `ask` for a sandboxed (confining, non-full-access)
79
+ * `Bash` call — allow instead. Defaults to `false`.
80
+ */
81
+ exemptSandboxedBashFromToolAsk?: boolean;
82
+ /** Whether `bypassPermissions` mode is disabled (falls back to `default`). */
83
+ disableBypassPermissionsMode?: boolean;
84
+ /**
85
+ * Whether the risk-classifier escalation stage runs inside the decision
86
+ * flow (catastrophic commands hard-deny; protected/out-of-scope file writes
87
+ * ask unless under `bypassPermissions`). Defaults to `true`.
88
+ */
89
+ classifierEnabled?: boolean;
90
+ }
91
+ /** The standard file-edit tool set, applied when {@link Config.fileEditTools} is omitted. */
92
+ export declare const DEFAULT_FILE_EDIT_TOOLS: string[];
93
+ /** The standard read-only tool set, applied when {@link Config.readOnlyTools} is omitted. */
94
+ export declare const DEFAULT_READ_ONLY_TOOLS: string[];
95
+ /** The shared settings schema (Config-facing and settings-provider-facing). */
96
+ export declare function permissionSettingsSchema(): z<PermissionSettings>;
97
+ /** The plugin Config schema (defaults applied by schemastery). */
98
+ export declare const ConfigSchema: z<Config>;
99
+ //# sourceMappingURL=settings-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-schema.d.ts","sourceRoot":"","sources":["../src/settings-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,CAAC,MAAM,0BAA0B,CAAA;AACxC,OAAO,EAAqC,KAAK,cAAc,EAAE,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAC9G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,gEAAgE;AAChE,MAAM,WAAW,kBAAkB;IACjC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,wEAAwE;IACxE,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,0EAA0E;IAC1E,4BAA4B,CAAC,EAAE,SAAS,CAAA;IACxC,mFAAmF;IACnF,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAA;IAChC,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAA;CAC5B;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,mBAAmB;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,iBAAiB;IACjB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,0EAA0E;AAC1E,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAA;IACrC,2FAA2F;IAC3F,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC,8EAA8E;IAC9E,4BAA4B,CAAC,EAAE,OAAO,CAAA;IACtC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,6FAA6F;AAC7F,eAAO,MAAM,uBAAuB,UAAyE,CAAA;AAE7G,6FAA6F;AAC7F,eAAO,MAAM,uBAAuB,UAAgE,CAAA;AAUpG,+EAA+E;AAC/E,wBAAgB,wBAAwB,IAAI,CAAC,CAAC,kBAAkB,CAAC,CAqBhE;AAED,kEAAkE;AAClE,eAAO,MAAM,YAAY,EAAE,CAAC,CAAC,MAAM,CAgBjC,CAAA"}