@stratusagent/permissions 0.3.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.
- package/dist/commands.d.ts +113 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +363 -0
- package/dist/commands.js.map +1 -0
- package/dist/index.d.ts +106 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -25
- package/dist/index.js.map +1 -1
- package/dist/whitelist.d.ts +33 -0
- package/dist/whitelist.d.ts.map +1 -0
- package/dist/whitelist.js +69 -0
- package/dist/whitelist.js.map +1 -0
- package/package.json +3 -2
- package/dist/tsconfig.test.tsbuildinfo +0 -1
|
@@ -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"}
|
package/dist/commands.js
ADDED
|
@@ -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":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import type { ApprovalPolicy, ToolRisk } from '@stratusagent/core';
|
|
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
|
*
|
|
@@ -7,12 +17,48 @@ import type { ApprovalPolicy, ToolRisk } from '@stratusagent/core';
|
|
|
7
17
|
* - `headless` — nobody is reachable. Safe calls run, everything else is
|
|
8
18
|
* refused and the refusal is reported, because a daemon that silently
|
|
9
19
|
* drops half a turn is worse than one that says what it would not do.
|
|
20
|
+
* - `remote` — nobody is at a terminal, but somebody is reachable through
|
|
21
|
+
* a channel. The turn parks on a request published elsewhere and resumes
|
|
22
|
+
* on the answer.
|
|
10
23
|
*
|
|
11
|
-
* `remote`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
24
|
+
* `remote` needs a `request` transport for the same reason `interactive`
|
|
25
|
+
* needs `ask`: this package decides *whether* a human is required and what
|
|
26
|
+
* their answer means, and never how to reach one. Delivery is the
|
|
27
|
+
* gateway's (a bus event) and the channel's (Slack buttons).
|
|
28
|
+
*/
|
|
29
|
+
export type PermissionMode = 'interactive' | 'headless' | 'remote';
|
|
30
|
+
/**
|
|
31
|
+
* One gated call, handed to whatever can reach a person. Deliberately the
|
|
32
|
+
* same shape the kernel hands the policy — a transport that had to
|
|
33
|
+
* reconstruct the call from a name would be guessing at exactly the
|
|
34
|
+
* details an approver is being asked about.
|
|
35
|
+
*/
|
|
36
|
+
export interface ApprovalRequest {
|
|
37
|
+
session: Session;
|
|
38
|
+
call: ToolCall;
|
|
39
|
+
tool: Tool;
|
|
40
|
+
risk: ToolRisk;
|
|
41
|
+
/**
|
|
42
|
+
* When this call first parked, if it is being re-asked after a restart.
|
|
43
|
+
* A transport that imposes a deadline measures from here rather than
|
|
44
|
+
* granting a fresh window.
|
|
45
|
+
*/
|
|
46
|
+
parkedAt?: string;
|
|
47
|
+
/**
|
|
48
|
+
* The turn's abort signal. A transport MUST invalidate its outstanding
|
|
49
|
+
* request when this fires: the answer would otherwise arrive for a turn
|
|
50
|
+
* that no longer exists, and acting on it would run a tool for cancelled
|
|
51
|
+
* work.
|
|
52
|
+
*/
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Publishes a request and settles when a human answers it — or when
|
|
57
|
+
* whatever is carrying it gives up. It never rejects for a timeout: an
|
|
58
|
+
* expired request is a `deny`, which is a decision the turn can continue
|
|
59
|
+
* from, not an error that fails it.
|
|
14
60
|
*/
|
|
15
|
-
export type
|
|
61
|
+
export type ApprovalRequester = (request: ApprovalRequest) => Promise<ApprovalAnswer>;
|
|
16
62
|
/** Why a call was allowed or refused, for logs and events. */
|
|
17
63
|
export interface PermissionDecision {
|
|
18
64
|
allowed: boolean;
|
|
@@ -21,8 +67,26 @@ export interface PermissionDecision {
|
|
|
21
67
|
toolName: string;
|
|
22
68
|
sessionId: string;
|
|
23
69
|
agentId: string;
|
|
24
|
-
/**
|
|
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
|
+
*/
|
|
25
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;
|
|
26
90
|
}
|
|
27
91
|
export interface PermissionPolicyOptions {
|
|
28
92
|
mode: PermissionMode;
|
|
@@ -31,24 +95,51 @@ export interface PermissionPolicyOptions {
|
|
|
31
95
|
* leniently, since it comes from someone typing at a prompt.
|
|
32
96
|
*/
|
|
33
97
|
ask?: (question: string) => Promise<string>;
|
|
98
|
+
/** Reaches the human through a channel. Required for `remote`. */
|
|
99
|
+
request?: ApprovalRequester;
|
|
34
100
|
/**
|
|
35
101
|
* Called for every decision, allowed or not. The daemon routes this to
|
|
36
102
|
* its log: an unattended refusal that appears nowhere is indistinguishable
|
|
37
103
|
* from an agent that chose not to act.
|
|
38
104
|
*/
|
|
39
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;
|
|
40
131
|
}
|
|
41
|
-
/** Whether `risk` is at least as risky as `floor`. */
|
|
42
|
-
export declare const atLeastAsRisky: (risk: ToolRisk, floor: ToolRisk) => boolean;
|
|
43
132
|
/**
|
|
44
|
-
* The kernel's ApprovalPolicy, deciding by the tool's declared risk
|
|
45
|
-
*
|
|
46
|
-
*
|
|
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.
|
|
47
137
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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.
|
|
52
143
|
*/
|
|
53
144
|
export declare const createPermissionPolicy: (options: PermissionPolicyOptions) => ApprovalPolicy;
|
|
54
145
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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');
|
|
@@ -53,22 +59,83 @@ const untilAborted = async (work, signal) => {
|
|
|
53
59
|
*/
|
|
54
60
|
const sessionKey = (sessionId, toolName) => `${sessionId}\u0000${toolName}`;
|
|
55
61
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
62
|
+
* Puts the question to someone at a terminal. A prompt's answer is free
|
|
63
|
+
* text from a person mid-typing, so it is read leniently and anything that
|
|
64
|
+
* is not recognisably a yes is a no.
|
|
65
|
+
*/
|
|
66
|
+
const awaitPrompt = async (context, ask, command) => {
|
|
67
|
+
const { call, risk, session } = context;
|
|
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: `);
|
|
74
|
+
// The answer is never read after this point if the turn ends first,
|
|
75
|
+
// so a late yes cannot execute a tool for work that no longer exists.
|
|
76
|
+
pending.catch(() => undefined);
|
|
77
|
+
const response = await untilAborted(pending, context.signal);
|
|
78
|
+
if (response === ABORTED) {
|
|
79
|
+
return ABORTED;
|
|
80
|
+
}
|
|
81
|
+
const answer = response.trim().toLowerCase();
|
|
82
|
+
if (!YES.has(answer)) {
|
|
83
|
+
return 'deny';
|
|
84
|
+
}
|
|
85
|
+
return ALWAYS.has(answer) ? 'always' : 'once';
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Puts the question to someone reachable through a channel. Unlike the
|
|
89
|
+
* prompt, the answer arrives already typed — a button carries one of three
|
|
90
|
+
* values — so there is nothing to interpret; the work is entirely in not
|
|
91
|
+
* outliving the turn.
|
|
59
92
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
93
|
+
* A transport that rejects (its channel is down, the request could not be
|
|
94
|
+
* delivered) denies rather than failing the turn: the agent should be told
|
|
95
|
+
* its call was not approved and carry on, not crash on the machinery that
|
|
96
|
+
* was supposed to ask.
|
|
97
|
+
*/
|
|
98
|
+
const awaitRemote = async (context, request) => {
|
|
99
|
+
const pending = request({
|
|
100
|
+
session: context.session,
|
|
101
|
+
call: context.call,
|
|
102
|
+
tool: context.tool,
|
|
103
|
+
risk: context.risk,
|
|
104
|
+
...(context.parkedAt ? { parkedAt: context.parkedAt } : {}),
|
|
105
|
+
...(context.signal ? { signal: context.signal } : {}),
|
|
106
|
+
}).then((answer) => answer, () => 'deny');
|
|
107
|
+
return untilAborted(pending, context.signal);
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
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.
|
|
114
|
+
*
|
|
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.
|
|
64
120
|
*/
|
|
65
121
|
export const createPermissionPolicy = (options) => {
|
|
66
|
-
const { mode, ask, onDecision } = options;
|
|
122
|
+
const { mode, ask, request, onDecision, commands } = options;
|
|
67
123
|
if (mode === 'interactive' && !ask) {
|
|
68
124
|
throw new Error('interactive permission mode needs an `ask` function to reach a human.');
|
|
69
125
|
}
|
|
126
|
+
// Same reason as `ask`, and the more important half: a remote policy with
|
|
127
|
+
// nothing to publish through would park every gated call on a request no
|
|
128
|
+
// one ever sees, which reads as a hung agent rather than a refusal.
|
|
129
|
+
if (mode === 'remote' && !request) {
|
|
130
|
+
throw new Error('remote permission mode needs a `request` function to reach a human.');
|
|
131
|
+
}
|
|
70
132
|
const alwaysAllowed = new Set();
|
|
71
|
-
|
|
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) => {
|
|
72
139
|
onDecision?.({
|
|
73
140
|
allowed,
|
|
74
141
|
mode,
|
|
@@ -77,6 +144,7 @@ export const createPermissionPolicy = (options) => {
|
|
|
77
144
|
sessionId: context.session.id,
|
|
78
145
|
agentId: context.session.agent.id,
|
|
79
146
|
reason,
|
|
147
|
+
...(command === undefined ? {} : { command }),
|
|
80
148
|
});
|
|
81
149
|
return allowed;
|
|
82
150
|
};
|
|
@@ -86,11 +154,41 @@ export const createPermissionPolicy = (options) => {
|
|
|
86
154
|
if (risk === 'safe') {
|
|
87
155
|
return report(context, true, `${call.toolName} is safe and runs without approval`);
|
|
88
156
|
}
|
|
89
|
-
|
|
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))) {
|
|
90
185
|
return report(context, true, `${call.toolName} was approved for the rest of this session`);
|
|
91
186
|
}
|
|
92
187
|
if (mode === 'headless') {
|
|
93
|
-
return report(context, false,
|
|
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);
|
|
94
192
|
}
|
|
95
193
|
// An aborted turn must not sit on a prompt: the human would be
|
|
96
194
|
// answering for work that no longer exists, and an answer arriving
|
|
@@ -98,23 +196,48 @@ export const createPermissionPolicy = (options) => {
|
|
|
98
196
|
if (context.signal?.aborted) {
|
|
99
197
|
return report(context, false, `${call.toolName} was cancelled before it could be approved`);
|
|
100
198
|
}
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
199
|
+
// Both remaining modes wait on a person; only the way of reaching one
|
|
200
|
+
// differs. Everything after the wait — what `always` means, how a
|
|
201
|
+
// refusal is reported — is deliberately shared, so a decision made in
|
|
202
|
+
// Slack and one typed at a terminal cannot drift into meaning
|
|
203
|
+
// different things.
|
|
204
|
+
const answer = mode === 'remote'
|
|
205
|
+
? await awaitRemote(context, request)
|
|
206
|
+
: await awaitPrompt(context, ask, command);
|
|
207
|
+
if (answer === ABORTED) {
|
|
107
208
|
return report(context, false, `${call.toolName} was cancelled while awaiting approval`);
|
|
108
209
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
210
|
+
if (answer === 'deny') {
|
|
211
|
+
return report(context, false, mode === 'remote'
|
|
212
|
+
? `${call.toolName} was not approved`
|
|
213
|
+
: `${call.toolName} was refused at the prompt`, command);
|
|
112
214
|
}
|
|
113
|
-
if (
|
|
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
|
+
}
|
|
114
237
|
alwaysAllowed.add(sessionKey(session.id, call.toolName));
|
|
115
238
|
return report(context, true, `${call.toolName} was approved for the rest of this session`);
|
|
116
239
|
}
|
|
117
|
-
return report(context, true, `${call.toolName} was approved once
|
|
240
|
+
return report(context, true, `${call.toolName} was approved once`, command);
|
|
118
241
|
},
|
|
119
242
|
};
|
|
120
243
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
"node": ">=22.13 <23 || >=23.4"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@stratusagent/core": "0.
|
|
20
|
+
"@stratusagent/core": "0.6.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
+
"@types/node": "^24.6.0",
|
|
23
24
|
"typescript": "^5.9.3"
|
|
24
25
|
},
|
|
25
26
|
"license": "MIT",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"fileNames":["../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es5.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.scripthost.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.core.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.collection.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.generator.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2016.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.date.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.object.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.array.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.object.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2019.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.date.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2020.number.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.promise.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2021.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.array.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.error.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.intl.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.object.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.string.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/lib.es2022.full.d.ts","../../core/dist/index.d.ts","../src/index.ts"],"fileIdsList":[[64]],"fileInfos":[{"version":"c430d44666289dae81f30fa7b2edebf186ecc91a2d4c71266ea6ae76388792e1","affectsGlobalScope":true,"impliedFormat":1},{"version":"45b7ab580deca34ae9729e97c13cfd999df04416a79116c3bfb483804f85ded4","impliedFormat":1},{"version":"3facaf05f0c5fc569c5649dd359892c98a85557e3e0c847964caeb67076f4d75","impliedFormat":1},{"version":"e44bb8bbac7f10ecc786703fe0a6a4b952189f908707980ba8f3c8975a760962","impliedFormat":1},{"version":"5e1c4c362065a6b95ff952c0eab010f04dcd2c3494e813b493ecfd4fcb9fc0d8","impliedFormat":1},{"version":"68d73b4a11549f9c0b7d352d10e91e5dca8faa3322bfb77b661839c42b1ddec7","impliedFormat":1},{"version":"5efce4fc3c29ea84e8928f97adec086e3dc876365e0982cc8479a07954a3efd4","impliedFormat":1},{"version":"feecb1be483ed332fad555aff858affd90a48ab19ba7272ee084704eb7167569","impliedFormat":1},{"version":"ee7bad0c15b58988daa84371e0b89d313b762ab83cb5b31b8a2d1162e8eb41c2","impliedFormat":1},{"version":"080941d9f9ff9307f7e27a83bcd888b7c8270716c39af943532438932ec1d0b9","affectsGlobalScope":true,"impliedFormat":1},{"version":"2e80ee7a49e8ac312cc11b77f1475804bee36b3b2bc896bead8b6e1266befb43","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7a3c8b952931daebdfc7a2897c53c0a1c73624593fa070e46bd537e64dcd20a","affectsGlobalScope":true,"impliedFormat":1},{"version":"80e18897e5884b6723488d4f5652167e7bb5024f946743134ecc4aa4ee731f89","affectsGlobalScope":true,"impliedFormat":1},{"version":"cd034f499c6cdca722b60c04b5b1b78e058487a7085a8e0d6fb50809947ee573","affectsGlobalScope":true,"impliedFormat":1},{"version":"c57796738e7f83dbc4b8e65132f11a377649c00dd3eee333f672b8f0a6bea671","affectsGlobalScope":true,"impliedFormat":1},{"version":"dc2df20b1bcdc8c2d34af4926e2c3ab15ffe1160a63e58b7e09833f616efff44","affectsGlobalScope":true,"impliedFormat":1},{"version":"515d0b7b9bea2e31ea4ec968e9edd2c39d3eebf4a2d5cbd04e88639819ae3b71","affectsGlobalScope":true,"impliedFormat":1},{"version":"0559b1f683ac7505ae451f9a96ce4c3c92bdc71411651ca6ddb0e88baaaad6a3","affectsGlobalScope":true,"impliedFormat":1},{"version":"0dc1e7ceda9b8b9b455c3a2d67b0412feab00bd2f66656cd8850e8831b08b537","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce691fb9e5c64efb9547083e4a34091bcbe5bdb41027e310ebba8f7d96a98671","affectsGlobalScope":true,"impliedFormat":1},{"version":"8d697a2a929a5fcb38b7a65594020fcef05ec1630804a33748829c5ff53640d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ff2a353abf8a80ee399af572debb8faab2d33ad38c4b4474cff7f26e7653b8d","affectsGlobalScope":true,"impliedFormat":1},{"version":"fb0f136d372979348d59b3f5020b4cdb81b5504192b1cacff5d1fbba29378aa1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d15bea3d62cbbdb9797079416b8ac375ae99162a7fba5de2c6c505446486ac0a","affectsGlobalScope":true,"impliedFormat":1},{"version":"68d18b664c9d32a7336a70235958b8997ebc1c3b8505f4f1ae2b7e7753b87618","affectsGlobalScope":true,"impliedFormat":1},{"version":"eb3d66c8327153d8fa7dd03f9c58d351107fe824c79e9b56b462935176cdf12a","affectsGlobalScope":true,"impliedFormat":1},{"version":"38f0219c9e23c915ef9790ab1d680440d95419ad264816fa15009a8851e79119","affectsGlobalScope":true,"impliedFormat":1},{"version":"69ab18c3b76cd9b1be3d188eaf8bba06112ebbe2f47f6c322b5105a6fbc45a2e","affectsGlobalScope":true,"impliedFormat":1},{"version":"a680117f487a4d2f30ea46f1b4b7f58bef1480456e18ba53ee85c2746eeca012","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f11ff796926e0832f9ae148008138ad583bd181899ab7dd768a2666700b1893","affectsGlobalScope":true,"impliedFormat":1},{"version":"4de680d5bb41c17f7f68e0419412ca23c98d5749dcaaea1896172f06435891fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"954296b30da6d508a104a3a0b5d96b76495c709785c1d11610908e63481ee667","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac9538681b19688c8eae65811b329d3744af679e0bdfa5d842d0e32524c73e1c","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a969edff4bd52585473d24995c5ef223f6652d6ef46193309b3921d65dd4376","affectsGlobalScope":true,"impliedFormat":1},{"version":"9e9fbd7030c440b33d021da145d3232984c8bb7916f277e8ffd3dc2e3eae2bdb","affectsGlobalScope":true,"impliedFormat":1},{"version":"811ec78f7fefcabbda4bfa93b3eb67d9ae166ef95f9bff989d964061cbf81a0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"717937616a17072082152a2ef351cb51f98802fb4b2fdabd32399843875974ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"d7e7d9b7b50e5f22c915b525acc5a49a7a6584cf8f62d0569e557c5cfc4b2ac2","affectsGlobalScope":true,"impliedFormat":1},{"version":"71c37f4c9543f31dfced6c7840e068c5a5aacb7b89111a4364b1d5276b852557","affectsGlobalScope":true,"impliedFormat":1},{"version":"576711e016cf4f1804676043e6a0a5414252560eb57de9faceee34d79798c850","affectsGlobalScope":true,"impliedFormat":1},{"version":"89c1b1281ba7b8a96efc676b11b264de7a8374c5ea1e6617f11880a13fc56dc6","affectsGlobalScope":true,"impliedFormat":1},{"version":"74f7fa2d027d5b33eb0471c8e82a6c87216223181ec31247c357a3e8e2fddc5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"d6d7ae4d1f1f3772e2a3cde568ed08991a8ae34a080ff1151af28b7f798e22ca","affectsGlobalScope":true,"impliedFormat":1},{"version":"063600664504610fe3e99b717a1223f8b1900087fab0b4cad1496a114744f8df","affectsGlobalScope":true,"impliedFormat":1},{"version":"934019d7e3c81950f9a8426d093458b65d5aff2c7c1511233c0fd5b941e608ab","affectsGlobalScope":true,"impliedFormat":1},{"version":"52ada8e0b6e0482b728070b7639ee42e83a9b1c22d205992756fe020fd9f4a47","affectsGlobalScope":true,"impliedFormat":1},{"version":"3bdefe1bfd4d6dee0e26f928f93ccc128f1b64d5d501ff4a8cf3c6371200e5e6","affectsGlobalScope":true,"impliedFormat":1},{"version":"59fb2c069260b4ba00b5643b907ef5d5341b167e7d1dbf58dfd895658bda2867","affectsGlobalScope":true,"impliedFormat":1},{"version":"639e512c0dfc3fad96a84caad71b8834d66329a1f28dc95e3946c9b58176c73a","affectsGlobalScope":true,"impliedFormat":1},{"version":"368af93f74c9c932edd84c58883e736c9e3d53cec1fe24c0b0ff451f529ceab1","affectsGlobalScope":true,"impliedFormat":1},{"version":"af3dd424cf267428f30ccfc376f47a2c0114546b55c44d8c0f1d57d841e28d74","affectsGlobalScope":true,"impliedFormat":1},{"version":"995c005ab91a498455ea8dfb63aa9f83fa2ea793c3d8aa344be4a1678d06d399","affectsGlobalScope":true,"impliedFormat":1},{"version":"959d36cddf5e7d572a65045b876f2956c973a586da58e5d26cde519184fd9b8a","affectsGlobalScope":true,"impliedFormat":1},{"version":"965f36eae237dd74e6cca203a43e9ca801ce38824ead814728a2807b1910117d","affectsGlobalScope":true,"impliedFormat":1},{"version":"3925a6c820dcb1a06506c90b1577db1fdbf7705d65b62b99dce4be75c637e26b","affectsGlobalScope":true,"impliedFormat":1},{"version":"0a3d63ef2b853447ec4f749d3f368ce642264246e02911fcb1590d8c161b8005","affectsGlobalScope":true,"impliedFormat":1},{"version":"8cdf8847677ac7d20486e54dd3fcf09eda95812ac8ace44b4418da1bbbab6eb8","affectsGlobalScope":true,"impliedFormat":1},{"version":"8444af78980e3b20b49324f4a16ba35024fef3ee069a0eb67616ea6ca821c47a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3287d9d085fbd618c3971944b65b4be57859f5415f495b33a6adc994edd2f004","affectsGlobalScope":true,"impliedFormat":1},{"version":"b4b67b1a91182421f5df999988c690f14d813b9850b40acd06ed44691f6727ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"8e7f8264d0fb4c5339605a15daadb037bf238c10b654bb3eee14208f860a32ea","affectsGlobalScope":true,"impliedFormat":1},{"version":"782dec38049b92d4e85c1585fbea5474a219c6984a35b004963b00beb1aab538","affectsGlobalScope":true,"impliedFormat":1},{"version":"3cbad9a1ba4453443026ed38e4b8be018abb26565fa7c944376463ad9df07c41","impliedFormat":1},{"version":"2684504f5ac9d0d77f79c2971ad2cca76d0cc0669dceb1a79bfa18754f6351c3","impliedFormat":99},{"version":"45115b15bdd714ca361c03deab56f27f17d8c44222f2f8feb60c3f3388ac1f00","impliedFormat":99}],"root":[65],"options":{"composite":true,"declaration":true,"declarationMap":true,"esModuleInterop":true,"exactOptionalPropertyTypes":true,"module":199,"noUncheckedIndexedAccess":true,"outDir":"./","rootDir":"..","skipLibCheck":true,"sourceMap":true,"strict":true,"target":9},"referencedMap":[[65,1]],"affectedFilesPendingEmit":[[65,51]],"emitSignatures":[65],"version":"5.9.3"}
|