@stratusagent/permissions 0.4.0 → 0.6.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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The command-scope engine: which *invocations* of a shell tool may run
3
+ * unattended.
4
+ *
5
+ * `ToolRisk` classifies a tool. That is too coarse for a shell, whose calls
6
+ * range from `git status` to `curl … | sh`, so this narrows a `gated` tool
7
+ * one call at a time — a base command plus the argument shapes that keep it
8
+ * read-only, never a bare executable and never a whole command string.
9
+ *
10
+ * Parsing is deliberately dumb, and that is the design rather than a
11
+ * shortcut: tokenize, take the base command, scan for control operators.
12
+ * Anything ambiguous is "not safe", so the failure mode of a parser that
13
+ * disagrees with `sh` is a prompt somebody has to answer, not a command
14
+ * nobody vetted.
15
+ */
16
+ /** One narrow permission to run a command: what, and in which forms. */
17
+ export interface CommandScope {
18
+ /** The base command, matched exactly — never a path (`/bin/git` is not `git`). */
19
+ command: string;
20
+ /** Literal arguments that must follow it, in order (`['push']`). */
21
+ args?: string[];
22
+ /**
23
+ * Long flags (`--force`) and short letters (`f`) that disqualify a match.
24
+ * Short letters are checked inside bundles, so `-fdx` trips on `f`.
25
+ */
26
+ deniedFlags?: string[];
27
+ /** Literal arguments that disqualify a match anywhere after `args`. */
28
+ deniedArgs?: string[];
29
+ /**
30
+ * Refuse *any* argument that is not a flag.
31
+ *
32
+ * The listing forms of `git branch` and `git tag` are the safe ones, and
33
+ * what separates them from the creating forms is a positional: `git
34
+ * branch` lists, `git branch release` creates. Excluding flags is not
35
+ * enough there, because creation needs none.
36
+ */
37
+ listOnly?: boolean;
38
+ /**
39
+ * The only flags this scope permits. Present, it replaces "anything not
40
+ * denied" with "nothing but these" — which is the direction to fail in
41
+ * for a command whose flags can mutate: `git branch --unset-upstream`
42
+ * changes repository config, takes no positional, and is not a delete or
43
+ * a force, so a deny list has to have thought of it and an allow list
44
+ * does not.
45
+ *
46
+ * Long flags match by name, so `--sort=-committerdate` is `--sort`.
47
+ * Short flags match per letter inside a bundle, and digits are ignored
48
+ * because they are arguments rather than flags (`git tag -n5`).
49
+ */
50
+ allowedFlags?: string[];
51
+ /**
52
+ * Refuse `:branch` and `+branch` arguments. Git's refspec syntax makes
53
+ * those a delete and a forced update *without any flag*, so a scope that
54
+ * only excluded flags would let `git push origin :main` through as an
55
+ * ordinary push.
56
+ */
57
+ denyRefspecForms?: boolean;
58
+ }
59
+ /**
60
+ * The commands that run unattended out of the box.
61
+ *
62
+ * Short on purpose. Every entry is a promise that no argument shape reaches
63
+ * outside the repository it is run in, and the tempting additions —
64
+ * `cat`, `ls`, `grep` — cannot make it: they read whatever path they are
65
+ * given, and `cat ~/.stratus/credentials.json` is a safe-listed credential
66
+ * read. An operator who wants those adds them deliberately, or approves
67
+ * them once and keeps the scope.
68
+ *
69
+ * The test is *what an argument can make the command do*, not what the
70
+ * command is called. `date` was on this list until someone pointed out
71
+ * `date --file=~/.stratus/credentials.json`, which is not a date lookup:
72
+ * GNU `date` reads that file and echoes each unparseable line back in its
73
+ * error text, which the shell tool returns. A command that can be handed a
74
+ * path is a file reader wearing another name.
75
+ */
76
+ export declare const SAFE_COMMAND_SCOPES: CommandScope[];
77
+ export interface CommandAnalysis {
78
+ /** The command as written, for messages and prompts. */
79
+ command: string;
80
+ /** The base command, absent when the string could not be tokenized. */
81
+ base?: string;
82
+ tokens: string[];
83
+ /**
84
+ * Why this invocation cannot be auto-approved at all — a control
85
+ * operator, an unbalanced quote, an absolute path. Undefined means it is
86
+ * a candidate for scope matching, not that it is allowed.
87
+ */
88
+ disqualifiedBy?: string;
89
+ }
90
+ /** Read a command string as far as it can be read safely. */
91
+ export declare const analyzeCommand: (command: string) => CommandAnalysis;
92
+ /** Whether an invocation falls inside one scope. */
93
+ export declare const matchesScope: (analysis: CommandAnalysis, scope: CommandScope) => boolean;
94
+ /** The first scope covering this invocation, if any covers it. */
95
+ export declare const findMatchingScope: (analysis: CommandAnalysis, scopes: readonly CommandScope[]) => CommandScope | undefined;
96
+ /**
97
+ * The scope an "always allow" persists.
98
+ *
99
+ * Base command plus its first literal argument — `git push`, not
100
+ * `git push origin main` (too narrow to be worth storing) and not `git`
101
+ * (too broad to be safe) — carrying the destructive-form constraints that
102
+ * make the distinction real, and inheriting anything the safe list already
103
+ * excludes for the same command so a persisted scope can never erase a
104
+ * flag distinction the built-in list draws.
105
+ */
106
+ export declare const normalizeCommandScope: (analysis: CommandAnalysis) => CommandScope | undefined;
107
+ /** One line an operator can read in a log or a whitelist listing. */
108
+ export declare const describeCommandScope: (scope: CommandScope) => string;
109
+ /** Whether two scopes permit the same thing, so a whitelist does not grow duplicates. */
110
+ export declare const sameScope: (left: CommandScope, right: CommandScope) => boolean;
111
+ /** Read one scope out of a whitelist file, or refuse it. */
112
+ export declare const parseCommandScope: (raw: unknown) => CommandScope | undefined;
113
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA+CD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAAY,EAoD7C,CAAC;AAqBF,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA4CD,6DAA6D;AAC7D,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,KAAG,eA0BhD,CAAC;AAgCF,oDAAoD;AACpD,eAAO,MAAM,YAAY,GAAI,UAAU,eAAe,EAAE,OAAO,YAAY,KAAG,OA0C7E,CAAC;AAEF,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,GAC5B,UAAU,eAAe,EACzB,QAAQ,SAAS,YAAY,EAAE,KAC9B,YAAY,GAAG,SAAkE,CAAC;AAErF;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,GAAI,UAAU,eAAe,KAAG,YAAY,GAAG,SAgChF,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,oBAAoB,GAAI,OAAO,YAAY,KAAG,MACT,CAAC;AAEnD,yFAAyF;AACzF,eAAO,MAAM,SAAS,GAAI,MAAM,YAAY,EAAE,OAAO,YAAY,KAAG,OACsB,CAAC;AAY3F,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB,GAAI,KAAK,OAAO,KAAG,YAAY,GAAG,SAwB/D,CAAC"}
@@ -0,0 +1,363 @@
1
+ /**
2
+ * The command-scope engine: which *invocations* of a shell tool may run
3
+ * unattended.
4
+ *
5
+ * `ToolRisk` classifies a tool. That is too coarse for a shell, whose calls
6
+ * range from `git status` to `curl … | sh`, so this narrows a `gated` tool
7
+ * one call at a time — a base command plus the argument shapes that keep it
8
+ * read-only, never a bare executable and never a whole command string.
9
+ *
10
+ * Parsing is deliberately dumb, and that is the design rather than a
11
+ * shortcut: tokenize, take the base command, scan for control operators.
12
+ * Anything ambiguous is "not safe", so the failure mode of a parser that
13
+ * disagrees with `sh` is a prompt somebody has to answer, not a command
14
+ * nobody vetted.
15
+ */
16
+ /**
17
+ * Flags that turn something else into a program, a network peer, or a file
18
+ * write — and are therefore refused in every scope, safe-listed or
19
+ * persisted. `git -c core.pager=…` and `git diff --no-index /etc/passwd`
20
+ * are the reason: both are read-only commands by name.
21
+ */
22
+ const ALWAYS_DENIED_FLAGS = [
23
+ '-c',
24
+ '--config',
25
+ '--config-env',
26
+ '--exec',
27
+ '--exec-path',
28
+ '--upload-pack',
29
+ '--receive-pack',
30
+ '--ext-diff',
31
+ '--no-index',
32
+ '--output',
33
+ '--open-files-in-pager',
34
+ // Reads a path the scope never mentioned. `date --file=…/credentials.json`
35
+ // is not a date lookup; it is a file read whose contents come back in the
36
+ // error text, from a command whose name says otherwise.
37
+ '--file',
38
+ ];
39
+ /**
40
+ * What a persisted scope excludes, whatever it was approved for. Approving
41
+ * `git push origin main` must not persist a permission that covers
42
+ * `git push --force` — the point of storing a scope rather than a command
43
+ * string is to be useful next time, and the point of storing a scope rather
44
+ * than the bare executable is that it still says no to this.
45
+ */
46
+ const DESTRUCTIVE_FLAGS = [
47
+ '--force',
48
+ '--force-with-lease',
49
+ '--force-if-includes',
50
+ '--delete',
51
+ '--prune',
52
+ '--mirror',
53
+ '--hard',
54
+ '--no-verify',
55
+ 'f',
56
+ 'd',
57
+ 'D',
58
+ ];
59
+ /**
60
+ * The commands that run unattended out of the box.
61
+ *
62
+ * Short on purpose. Every entry is a promise that no argument shape reaches
63
+ * outside the repository it is run in, and the tempting additions —
64
+ * `cat`, `ls`, `grep` — cannot make it: they read whatever path they are
65
+ * given, and `cat ~/.stratus/credentials.json` is a safe-listed credential
66
+ * read. An operator who wants those adds them deliberately, or approves
67
+ * them once and keeps the scope.
68
+ *
69
+ * The test is *what an argument can make the command do*, not what the
70
+ * command is called. `date` was on this list until someone pointed out
71
+ * `date --file=~/.stratus/credentials.json`, which is not a date lookup:
72
+ * GNU `date` reads that file and echoes each unparseable line back in its
73
+ * error text, which the shell tool returns. A command that can be handed a
74
+ * path is a file reader wearing another name.
75
+ */
76
+ export const SAFE_COMMAND_SCOPES = [
77
+ { command: 'git', args: ['status'] },
78
+ { command: 'git', args: ['log'] },
79
+ { command: 'git', args: ['diff'] },
80
+ { command: 'git', args: ['show'] },
81
+ { command: 'git', args: ['blame'] },
82
+ { command: 'git', args: ['describe'] },
83
+ { command: 'git', args: ['rev-parse'] },
84
+ { command: 'git', args: ['ls-files'] },
85
+ { command: 'git', args: ['shortlog'] },
86
+ // Listing branches, tags, and remotes is read-only. Creating one needs no
87
+ // flag at all — `git branch release` is a mutation and `git branch` is
88
+ // not — so these are list-only, and every other form asks.
89
+ {
90
+ command: 'git',
91
+ args: ['branch'],
92
+ listOnly: true,
93
+ // Named rather than excluded: `git branch` has flag-only mutations
94
+ // (`--unset-upstream`, `-u origin/main`) that no list of destructive
95
+ // *shapes* would have caught, so this scope covers the listing flags
96
+ // and nothing else.
97
+ allowedFlags: [
98
+ '--list', '--all', '--remotes', '--show-current', '--verbose', '--no-verbose',
99
+ '--color', '--no-color', '--column', '--no-column', '--sort', '--format',
100
+ '--contains', '--no-contains', '--merged', '--no-merged', '--points-at',
101
+ '--ignore-case', '--omit-empty', '--abbrev', '--no-abbrev',
102
+ 'l', 'a', 'r', 'v', 'i', 'q',
103
+ ],
104
+ deniedFlags: ['--delete', '--move', '--copy', '--force', 'd', 'D', 'm', 'M', 'C', 'u'],
105
+ },
106
+ {
107
+ command: 'git',
108
+ args: ['tag'],
109
+ listOnly: true,
110
+ allowedFlags: [
111
+ '--list', '--contains', '--no-contains', '--merged', '--no-merged', '--points-at',
112
+ '--sort', '--format', '--color', '--no-color', '--column', '--no-column',
113
+ '--ignore-case', '--omit-empty',
114
+ 'l', 'n', 'i',
115
+ ],
116
+ deniedFlags: ['--delete', '--force', 'd', 'f'],
117
+ },
118
+ {
119
+ command: 'git',
120
+ args: ['remote'],
121
+ listOnly: true,
122
+ allowedFlags: ['--verbose', 'v'],
123
+ deniedArgs: ['add', 'remove', 'rm', 'rename', 'set-url', 'set-head', 'set-branches', 'prune', 'update'],
124
+ },
125
+ { command: 'pwd' },
126
+ { command: 'whoami' },
127
+ { command: 'uname' },
128
+ ];
129
+ /**
130
+ * Every shell control operator, as a rejection set rather than a blacklist
131
+ * of the memorable ones. A single `&` backgrounds a command as surely as
132
+ * `&&` chains one, and a newline runs the next line whatever came before
133
+ * it — an enumerated list is exactly how `git status\ncurl evil.sh` gets
134
+ * auto-approved.
135
+ */
136
+ const CONTROL_OPERATORS = [
137
+ { pattern: /\$\(/, name: 'command substitution ($( ))' },
138
+ { pattern: /`/, name: 'command substitution (backticks)' },
139
+ { pattern: /\|/, name: 'a pipe (|)' },
140
+ { pattern: /&/, name: 'an ampersand (& or &&)' },
141
+ { pattern: /;/, name: 'a semicolon (;)' },
142
+ { pattern: /[\r\n]/, name: 'a newline' },
143
+ { pattern: /[()]/, name: 'a subshell (parentheses)' },
144
+ { pattern: /[<>]/, name: 'a redirection (< or >)' },
145
+ { pattern: /\$\{/, name: 'a parameter expansion (${ })' },
146
+ ];
147
+ const tokenize = (command) => {
148
+ const tokens = [];
149
+ let current = '';
150
+ let quote;
151
+ let started = false;
152
+ for (const char of command) {
153
+ if (quote) {
154
+ if (char === quote) {
155
+ quote = undefined;
156
+ }
157
+ else {
158
+ current += char;
159
+ }
160
+ continue;
161
+ }
162
+ if (char === '"' || char === "'") {
163
+ quote = char;
164
+ started = true;
165
+ continue;
166
+ }
167
+ if (char === ' ' || char === '\t') {
168
+ if (started) {
169
+ tokens.push(current);
170
+ current = '';
171
+ started = false;
172
+ }
173
+ continue;
174
+ }
175
+ current += char;
176
+ started = true;
177
+ }
178
+ if (quote) {
179
+ // An unbalanced quote means this parser and the shell disagree about
180
+ // where the command ends. There is no safe reading of that.
181
+ return undefined;
182
+ }
183
+ if (started) {
184
+ tokens.push(current);
185
+ }
186
+ return tokens;
187
+ };
188
+ /** Read a command string as far as it can be read safely. */
189
+ export const analyzeCommand = (command) => {
190
+ for (const operator of CONTROL_OPERATORS) {
191
+ if (operator.pattern.test(command)) {
192
+ return { command, tokens: [], disqualifiedBy: `it contains ${operator.name}` };
193
+ }
194
+ }
195
+ const tokens = tokenize(command);
196
+ if (!tokens || tokens.length === 0) {
197
+ return { command, tokens: [], disqualifiedBy: 'it could not be read as a command' };
198
+ }
199
+ const base = tokens[0];
200
+ if (base.includes('/') || base.includes('\\')) {
201
+ // A scope names a command, and `/usr/bin/git` is not that name. Refusing
202
+ // beats resolving: `./git` in a cloned repository is a different program
203
+ // with the same basename.
204
+ return { command, tokens, disqualifiedBy: 'it names a path rather than a command' };
205
+ }
206
+ if (base.startsWith('-') || base.includes('=')) {
207
+ // `FOO=bar cmd` is an environment assignment, which is the shell's job
208
+ // and not this parser's.
209
+ return { command, tokens, disqualifiedBy: 'it does not start with a command' };
210
+ }
211
+ return { command, base, tokens };
212
+ };
213
+ const flagsOf = (token) => {
214
+ if (token.startsWith('--')) {
215
+ const [name] = token.split('=');
216
+ return { long: name ?? token, shorts: [] };
217
+ }
218
+ if (token.startsWith('-') && token.length > 1) {
219
+ const [bundle] = token.slice(1).split('=');
220
+ return { shorts: [...(bundle ?? '')] };
221
+ }
222
+ return { shorts: [] };
223
+ };
224
+ const deniesFlag = (denied, token) => {
225
+ const { long, shorts } = flagsOf(token);
226
+ if (long && denied.includes(long)) {
227
+ return true;
228
+ }
229
+ return shorts.some((letter) => denied.includes(letter));
230
+ };
231
+ /** Whether every part of a flag token is on an allowlist. */
232
+ const allowsFlag = (allowed, token) => {
233
+ const { long, shorts } = flagsOf(token);
234
+ if (long) {
235
+ return allowed.includes(long);
236
+ }
237
+ const letters = shorts.filter((letter) => !/[0-9]/.test(letter));
238
+ return letters.length > 0 && letters.every((letter) => allowed.includes(letter));
239
+ };
240
+ /** Whether an invocation falls inside one scope. */
241
+ export const matchesScope = (analysis, scope) => {
242
+ if (analysis.disqualifiedBy || analysis.base === undefined) {
243
+ return false;
244
+ }
245
+ if (analysis.base !== scope.command) {
246
+ return false;
247
+ }
248
+ const args = analysis.tokens.slice(1);
249
+ const required = scope.args ?? [];
250
+ if (args.length < required.length) {
251
+ return false;
252
+ }
253
+ for (const [index, expected] of required.entries()) {
254
+ if (args[index] !== expected) {
255
+ return false;
256
+ }
257
+ }
258
+ const denied = [...ALWAYS_DENIED_FLAGS, ...(scope.deniedFlags ?? [])];
259
+ const rest = args.slice(required.length);
260
+ for (const token of rest) {
261
+ if (token.startsWith('-')) {
262
+ if (deniesFlag(denied, token)) {
263
+ return false;
264
+ }
265
+ if (scope.allowedFlags && !allowsFlag(scope.allowedFlags, token)) {
266
+ return false;
267
+ }
268
+ continue;
269
+ }
270
+ if (scope.listOnly) {
271
+ return false;
272
+ }
273
+ if (scope.deniedArgs?.includes(token)) {
274
+ return false;
275
+ }
276
+ if (scope.denyRefspecForms && (token.startsWith(':') || token.startsWith('+'))) {
277
+ return false;
278
+ }
279
+ }
280
+ return true;
281
+ };
282
+ /** The first scope covering this invocation, if any covers it. */
283
+ export const findMatchingScope = (analysis, scopes) => scopes.find((scope) => matchesScope(analysis, scope));
284
+ /**
285
+ * The scope an "always allow" persists.
286
+ *
287
+ * Base command plus its first literal argument — `git push`, not
288
+ * `git push origin main` (too narrow to be worth storing) and not `git`
289
+ * (too broad to be safe) — carrying the destructive-form constraints that
290
+ * make the distinction real, and inheriting anything the safe list already
291
+ * excludes for the same command so a persisted scope can never erase a
292
+ * flag distinction the built-in list draws.
293
+ */
294
+ export const normalizeCommandScope = (analysis) => {
295
+ if (analysis.disqualifiedBy || analysis.base === undefined) {
296
+ return undefined;
297
+ }
298
+ const first = analysis.tokens.slice(1).find((token) => !token.startsWith('-'));
299
+ const args = first === undefined ? [] : [first];
300
+ const sameScope = SAFE_COMMAND_SCOPES
301
+ .filter((scope) => scope.command === analysis.base && (scope.args ?? []).join(' ') === args.join(' '));
302
+ const inherited = sameScope.flatMap((scope) => scope.deniedFlags ?? []);
303
+ const deniedArgs = sameScope.flatMap((scope) => scope.deniedArgs ?? []);
304
+ // Only when every safe scope for this command names one: a persisted
305
+ // scope must be no wider than the built-in, and an allowlist from one of
306
+ // two scopes would narrow the other by accident.
307
+ const allowedFlags = sameScope.length > 0 && sameScope.every((scope) => scope.allowedFlags)
308
+ ? [...new Set(sameScope.flatMap((scope) => scope.allowedFlags ?? []))]
309
+ : undefined;
310
+ return {
311
+ command: analysis.base,
312
+ ...(args.length > 0 ? { args } : {}),
313
+ deniedFlags: [...new Set([...DESTRUCTIVE_FLAGS, ...inherited])],
314
+ ...(deniedArgs.length > 0 ? { deniedArgs: [...new Set(deniedArgs)] } : {}),
315
+ // A persisted scope cannot be wider than the safe list's own for the
316
+ // same command: approving `git branch` once must not turn creating a
317
+ // branch into something that runs unattended forever after.
318
+ ...(sameScope.some((scope) => scope.listOnly) ? { listOnly: true } : {}),
319
+ ...(allowedFlags ? { allowedFlags } : {}),
320
+ // Git's syntax, so git's rule: elsewhere a leading `+` is an ordinary
321
+ // argument (`chmod +x`) and refusing it would only cost a prompt for no
322
+ // safety.
323
+ ...(analysis.base === 'git' ? { denyRefspecForms: true } : {}),
324
+ };
325
+ };
326
+ /** One line an operator can read in a log or a whitelist listing. */
327
+ export const describeCommandScope = (scope) => [scope.command, ...(scope.args ?? [])].join(' ');
328
+ /** Whether two scopes permit the same thing, so a whitelist does not grow duplicates. */
329
+ export const sameScope = (left, right) => JSON.stringify(normalizeForCompare(left)) === JSON.stringify(normalizeForCompare(right));
330
+ const normalizeForCompare = (scope) => ({
331
+ command: scope.command,
332
+ args: scope.args ?? [],
333
+ deniedFlags: [...(scope.deniedFlags ?? [])].sort(),
334
+ deniedArgs: [...(scope.deniedArgs ?? [])].sort(),
335
+ denyRefspecForms: scope.denyRefspecForms ?? false,
336
+ listOnly: scope.listOnly ?? false,
337
+ allowedFlags: [...(scope.allowedFlags ?? [])].sort(),
338
+ });
339
+ /** Read one scope out of a whitelist file, or refuse it. */
340
+ export const parseCommandScope = (raw) => {
341
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
342
+ return undefined;
343
+ }
344
+ const source = raw;
345
+ if (typeof source.command !== 'string' || source.command.length === 0) {
346
+ return undefined;
347
+ }
348
+ const strings = (value) => Array.isArray(value) && value.every((entry) => typeof entry === 'string') ? value : undefined;
349
+ const args = strings(source.args);
350
+ const deniedFlags = strings(source.deniedFlags);
351
+ const deniedArgs = strings(source.deniedArgs);
352
+ const allowedFlags = strings(source.allowedFlags);
353
+ return {
354
+ command: source.command,
355
+ ...(args ? { args } : {}),
356
+ ...(deniedFlags ? { deniedFlags } : {}),
357
+ ...(deniedArgs ? { deniedArgs } : {}),
358
+ ...(source.denyRefspecForms === true ? { denyRefspecForms: true } : {}),
359
+ ...(source.listOnly === true ? { listOnly: true } : {}),
360
+ ...(allowedFlags ? { allowedFlags } : {}),
361
+ };
362
+ };
363
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AA8CH;;;;;GAKG;AACH,MAAM,mBAAmB,GAAG;IAC1B,IAAI;IACJ,UAAU;IACV,cAAc;IACd,QAAQ;IACR,aAAa;IACb,eAAe;IACf,gBAAgB;IAChB,YAAY;IACZ,YAAY;IACZ,UAAU;IACV,uBAAuB;IACvB,2EAA2E;IAC3E,0EAA0E;IAC1E,wDAAwD;IACxD,QAAQ;CACT,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,iBAAiB,GAAG;IACxB,SAAS;IACT,oBAAoB;IACpB,qBAAqB;IACrB,UAAU;IACV,SAAS;IACT,UAAU;IACV,QAAQ;IACR,aAAa;IACb,GAAG;IACH,GAAG;IACH,GAAG;CACJ,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAmB;IACjD,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE;IACpC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE;IACjC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE;IAClC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE;IAClC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE;IACnC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE;IACtC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE;IACvC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE;IACtC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE;IACtC,0EAA0E;IAC1E,uEAAuE;IACvE,2DAA2D;IAC3D;QACE,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,QAAQ,CAAC;QAChB,QAAQ,EAAE,IAAI;QACd,mEAAmE;QACnE,qEAAqE;QACrE,qEAAqE;QACrE,oBAAoB;QACpB,YAAY,EAAE;YACZ,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc;YAC7E,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU;YACxE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa;YACvE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa;YAC1D,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;SAC7B;QACD,WAAW,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;KACvF;IACD;QACE,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,KAAK,CAAC;QACb,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE;YACZ,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa;YACjF,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa;YACxE,eAAe,EAAE,cAAc;YAC/B,GAAG,EAAE,GAAG,EAAE,GAAG;SACd;QACD,WAAW,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC;KAC/C;IACD;QACE,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,QAAQ,CAAC;QAChB,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,CAAC,WAAW,EAAE,GAAG,CAAC;QAChC,UAAU,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC;KACxG;IACD,EAAE,OAAO,EAAE,KAAK,EAAE;IAClB,EAAE,OAAO,EAAE,QAAQ,EAAE;IACrB,EAAE,OAAO,EAAE,OAAO,EAAE;CACrB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,iBAAiB,GAA6C;IAClE,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,6BAA6B,EAAE;IACxD,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,kCAAkC,EAAE;IAC1D,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE;IACrC,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,wBAAwB,EAAE;IAChD,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,iBAAiB,EAAE;IACzC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE;IACxC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE;IACrD,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,EAAE;IACnD,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,8BAA8B,EAAE;CAC1D,CAAC;AAgBF,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAwB,EAAE;IACzD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,IAAI,KAA4B,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;gBACnB,KAAK,GAAG,SAAS,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACN,OAAO,IAAI,IAAI,CAAC;YAClB,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjC,KAAK,GAAG,IAAI,CAAC;YACb,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClC,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,GAAG,KAAK,CAAC;YAClB,CAAC;YACD,SAAS;QACX,CAAC;QACD,OAAO,IAAI,IAAI,CAAC;QAChB,OAAO,GAAG,IAAI,CAAC;IACjB,CAAC;IACD,IAAI,KAAK,EAAE,CAAC;QACV,qEAAqE;QACrE,4DAA4D;QAC5D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AAEF,6DAA6D;AAC7D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAe,EAAmB,EAAE;IACjE,KAAK,MAAM,QAAQ,IAAI,iBAAiB,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,eAAe,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACjF,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE,CAAC;IACtF,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAW,CAAC;IACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9C,yEAAyE;QACzE,yEAAyE;QACzE,0BAA0B;QAC1B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,uCAAuC,EAAE,CAAC;IACtF,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,uEAAuE;QACvE,yBAAyB;QACzB,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,kCAAkC,EAAE,CAAC;IACjF,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,KAAa,EAAuC,EAAE;IACrE,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,IAAI,IAAI,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3C,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;IACzC,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,MAAgB,EAAE,KAAa,EAAW,EAAE;IAC9D,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF,6DAA6D;AAC7D,MAAM,UAAU,GAAG,CAAC,OAAiB,EAAE,KAAa,EAAW,EAAE;IAC/D,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IACjE,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AACnF,CAAC,CAAC;AAEF,oDAAoD;AACpD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAyB,EAAE,KAAmB,EAAW,EAAE;IACtF,IAAI,QAAQ,CAAC,cAAc,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3D,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACnD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,GAAG,mBAAmB,EAAE,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzC,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC9B,OAAO,KAAK,CAAC;YACf,CAAC;YACD,IAAI,KAAK,CAAC,YAAY,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;gBACjE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACtC,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC/E,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,QAAyB,EACzB,MAA+B,EACL,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AAErF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,QAAyB,EAA4B,EAAE;IAC3F,IAAI,QAAQ,CAAC,cAAc,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC3D,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/E,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,mBAAmB;SAClC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACzG,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;IACxE,qEAAqE;IACrE,yEAAyE;IACzE,iDAAiD;IACjD,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC;QACzF,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC;QACtE,CAAC,CAAC,SAAS,CAAC;IAEd,OAAO;QACL,OAAO,EAAE,QAAQ,CAAC,IAAI;QACtB,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpC,WAAW,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,iBAAiB,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;QAC/D,GAAG,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,qEAAqE;QACrE,qEAAqE;QACrE,4DAA4D;QAC5D,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,sEAAsE;QACtE,wEAAwE;QACxE,UAAU;QACV,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC/D,CAAC;AACJ,CAAC,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAmB,EAAU,EAAE,CAClE,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEnD,yFAAyF;AACzF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAkB,EAAE,KAAmB,EAAW,EAAE,CAC5E,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;AAE3F,MAAM,mBAAmB,GAAG,CAAC,KAAmB,EAAE,EAAE,CAAC,CAAC;IACpD,OAAO,EAAE,KAAK,CAAC,OAAO;IACtB,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;IACtB,WAAW,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;IAClD,UAAU,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;IAChD,gBAAgB,EAAE,KAAK,CAAC,gBAAgB,IAAI,KAAK;IACjD,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;IACjC,YAAY,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;CACrD,CAAC,CAAC;AAEH,4DAA4D;AAC5D,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAY,EAA4B,EAAE;IAC1E,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,GAA8B,CAAC;IAC9C,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,KAAc,EAAwB,EAAE,CACvD,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAE,KAAkB,CAAC,CAAC,CAAC,SAAS,CAAC;IAE9G,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAClD,OAAO;QACL,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,MAAM,CAAC,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvE,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvD,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC;AACJ,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,14 @@
1
1
  import type { ApprovalAnswer, ApprovalPolicy, Session, Tool, ToolCall, ToolRisk } from '@stratusagent/core';
2
+ /**
3
+ * Re-exported, not re-implemented: the risk ordering moved to the kernel
4
+ * when the plugin loader became its second consumer, and this package
5
+ * published the name first.
6
+ */
7
+ export { atLeastAsRisky } from '@stratusagent/core';
8
+ import { type CommandScope } from './commands.ts';
9
+ import type { CommandWhitelistStore } from './whitelist.ts';
10
+ export { analyzeCommand, describeCommandScope, findMatchingScope, matchesScope, normalizeCommandScope, parseCommandScope, sameScope, SAFE_COMMAND_SCOPES, type CommandAnalysis, type CommandScope, } from './commands.ts';
11
+ export { createFileCommandWhitelist, whitelistPathFor, type CommandWhitelistStore, } from './whitelist.ts';
2
12
  /**
3
13
  * How a policy reaches a human — or admits that it cannot.
4
14
  *
@@ -57,8 +67,26 @@ export interface PermissionDecision {
57
67
  toolName: string;
58
68
  sessionId: string;
59
69
  agentId: string;
60
- /** One line, written for a person reading a daemon log at 3am. */
70
+ /**
71
+ * One line, written for a person reading a daemon log at 3am — and
72
+ * deliberately free of the tool's input.
73
+ *
74
+ * The daemon log is a trace, not a second transcript: it records that a
75
+ * tool ran, never what it was called with. A refusal that quoted the
76
+ * command would put an agent-composed string — a URL, a token somebody
77
+ * pasted into a prompt — into a file this project documents as safe to
78
+ * read and to share. What the reason names instead is the *scope*: the
79
+ * base command and its subcommand, which is the actionable half and is a
80
+ * classification rather than the arguments.
81
+ */
61
82
  reason: string;
83
+ /**
84
+ * The command this decision was about, for a surface that is showing a
85
+ * person the thing they are approving — a Slack prompt, a live console.
86
+ * Carried separately precisely so it is not in `reason`, and a consumer
87
+ * that logs it is making that choice knowingly.
88
+ */
89
+ command?: string;
62
90
  }
63
91
  export interface PermissionPolicyOptions {
64
92
  mode: PermissionMode;
@@ -75,18 +103,43 @@ export interface PermissionPolicyOptions {
75
103
  * from an agent that chose not to act.
76
104
  */
77
105
  onDecision?: (decision: PermissionDecision) => void;
106
+ /**
107
+ * The command-scope engine, for tools that carry a command string
108
+ * (`Tool.commandFor`). Omitted, every gated call needs a human however
109
+ * innocuous its arguments — which is the honest behaviour for a daemon
110
+ * with no scope list, and an unusable one for a shell.
111
+ */
112
+ commands?: CommandScopeOptions;
113
+ }
114
+ export interface CommandScopeOptions {
115
+ /**
116
+ * Scopes that run unattended. Defaults to `SAFE_COMMAND_SCOPES`; pass a
117
+ * list to replace it, or spread it to extend.
118
+ */
119
+ safeScopes?: readonly CommandScope[];
120
+ /** Where "always allow" persists a scope, and where one is read back. */
121
+ whitelist?: CommandWhitelistStore;
122
+ /**
123
+ * Called when a scope is persisted. The daemon logs it: an approval that
124
+ * widens what runs unattended, for every future session, is exactly the
125
+ * decision that must not be the one leaving no trace.
126
+ */
127
+ onScopeRemembered?: (event: {
128
+ agentId: string;
129
+ scope: CommandScope;
130
+ }) => void;
78
131
  }
79
- /** Whether `risk` is at least as risky as `floor`. */
80
- export declare const atLeastAsRisky: (risk: ToolRisk, floor: ToolRisk) => boolean;
81
132
  /**
82
- * The kernel's ApprovalPolicy, deciding by the tool's declared risk rather
83
- * than by its name. `safe` runs unattended; anything else needs a human,
84
- * and whether one exists is what `mode` answers.
133
+ * The kernel's ApprovalPolicy, deciding by the tool's declared risk and —
134
+ * for a tool that carries one — by the command an invocation would run.
135
+ * `safe` runs unattended; anything else needs a human unless a scope covers
136
+ * it, and whether a human exists at all is what `mode` answers.
85
137
  *
86
- * This is deliberately coarse. Fine-grained judgment — which *arguments* to
87
- * a shell tool are safe, which flags turn a read into a write — belongs with
88
- * the shell tool itself, which does not exist yet; writing that machinery
89
- * now would mean guessing at the shape of a caller nobody has built.
138
+ * The three tiers resolve in order: scopes granted "always" in this session,
139
+ * then the agent's persistent whitelist, then the built-in safe list —
140
+ * after which there is nothing left but asking. A `dangerous` tool skips
141
+ * the scope engine entirely: its risk is a statement about the tool, and no
142
+ * argument shape makes `rm -rf` a read.
90
143
  */
91
144
  export declare const createPermissionPolicy: (options: PermissionPolicyOptions) => ApprovalPolicy;
92
145
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EACd,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEtF,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB;;;OAGG;IACH,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,kEAAkE;IAClE,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACrD;AAID,sDAAsD;AACtD,eAAO,MAAM,cAAc,GAAI,MAAM,QAAQ,EAAE,OAAO,QAAQ,KAAG,OAC1B,CAAC;AAuHxC;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,uBAAuB,KAAG,cAqFzE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EACd,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAE5B;;;;GAIG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,YAAY,EACZ,qBAAqB,EACrB,iBAAiB,EACjB,SAAS,EACT,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,0BAA0B,EAC1B,gBAAgB,EAChB,KAAK,qBAAqB,GAC3B,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,QAAQ,CAAC;IACf;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEtF,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,cAAc,CAAC;IACrB;;;OAGG;IACH,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,kEAAkE;IAClE,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACpD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;CAChC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACrC,yEAAyE;IACzE,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,KAAK,IAAI,CAAC;CAC/E;AA6HD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,uBAAuB,KAAG,cAwKzE,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
- const RISK_ORDER = { safe: 0, gated: 1, dangerous: 2 };
2
- /** Whether `risk` is at least as risky as `floor`. */
3
- export const atLeastAsRisky = (risk, floor) => RISK_ORDER[risk] >= RISK_ORDER[floor];
1
+ /**
2
+ * Re-exported, not re-implemented: the risk ordering moved to the kernel
3
+ * when the plugin loader became its second consumer, and this package
4
+ * published the name first.
5
+ */
6
+ export { atLeastAsRisky } from '@stratusagent/core';
7
+ import { analyzeCommand, describeCommandScope, findMatchingScope, normalizeCommandScope, SAFE_COMMAND_SCOPES, } from "./commands.js";
8
+ export { analyzeCommand, describeCommandScope, findMatchingScope, matchesScope, normalizeCommandScope, parseCommandScope, sameScope, SAFE_COMMAND_SCOPES, } from "./commands.js";
9
+ export { createFileCommandWhitelist, whitelistPathFor, } from "./whitelist.js";
4
10
  const YES = new Set(['y', 'yes', 'always', 'a']);
5
11
  const ALWAYS = new Set(['always', 'a']);
6
12
  const ABORTED = Symbol('aborted');
@@ -57,9 +63,14 @@ const sessionKey = (sessionId, toolName) => `${sessionId}\u0000${toolName}`;
57
63
  * text from a person mid-typing, so it is read leniently and anything that
58
64
  * is not recognisably a yes is a no.
59
65
  */
60
- const awaitPrompt = async (context, ask) => {
66
+ const awaitPrompt = async (context, ask, command) => {
61
67
  const { call, risk, session } = context;
62
- const pending = ask(`Allow ${call.toolName} (${risk}) for ${session.agent.name}? [y]es / [a]lways this session / [N]o: `);
68
+ // The command, when there is one: for a shell call the tool name is the
69
+ // least interesting half of the question, and an approver shown only
70
+ // `shell.run (gated)` is being asked to trust something they cannot see.
71
+ const what = command === undefined ? `${call.toolName} (${risk})` : `${call.toolName}: ${command}`;
72
+ const always = command === undefined ? 'always this session' : 'always this scope';
73
+ const pending = ask(`Allow ${what} for ${session.agent.name}? [y]es / [a]lways (${always}) / [N]o: `);
63
74
  // The answer is never read after this point if the turn ends first,
64
75
  // so a late yes cannot execute a tool for work that no longer exists.
65
76
  pending.catch(() => undefined);
@@ -96,17 +107,19 @@ const awaitRemote = async (context, request) => {
96
107
  return untilAborted(pending, context.signal);
97
108
  };
98
109
  /**
99
- * The kernel's ApprovalPolicy, deciding by the tool's declared risk rather
100
- * than by its name. `safe` runs unattended; anything else needs a human,
101
- * and whether one exists is what `mode` answers.
110
+ * The kernel's ApprovalPolicy, deciding by the tool's declared risk and —
111
+ * for a tool that carries one — by the command an invocation would run.
112
+ * `safe` runs unattended; anything else needs a human unless a scope covers
113
+ * it, and whether a human exists at all is what `mode` answers.
102
114
  *
103
- * This is deliberately coarse. Fine-grained judgment — which *arguments* to
104
- * a shell tool are safe, which flags turn a read into a write — belongs with
105
- * the shell tool itself, which does not exist yet; writing that machinery
106
- * now would mean guessing at the shape of a caller nobody has built.
115
+ * The three tiers resolve in order: scopes granted "always" in this session,
116
+ * then the agent's persistent whitelist, then the built-in safe list —
117
+ * after which there is nothing left but asking. A `dangerous` tool skips
118
+ * the scope engine entirely: its risk is a statement about the tool, and no
119
+ * argument shape makes `rm -rf` a read.
107
120
  */
108
121
  export const createPermissionPolicy = (options) => {
109
- const { mode, ask, request, onDecision } = options;
122
+ const { mode, ask, request, onDecision, commands } = options;
110
123
  if (mode === 'interactive' && !ask) {
111
124
  throw new Error('interactive permission mode needs an `ask` function to reach a human.');
112
125
  }
@@ -117,7 +130,12 @@ export const createPermissionPolicy = (options) => {
117
130
  throw new Error('remote permission mode needs a `request` function to reach a human.');
118
131
  }
119
132
  const alwaysAllowed = new Set();
120
- const report = (context, allowed, reason) => {
133
+ // Tier one of the three: scopes granted with "always" during this
134
+ // process's life. It is not merely a cache of the file — a policy built
135
+ // with no whitelist store still has to remember an answer for the rest of
136
+ // the session, which is what `always` means at a terminal.
137
+ const sessionScopes = new Map();
138
+ const report = (context, allowed, reason, command) => {
121
139
  onDecision?.({
122
140
  allowed,
123
141
  mode,
@@ -126,6 +144,7 @@ export const createPermissionPolicy = (options) => {
126
144
  sessionId: context.session.id,
127
145
  agentId: context.session.agent.id,
128
146
  reason,
147
+ ...(command === undefined ? {} : { command }),
129
148
  });
130
149
  return allowed;
131
150
  };
@@ -135,11 +154,41 @@ export const createPermissionPolicy = (options) => {
135
154
  if (risk === 'safe') {
136
155
  return report(context, true, `${call.toolName} is safe and runs without approval`);
137
156
  }
138
- if (alwaysAllowed.has(sessionKey(session.id, call.toolName))) {
157
+ // What this *invocation* would run, for a tool whose danger lives in
158
+ // its arguments. Resolved before anything else so that the tool-wide
159
+ // "always" below can never apply to it: one yes to `git status` must
160
+ // not become a standing yes to every command the shell can run.
161
+ const command = risk === 'gated' ? context.tool.commandFor?.(call.input) : undefined;
162
+ const analysis = command === undefined ? undefined : analyzeCommand(command);
163
+ if (analysis) {
164
+ if (analysis.disqualifiedBy) {
165
+ // Never auto-approved, whatever the base command is: a safe scope
166
+ // in front of a pipe is the exact shape this rule exists for.
167
+ if (mode === 'headless') {
168
+ return report(context, false, `${call.toolName} cannot run unattended: ${analysis.disqualifiedBy}`, command);
169
+ }
170
+ }
171
+ else {
172
+ const stored = commands?.whitelist ? await commands.whitelist.scopesFor(session.agent.id) : [];
173
+ const candidates = [
174
+ ...(sessionScopes.get(session.agent.id) ?? []),
175
+ ...stored,
176
+ ...(commands?.safeScopes ?? SAFE_COMMAND_SCOPES),
177
+ ];
178
+ const scope = findMatchingScope(analysis, candidates);
179
+ if (scope) {
180
+ return report(context, true, `${call.toolName} ran inside the approved scope "${describeCommandScope(scope)}"`, command);
181
+ }
182
+ }
183
+ }
184
+ else if (alwaysAllowed.has(sessionKey(session.id, call.toolName))) {
139
185
  return report(context, true, `${call.toolName} was approved for the rest of this session`);
140
186
  }
141
187
  if (mode === 'headless') {
142
- return report(context, false, `${call.toolName} is ${risk} and nobody is available to approve it`);
188
+ return report(context, false, command === undefined
189
+ ? `${call.toolName} is ${risk} and nobody is available to approve it`
190
+ : `${call.toolName} was called outside every approved scope`
191
+ + `${analysis?.base ? ` (${analysis.base})` : ''} and nobody is available to approve it`, command);
143
192
  }
144
193
  // An aborted turn must not sit on a prompt: the human would be
145
194
  // answering for work that no longer exists, and an answer arriving
@@ -154,20 +203,41 @@ export const createPermissionPolicy = (options) => {
154
203
  // different things.
155
204
  const answer = mode === 'remote'
156
205
  ? await awaitRemote(context, request)
157
- : await awaitPrompt(context, ask);
206
+ : await awaitPrompt(context, ask, command);
158
207
  if (answer === ABORTED) {
159
208
  return report(context, false, `${call.toolName} was cancelled while awaiting approval`);
160
209
  }
161
210
  if (answer === 'deny') {
162
211
  return report(context, false, mode === 'remote'
163
212
  ? `${call.toolName} was not approved`
164
- : `${call.toolName} was refused at the prompt`);
213
+ : `${call.toolName} was refused at the prompt`, command);
165
214
  }
166
215
  if (answer === 'always') {
216
+ const scope = analysis ? normalizeCommandScope(analysis) : undefined;
217
+ if (analysis && !scope) {
218
+ // A command this parser could not reduce to a scope — a pipe, a
219
+ // subshell, an unbalanced quote. "Always" must not fall back to
220
+ // the tool-wide grant here: the approver widened one command they
221
+ // read, and remembering `shell.run` instead would hand the agent
222
+ // every command for the rest of the session.
223
+ return report(context, true, `${call.toolName} was approved once; it cannot be reduced to a scope, so it will ask again`, command);
224
+ }
225
+ if (scope) {
226
+ // A scope, never the command string (useless next time) and never
227
+ // the bare executable (a shell). `git push origin main` persists
228
+ // `git push` minus its destructive forms, so `git push --force`
229
+ // asks again.
230
+ sessionScopes.set(session.agent.id, [...(sessionScopes.get(session.agent.id) ?? []), scope]);
231
+ if (commands?.whitelist) {
232
+ await commands.whitelist.remember(session.agent.id, scope);
233
+ commands.onScopeRemembered?.({ agentId: session.agent.id, scope });
234
+ }
235
+ return report(context, true, `${call.toolName} was approved, and "${describeCommandScope(scope)}" now runs without asking`, command);
236
+ }
167
237
  alwaysAllowed.add(sessionKey(session.id, call.toolName));
168
238
  return report(context, true, `${call.toolName} was approved for the rest of this session`);
169
239
  }
170
- return report(context, true, `${call.toolName} was approved once`);
240
+ return report(context, true, `${call.toolName} was approved once`, command);
171
241
  },
172
242
  };
173
243
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type CommandScope } from './commands.ts';
2
+ /**
3
+ * What one agent may run unattended, beyond the built-in safe list.
4
+ *
5
+ * Per agent, because "always allow" is an answer about one identity's work
6
+ * — a scope somebody granted Ava in a repository of theirs is not a scope
7
+ * Juno inherits.
8
+ */
9
+ export interface CommandWhitelistStore {
10
+ scopesFor(agentId: string): Promise<CommandScope[]>;
11
+ remember(agentId: string, scope: CommandScope): Promise<void>;
12
+ }
13
+ /** `<id>.whitelist.json`, beside the agent's soul in ~/.stratus/agents. */
14
+ export declare const whitelistPathFor: (directory: string, agentId: string) => string;
15
+ /**
16
+ * The persistent half of the three-tier resolution, on disk.
17
+ *
18
+ * Written `0600` with an explicit `chmod`, like every other file in
19
+ * `~/.stratus` that decides something: this one lists commands that run
20
+ * with nobody watching, so another account on the machine appending a line
21
+ * to it is the whole permission engine defeated. `writeFile`'s mode only
22
+ * applies when it creates the file, so an upgrade over a looser install
23
+ * would otherwise keep the old permissions.
24
+ *
25
+ * Scopes are cached in memory once read. A daemon therefore does not notice
26
+ * a hand-edited file until it restarts — which is the right way round for a
27
+ * file whose edits grant permissions, and the same bargain the credential
28
+ * store makes.
29
+ */
30
+ export declare const createFileCommandWhitelist: (options: {
31
+ directory: string;
32
+ }) => CommandWhitelistStore;
33
+ //# sourceMappingURL=whitelist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whitelist.d.ts","sourceRoot":"","sources":["../src/whitelist.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgC,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAEhF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AASD,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,EAAE,SAAS,MAAM,KAAG,MACnB,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,0BAA0B,GAAI,SAAS;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,KAAG,qBA+C3E,CAAC"}
@@ -0,0 +1,69 @@
1
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parseCommandScope, sameScope } from "./commands.js";
4
+ const WHITELIST_VERSION = 1;
5
+ /** `<id>.whitelist.json`, beside the agent's soul in ~/.stratus/agents. */
6
+ export const whitelistPathFor = (directory, agentId) => path.join(directory, `${agentId}.whitelist.json`);
7
+ /**
8
+ * The persistent half of the three-tier resolution, on disk.
9
+ *
10
+ * Written `0600` with an explicit `chmod`, like every other file in
11
+ * `~/.stratus` that decides something: this one lists commands that run
12
+ * with nobody watching, so another account on the machine appending a line
13
+ * to it is the whole permission engine defeated. `writeFile`'s mode only
14
+ * applies when it creates the file, so an upgrade over a looser install
15
+ * would otherwise keep the old permissions.
16
+ *
17
+ * Scopes are cached in memory once read. A daemon therefore does not notice
18
+ * a hand-edited file until it restarts — which is the right way round for a
19
+ * file whose edits grant permissions, and the same bargain the credential
20
+ * store makes.
21
+ */
22
+ export const createFileCommandWhitelist = (options) => {
23
+ const cache = new Map();
24
+ const read = async (agentId) => {
25
+ const cached = cache.get(agentId);
26
+ if (cached) {
27
+ return cached;
28
+ }
29
+ let scopes = [];
30
+ try {
31
+ // The agent id is a validated invariant by the time it reaches any
32
+ // path join (see 03) — it is a single path segment or it was refused
33
+ // at the parse boundary, so this does not re-check it.
34
+ const raw = await readFile(whitelistPathFor(options.directory, agentId), 'utf8');
35
+ const parsed = JSON.parse(raw);
36
+ scopes = Array.isArray(parsed.scopes)
37
+ ? parsed.scopes.map(parseCommandScope).filter((scope) => scope !== undefined)
38
+ : [];
39
+ }
40
+ catch {
41
+ // No whitelist, or one that will not parse. Both mean the same thing
42
+ // to a caller — no stored scopes — and neither is worth failing a
43
+ // turn over: the fallback is asking a human, which is where an agent
44
+ // with no whitelist starts anyway.
45
+ scopes = [];
46
+ }
47
+ cache.set(agentId, scopes);
48
+ return scopes;
49
+ };
50
+ return {
51
+ async scopesFor(agentId) {
52
+ return [...(await read(agentId))];
53
+ },
54
+ async remember(agentId, scope) {
55
+ const scopes = await read(agentId);
56
+ if (scopes.some((existing) => sameScope(existing, scope))) {
57
+ return;
58
+ }
59
+ const updated = [...scopes, scope];
60
+ cache.set(agentId, updated);
61
+ await mkdir(options.directory, { recursive: true });
62
+ const file = { version: WHITELIST_VERSION, scopes: updated };
63
+ const target = whitelistPathFor(options.directory, agentId);
64
+ await writeFile(target, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
65
+ await chmod(target, 0o600);
66
+ },
67
+ };
68
+ };
69
+ //# sourceMappingURL=whitelist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whitelist.js","sourceRoot":"","sources":["../src/whitelist.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stratusagent/permissions",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "node": ">=22.13 <23 || >=23.4"
18
18
  },
19
19
  "dependencies": {
20
- "@stratusagent/core": "0.4.0"
20
+ "@stratusagent/core": "0.6.0"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^24.6.0",