@gaunt-sloth/core 2.0.0-alpha.2 → 2.0.0-alpha.4
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/.gsloth.code.md +10 -0
- package/README.md +3 -4
- package/dist/config/defaults.d.ts +84 -0
- package/dist/config/defaults.js +97 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loader.d.ts +88 -0
- package/dist/config/loader.js +604 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +471 -0
- package/dist/config/schema.js +301 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/shell-policy.d.ts +212 -0
- package/dist/config/shell-policy.js +142 -0
- package/dist/config/shell-policy.js.map +1 -0
- package/dist/config/types.d.ts +453 -0
- package/dist/config/types.js +12 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config.d.ts +18 -647
- package/dist/config.js +15 -516
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +24 -1
- package/dist/core/GthAbstractAgent.js +86 -4
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +127 -1
- package/dist/core/GthAgentRunner.js +298 -4
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/shell/allowlist.d.ts +75 -0
- package/dist/core/shell/allowlist.js +187 -0
- package/dist/core/shell/allowlist.js.map +1 -0
- package/dist/core/shell/arity.d.ts +75 -0
- package/dist/core/shell/arity.js +313 -0
- package/dist/core/shell/arity.js.map +1 -0
- package/dist/core/shell/judge.d.ts +161 -0
- package/dist/core/shell/judge.js +261 -0
- package/dist/core/shell/judge.js.map +1 -0
- package/dist/core/shell/normalize.d.ts +27 -0
- package/dist/core/shell/normalize.js +53 -0
- package/dist/core/shell/normalize.js.map +1 -0
- package/dist/core/types.d.ts +75 -0
- package/dist/core/types.js.map +1 -1
- package/dist/providers/openrouter.js +2 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/utils/fileUtils.d.ts +4 -1
- package/dist/utils/fileUtils.js +19 -10
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/systemUtils.d.ts +31 -0
- package/dist/utils/systemUtils.js +38 -0
- package/dist/utils/systemUtils.js.map +1 -1
- package/package.json +12 -8
- package/schema/gsloth-config.schema.json +1548 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/arity
|
|
3
|
+
*
|
|
4
|
+
* EXT-9 Tier-2 ergonomics: classify a shell command into a stable, human-readable
|
|
5
|
+
* **prefix** (binary + N meaningful subcommands) so an approval the human grants once
|
|
6
|
+
* (`git checkout main`) can be remembered as a pattern (`git checkout *`) and matched
|
|
7
|
+
* against later flag-variants (`git checkout -b foo bar`) WITHOUT re-prompting.
|
|
8
|
+
*
|
|
9
|
+
* The arity table (which tokens past the binary are "meaningful subcommands" rather
|
|
10
|
+
* than operands/flags) is a port of a subset of opencode's ~160-command table
|
|
11
|
+
* (`opencode/packages/opencode/src/permission/arity.ts`). Binaries not in the table
|
|
12
|
+
* default to arity 0 = just the binary.
|
|
13
|
+
*
|
|
14
|
+
* SECURITY — this is the anti-injection core. {@link classifyCommand} returns `null`
|
|
15
|
+
* (fail-closed) whenever the command contains shell composition that could change the
|
|
16
|
+
* target of the operation: separators (`;`, `&&`, `||`, `|`, `&`), newlines, command
|
|
17
|
+
* substitution (`$(...)`, backticks), process substitution (`<(...)`/`>(...)`), or
|
|
18
|
+
* redirections. Such commands NEVER auto-match an allow-list entry — they always go to
|
|
19
|
+
* fresh human approval. This is what stops `git checkout x; rm -rf /` from matching an
|
|
20
|
+
* approved `git checkout *`.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Result of classifying a command for allow-list matching.
|
|
24
|
+
*/
|
|
25
|
+
export interface CommandClassification {
|
|
26
|
+
/**
|
|
27
|
+
* The meaningful command prefix — binary plus subcommands per the arity table, with
|
|
28
|
+
* flags removed. This is the allow-list KEY: two commands with the same prefix are the
|
|
29
|
+
* "same operation" for approval purposes (`git checkout main` and `git checkout -b x`
|
|
30
|
+
* both → `git checkout`).
|
|
31
|
+
*/
|
|
32
|
+
prefix: string;
|
|
33
|
+
/**
|
|
34
|
+
* Human-facing display pattern, e.g. `git checkout *` (or `ls *`). The trailing `*`
|
|
35
|
+
* signals "any args/flags". A prefix that already consumed the whole command (no extra
|
|
36
|
+
* operands) still gets ` *` for a consistent, honest "future args allowed" affordance.
|
|
37
|
+
*/
|
|
38
|
+
pattern: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Tokenize a shell command into argv, honoring single and double quotes. Quotes group
|
|
42
|
+
* (and are stripped from) a token; backslash-escaping inside double quotes is collapsed
|
|
43
|
+
* by the prior normalize step, so this tokenizer treats a residual `\` literally.
|
|
44
|
+
*
|
|
45
|
+
* This is a deliberately small tokenizer used ONLY for prefix detection — never for
|
|
46
|
+
* execution. The original command string is what runs.
|
|
47
|
+
*
|
|
48
|
+
* Returns `null` if quoting is unbalanced (an open quote with no close), which is itself
|
|
49
|
+
* a reason to refuse classification (ambiguous parse → fail-closed).
|
|
50
|
+
*/
|
|
51
|
+
export declare function tokenize(command: string): string[] | null;
|
|
52
|
+
/**
|
|
53
|
+
* Look up the arity (number of leading meaningful tokens) for an argv, using the
|
|
54
|
+
* longest-matching-prefix rule against {@link ARITY}. Flag tokens (starting with `-`) are
|
|
55
|
+
* dropped when forming the candidate prefixes so boolean flags like `git --no-pager
|
|
56
|
+
* checkout` still resolve `git checkout`. Unknown binaries default to arity 0 → just the
|
|
57
|
+
* binary.
|
|
58
|
+
*
|
|
59
|
+
* LIMITATION (intentional, fail-closed): we do NOT maintain a per-flag arity table, so an
|
|
60
|
+
* arg-taking flag leaves its operand in the non-flag stream (e.g. `git -C . checkout` →
|
|
61
|
+
* `git .`). That simply fails to match an approved `git checkout` and re-prompts — safe, by
|
|
62
|
+
* design — rather than risking a mis-classification that mis-approves.
|
|
63
|
+
*
|
|
64
|
+
* Returns the list of meaningful tokens (binary + subcommands), flag tokens excluded.
|
|
65
|
+
*/
|
|
66
|
+
export declare function meaningfulPrefixTokens(argv: string[]): string[];
|
|
67
|
+
/**
|
|
68
|
+
* Classify a command into a stable allow-list prefix + display pattern, or `null` when it
|
|
69
|
+
* cannot be safely classified for matching (composition/redirection/substitution present,
|
|
70
|
+
* empty, or unbalanced quotes).
|
|
71
|
+
*
|
|
72
|
+
* @param command Raw command string as the model proposed it.
|
|
73
|
+
* @param normalize Normalizer to apply for the detection form (inject normalizeCommand).
|
|
74
|
+
*/
|
|
75
|
+
export declare function classifyCommand(command: string, normalize: (cmd: string) => string): CommandClassification | null;
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/arity
|
|
3
|
+
*
|
|
4
|
+
* EXT-9 Tier-2 ergonomics: classify a shell command into a stable, human-readable
|
|
5
|
+
* **prefix** (binary + N meaningful subcommands) so an approval the human grants once
|
|
6
|
+
* (`git checkout main`) can be remembered as a pattern (`git checkout *`) and matched
|
|
7
|
+
* against later flag-variants (`git checkout -b foo bar`) WITHOUT re-prompting.
|
|
8
|
+
*
|
|
9
|
+
* The arity table (which tokens past the binary are "meaningful subcommands" rather
|
|
10
|
+
* than operands/flags) is a port of a subset of opencode's ~160-command table
|
|
11
|
+
* (`opencode/packages/opencode/src/permission/arity.ts`). Binaries not in the table
|
|
12
|
+
* default to arity 0 = just the binary.
|
|
13
|
+
*
|
|
14
|
+
* SECURITY — this is the anti-injection core. {@link classifyCommand} returns `null`
|
|
15
|
+
* (fail-closed) whenever the command contains shell composition that could change the
|
|
16
|
+
* target of the operation: separators (`;`, `&&`, `||`, `|`, `&`), newlines, command
|
|
17
|
+
* substitution (`$(...)`, backticks), process substitution (`<(...)`/`>(...)`), or
|
|
18
|
+
* redirections. Such commands NEVER auto-match an allow-list entry — they always go to
|
|
19
|
+
* fresh human approval. This is what stops `git checkout x; rm -rf /` from matching an
|
|
20
|
+
* approved `git checkout *`.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Arity table: command-prefix string → number of leading tokens (binary + subcommands,
|
|
24
|
+
* flags excluded) that define the "human-understandable command". Longest matching
|
|
25
|
+
* prefix wins. Ported subset of opencode's table — git/npm/pnpm/yarn/docker/kubectl/
|
|
26
|
+
* cargo/go/etc. Binaries absent here default to arity 0 (just the binary), see
|
|
27
|
+
* {@link arityFor}.
|
|
28
|
+
*
|
|
29
|
+
* Rule (from opencode): flags never count as tokens; only subcommands do. Include a
|
|
30
|
+
* longer prefix only when its arity differs from what the shorter prefix implies.
|
|
31
|
+
*/
|
|
32
|
+
const ARITY = {
|
|
33
|
+
// Single-token utilities (arity 1 = just the binary; here for completeness/clarity).
|
|
34
|
+
cat: 1,
|
|
35
|
+
cd: 1,
|
|
36
|
+
chmod: 1,
|
|
37
|
+
chown: 1,
|
|
38
|
+
cp: 1,
|
|
39
|
+
echo: 1,
|
|
40
|
+
env: 1,
|
|
41
|
+
export: 1,
|
|
42
|
+
grep: 1,
|
|
43
|
+
kill: 1,
|
|
44
|
+
killall: 1,
|
|
45
|
+
ln: 1,
|
|
46
|
+
ls: 1,
|
|
47
|
+
mkdir: 1,
|
|
48
|
+
mv: 1,
|
|
49
|
+
ps: 1,
|
|
50
|
+
pwd: 1,
|
|
51
|
+
rm: 1,
|
|
52
|
+
rmdir: 1,
|
|
53
|
+
sleep: 1,
|
|
54
|
+
source: 1,
|
|
55
|
+
tail: 1,
|
|
56
|
+
head: 1,
|
|
57
|
+
touch: 1,
|
|
58
|
+
unset: 1,
|
|
59
|
+
which: 1,
|
|
60
|
+
find: 1,
|
|
61
|
+
// Cloud / infra CLIs.
|
|
62
|
+
aws: 3,
|
|
63
|
+
az: 3,
|
|
64
|
+
bazel: 2,
|
|
65
|
+
brew: 2,
|
|
66
|
+
bun: 2,
|
|
67
|
+
'bun run': 3,
|
|
68
|
+
'bun x': 3,
|
|
69
|
+
cargo: 2,
|
|
70
|
+
'cargo add': 3,
|
|
71
|
+
'cargo run': 3,
|
|
72
|
+
cdk: 2,
|
|
73
|
+
cf: 2,
|
|
74
|
+
cmake: 2,
|
|
75
|
+
composer: 2,
|
|
76
|
+
consul: 2,
|
|
77
|
+
'consul kv': 3,
|
|
78
|
+
crictl: 2,
|
|
79
|
+
deno: 2,
|
|
80
|
+
'deno task': 3,
|
|
81
|
+
doctl: 3,
|
|
82
|
+
docker: 2,
|
|
83
|
+
'docker builder': 3,
|
|
84
|
+
'docker compose': 3,
|
|
85
|
+
'docker container': 3,
|
|
86
|
+
'docker image': 3,
|
|
87
|
+
'docker network': 3,
|
|
88
|
+
'docker volume': 3,
|
|
89
|
+
eksctl: 2,
|
|
90
|
+
'eksctl create': 3,
|
|
91
|
+
firebase: 2,
|
|
92
|
+
flyctl: 2,
|
|
93
|
+
gcloud: 3,
|
|
94
|
+
gh: 3,
|
|
95
|
+
git: 2,
|
|
96
|
+
'git config': 3,
|
|
97
|
+
'git remote': 3,
|
|
98
|
+
'git stash': 3,
|
|
99
|
+
go: 2,
|
|
100
|
+
gradle: 2,
|
|
101
|
+
helm: 2,
|
|
102
|
+
heroku: 2,
|
|
103
|
+
hugo: 2,
|
|
104
|
+
ip: 2,
|
|
105
|
+
'ip addr': 3,
|
|
106
|
+
'ip link': 3,
|
|
107
|
+
'ip netns': 3,
|
|
108
|
+
'ip route': 3,
|
|
109
|
+
kind: 2,
|
|
110
|
+
'kind create': 3,
|
|
111
|
+
kubectl: 2,
|
|
112
|
+
'kubectl kustomize': 3,
|
|
113
|
+
'kubectl rollout': 3,
|
|
114
|
+
kustomize: 2,
|
|
115
|
+
make: 2,
|
|
116
|
+
mc: 2,
|
|
117
|
+
'mc admin': 3,
|
|
118
|
+
minikube: 2,
|
|
119
|
+
mongosh: 2,
|
|
120
|
+
mysql: 2,
|
|
121
|
+
mvn: 2,
|
|
122
|
+
ng: 2,
|
|
123
|
+
npm: 2,
|
|
124
|
+
'npm exec': 3,
|
|
125
|
+
'npm init': 3,
|
|
126
|
+
'npm run': 3,
|
|
127
|
+
'npm view': 3,
|
|
128
|
+
npx: 2,
|
|
129
|
+
nvm: 2,
|
|
130
|
+
nx: 2,
|
|
131
|
+
openssl: 2,
|
|
132
|
+
'openssl req': 3,
|
|
133
|
+
'openssl x509': 3,
|
|
134
|
+
pip: 2,
|
|
135
|
+
pipenv: 2,
|
|
136
|
+
pnpm: 2,
|
|
137
|
+
'pnpm dlx': 3,
|
|
138
|
+
'pnpm exec': 3,
|
|
139
|
+
'pnpm run': 3,
|
|
140
|
+
poetry: 2,
|
|
141
|
+
podman: 2,
|
|
142
|
+
'podman container': 3,
|
|
143
|
+
'podman image': 3,
|
|
144
|
+
psql: 2,
|
|
145
|
+
pulumi: 2,
|
|
146
|
+
'pulumi stack': 3,
|
|
147
|
+
pyenv: 2,
|
|
148
|
+
python: 2,
|
|
149
|
+
python3: 2,
|
|
150
|
+
rake: 2,
|
|
151
|
+
rbenv: 2,
|
|
152
|
+
'redis-cli': 2,
|
|
153
|
+
rustup: 2,
|
|
154
|
+
serverless: 2,
|
|
155
|
+
sfdx: 3,
|
|
156
|
+
skaffold: 2,
|
|
157
|
+
sls: 2,
|
|
158
|
+
sst: 2,
|
|
159
|
+
swift: 2,
|
|
160
|
+
systemctl: 2,
|
|
161
|
+
terraform: 2,
|
|
162
|
+
'terraform workspace': 3,
|
|
163
|
+
tmux: 2,
|
|
164
|
+
turbo: 2,
|
|
165
|
+
ufw: 2,
|
|
166
|
+
vault: 2,
|
|
167
|
+
'vault auth': 3,
|
|
168
|
+
'vault kv': 3,
|
|
169
|
+
vercel: 2,
|
|
170
|
+
volta: 2,
|
|
171
|
+
wp: 2,
|
|
172
|
+
yarn: 2,
|
|
173
|
+
'yarn dlx': 3,
|
|
174
|
+
'yarn run': 3,
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Tokens / sequences whose presence means the command composes or redirects in a way that
|
|
178
|
+
* could change what actually runs — so it must NOT be classifiable for auto-match. Checked
|
|
179
|
+
* against the NORMALIZED command (see normalize.ts) which has already collapsed obfuscation.
|
|
180
|
+
*
|
|
181
|
+
* Note: `&&`/`||`/`|` are covered by the bare `&`/`|` character scan; listed conceptually.
|
|
182
|
+
*/
|
|
183
|
+
function hasUnsafeComposition(normalized) {
|
|
184
|
+
// Newlines are folded by normalizeCommand, but guard anyway in case a raw string is passed.
|
|
185
|
+
if (/[\n\r]/.test(normalized))
|
|
186
|
+
return true;
|
|
187
|
+
// Shell control / separator operators and background.
|
|
188
|
+
if (/[;|&]/.test(normalized))
|
|
189
|
+
return true;
|
|
190
|
+
// Command substitution: $(...) or `...`.
|
|
191
|
+
if (/\$\(/.test(normalized))
|
|
192
|
+
return true;
|
|
193
|
+
if (/`/.test(normalized))
|
|
194
|
+
return true;
|
|
195
|
+
// Variable/arith expansion that could inject: ${...} and $((...)).
|
|
196
|
+
if (/\$\{/.test(normalized))
|
|
197
|
+
return true;
|
|
198
|
+
// Process substitution: <(...) or >(...).
|
|
199
|
+
if (/[<>]\(/.test(normalized))
|
|
200
|
+
return true;
|
|
201
|
+
// Redirections (any < or > not already caught as process substitution): >, >>, <, 2>, &>.
|
|
202
|
+
if (/[<>]/.test(normalized))
|
|
203
|
+
return true;
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Tokenize a shell command into argv, honoring single and double quotes. Quotes group
|
|
208
|
+
* (and are stripped from) a token; backslash-escaping inside double quotes is collapsed
|
|
209
|
+
* by the prior normalize step, so this tokenizer treats a residual `\` literally.
|
|
210
|
+
*
|
|
211
|
+
* This is a deliberately small tokenizer used ONLY for prefix detection — never for
|
|
212
|
+
* execution. The original command string is what runs.
|
|
213
|
+
*
|
|
214
|
+
* Returns `null` if quoting is unbalanced (an open quote with no close), which is itself
|
|
215
|
+
* a reason to refuse classification (ambiguous parse → fail-closed).
|
|
216
|
+
*/
|
|
217
|
+
export function tokenize(command) {
|
|
218
|
+
const tokens = [];
|
|
219
|
+
let current = '';
|
|
220
|
+
let inToken = false;
|
|
221
|
+
let quote = null;
|
|
222
|
+
for (let i = 0; i < command.length; i++) {
|
|
223
|
+
const ch = command[i];
|
|
224
|
+
if (quote) {
|
|
225
|
+
if (ch === quote) {
|
|
226
|
+
quote = null;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
current += ch;
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (ch === '"' || ch === "'") {
|
|
234
|
+
quote = ch;
|
|
235
|
+
inToken = true;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (ch === ' ' || ch === '\t') {
|
|
239
|
+
if (inToken) {
|
|
240
|
+
tokens.push(current);
|
|
241
|
+
current = '';
|
|
242
|
+
inToken = false;
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
current += ch;
|
|
247
|
+
inToken = true;
|
|
248
|
+
}
|
|
249
|
+
if (quote)
|
|
250
|
+
return null; // unbalanced quote
|
|
251
|
+
if (inToken)
|
|
252
|
+
tokens.push(current);
|
|
253
|
+
return tokens;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Look up the arity (number of leading meaningful tokens) for an argv, using the
|
|
257
|
+
* longest-matching-prefix rule against {@link ARITY}. Flag tokens (starting with `-`) are
|
|
258
|
+
* dropped when forming the candidate prefixes so boolean flags like `git --no-pager
|
|
259
|
+
* checkout` still resolve `git checkout`. Unknown binaries default to arity 0 → just the
|
|
260
|
+
* binary.
|
|
261
|
+
*
|
|
262
|
+
* LIMITATION (intentional, fail-closed): we do NOT maintain a per-flag arity table, so an
|
|
263
|
+
* arg-taking flag leaves its operand in the non-flag stream (e.g. `git -C . checkout` →
|
|
264
|
+
* `git .`). That simply fails to match an approved `git checkout` and re-prompts — safe, by
|
|
265
|
+
* design — rather than risking a mis-classification that mis-approves.
|
|
266
|
+
*
|
|
267
|
+
* Returns the list of meaningful tokens (binary + subcommands), flag tokens excluded.
|
|
268
|
+
*/
|
|
269
|
+
export function meaningfulPrefixTokens(argv) {
|
|
270
|
+
// Drop leading flags before the binary cannot happen (binary is first), but a binary
|
|
271
|
+
// can be followed by flags interleaved with subcommands (e.g. `git -C . checkout`).
|
|
272
|
+
// Build the flag-free token sequence first, preserving order.
|
|
273
|
+
const nonFlag = argv.filter((t) => !t.startsWith('-'));
|
|
274
|
+
if (nonFlag.length === 0)
|
|
275
|
+
return [];
|
|
276
|
+
// Longest matching prefix in the arity table wins.
|
|
277
|
+
for (let len = nonFlag.length; len > 0; len--) {
|
|
278
|
+
const candidate = nonFlag.slice(0, len).join(' ');
|
|
279
|
+
const arity = ARITY[candidate];
|
|
280
|
+
if (arity !== undefined) {
|
|
281
|
+
// arity counts meaningful tokens from the start of the non-flag sequence.
|
|
282
|
+
return nonFlag.slice(0, Math.min(arity, nonFlag.length));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Not in the table → arity 0 means "just the binary".
|
|
286
|
+
return nonFlag.slice(0, 1);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Classify a command into a stable allow-list prefix + display pattern, or `null` when it
|
|
290
|
+
* cannot be safely classified for matching (composition/redirection/substitution present,
|
|
291
|
+
* empty, or unbalanced quotes).
|
|
292
|
+
*
|
|
293
|
+
* @param command Raw command string as the model proposed it.
|
|
294
|
+
* @param normalize Normalizer to apply for the detection form (inject normalizeCommand).
|
|
295
|
+
*/
|
|
296
|
+
export function classifyCommand(command, normalize) {
|
|
297
|
+
const normalized = normalize(command);
|
|
298
|
+
if (!normalized)
|
|
299
|
+
return null;
|
|
300
|
+
// FAIL-CLOSED on any composition/redirection/substitution. This is the anti-injection
|
|
301
|
+
// guarantee: such commands never auto-match an allow-list entry.
|
|
302
|
+
if (hasUnsafeComposition(normalized))
|
|
303
|
+
return null;
|
|
304
|
+
const argv = tokenize(normalized);
|
|
305
|
+
if (!argv || argv.length === 0)
|
|
306
|
+
return null;
|
|
307
|
+
const prefixTokens = meaningfulPrefixTokens(argv);
|
|
308
|
+
if (prefixTokens.length === 0)
|
|
309
|
+
return null;
|
|
310
|
+
const prefix = prefixTokens.join(' ');
|
|
311
|
+
return { prefix, pattern: `${prefix} *` };
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=arity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arity.js","sourceRoot":"","sources":["../../../src/core/shell/arity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/judge
|
|
3
|
+
*
|
|
4
|
+
* EXT-10 — LLM-as-judge bash-safety gate. An optional, opt-in pre-filter that sits *in front
|
|
5
|
+
* of* the human approval prompt for `run_shell_command` (EXT-9). It is a tiered
|
|
6
|
+
* fatigue-reducer, NOT merely a blocker: clearly-safe commands auto-approve, the rest escalate
|
|
7
|
+
* to the human, and clearly-catastrophic ones may be rejected outright. Default OFF — it costs
|
|
8
|
+
* one LLM call per command — opt-in via {@link GthDevToolsConfig.shell}'s `judge` knob.
|
|
9
|
+
*
|
|
10
|
+
* Validated prior art (both place the judge in front of the human prompt as an auto-approve
|
|
11
|
+
* fatigue-reducer): openclaw `exec-auto-reviewer.ts` and hermes-agent `approval.py` "smart" mode.
|
|
12
|
+
*
|
|
13
|
+
* Two hardening guarantees are baked in here:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Prompt-injection defense.** The command is attacker-controlled text. It is normalized
|
|
16
|
+
* (reusing {@link normalizeCommand} + home-path folding) and embedded inside an XML
|
|
17
|
+
* `<command_to_evaluate>` tag, behind a preamble that states the tagged text is UNTRUSTED
|
|
18
|
+
* DATA to be analyzed, never instructions to follow. See {@link buildJudgePrompt}.
|
|
19
|
+
* 2. **Fail-closed on error.** If the LLM call throws, times out, or returns unparseable
|
|
20
|
+
* output, the verdict returned NEVER auto-approves — it is `high`/escalate. A judge failure
|
|
21
|
+
* can never silently green-light a command. See {@link FAIL_CLOSED_VERDICT}.
|
|
22
|
+
*
|
|
23
|
+
* Fail-closed-on-AMBIGUITY (when the command's target can't be statically resolved) lives in the
|
|
24
|
+
* decision mapping ({@link mapVerdictToAction}), not here, so it applies regardless of what the
|
|
25
|
+
* judge says.
|
|
26
|
+
*
|
|
27
|
+
* Mirrors the QA-3 judge substrate (`packages/review/src/middleware/reviewRateMiddleware.ts`):
|
|
28
|
+
* structured-output evaluation over `config.llm`, wrapped in try/catch.
|
|
29
|
+
*/
|
|
30
|
+
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
|
31
|
+
import * as z from 'zod';
|
|
32
|
+
import type { GthConfig } from '#src/config.js';
|
|
33
|
+
/**
|
|
34
|
+
* Structured verdict the judge model must return. Kept small and conservative:
|
|
35
|
+
* - `risk` is the primary tier driving the decision (low → auto-approve, medium/high → escalate).
|
|
36
|
+
* - `destructive` flags data-loss / irreversible operations (rm, drop, format, force-push, …).
|
|
37
|
+
* - `outOfScope` flags actions outside the current project/work (network exfil, system mutation,
|
|
38
|
+
* touching paths well outside cwd) — a signal to escalate even when not strictly destructive.
|
|
39
|
+
* - `reason` is one short sentence surfaced to the human when escalating.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ShellSafetyVerdictSchema: z.ZodObject<{
|
|
42
|
+
risk: z.ZodEnum<{
|
|
43
|
+
low: "low";
|
|
44
|
+
medium: "medium";
|
|
45
|
+
high: "high";
|
|
46
|
+
}>;
|
|
47
|
+
destructive: z.ZodBoolean;
|
|
48
|
+
outOfScope: z.ZodBoolean;
|
|
49
|
+
reason: z.ZodString;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
/**
|
|
52
|
+
* The judge's structured verdict on a single shell command.
|
|
53
|
+
*/
|
|
54
|
+
export type ShellSafetyVerdict = z.infer<typeof ShellSafetyVerdictSchema>;
|
|
55
|
+
/**
|
|
56
|
+
* The verdict returned whenever the judge cannot produce a trustworthy answer (LLM throws,
|
|
57
|
+
* times out, or returns unparseable output). Fail-closed: `high` + escalate, never auto-approve.
|
|
58
|
+
*/
|
|
59
|
+
export declare const FAIL_CLOSED_VERDICT: ShellSafetyVerdict;
|
|
60
|
+
/**
|
|
61
|
+
* Default wall-clock budget (ms) for the judge LLM call. Kept low so a slow/hung judge can't
|
|
62
|
+
* wedge the approval flow — on timeout we fail closed and escalate. Mirrors openclaw's low
|
|
63
|
+
* exec-reviewer timeout minimum.
|
|
64
|
+
*/
|
|
65
|
+
export declare const JUDGE_DEFAULT_TIMEOUT_MS = 30000;
|
|
66
|
+
/**
|
|
67
|
+
* System preamble for the judge. States the role, the untrusted-input contract (the tagged
|
|
68
|
+
* command is DATA, not instructions), and the bias toward escalation when unsure. Patterned
|
|
69
|
+
* after openclaw's `DEFAULT_EXEC_REVIEWER_SYSTEM_PROMPT` and hermes' untrusted-input framing.
|
|
70
|
+
*/
|
|
71
|
+
export declare const JUDGE_SYSTEM_PROMPT: string;
|
|
72
|
+
/**
|
|
73
|
+
* Detect whether the command invokes an interpreter on a script target AND passes an
|
|
74
|
+
* `$ALL_CAPS` shell-variable expansion in its arguments — openclaw's "script preflight". Such a
|
|
75
|
+
* command can leak environment (often secrets) into the script, so it should bias toward
|
|
76
|
+
* escalation. Lightweight heuristic over the normalized command; a positive flag is fed to the
|
|
77
|
+
* judge prompt AND forces escalation in the decision mapping.
|
|
78
|
+
*
|
|
79
|
+
* @returns true when an interpreter+script invocation also expands an ALL_CAPS env var.
|
|
80
|
+
*/
|
|
81
|
+
export declare function hasScriptEnvLeakRisk(normalizedCommand: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Fold an absolute home path to `~` so the judge sees a stable, less-identifying form (mirrors
|
|
84
|
+
* hermes `_normalize_command_for_detection` path folding). Best-effort: only the literal home
|
|
85
|
+
* dir prefix is folded.
|
|
86
|
+
*/
|
|
87
|
+
export declare function foldHomePath(command: string, home: string | undefined): string;
|
|
88
|
+
/**
|
|
89
|
+
* Build the messages for the judge call: a system preamble ({@link JUDGE_SYSTEM_PROMPT}) plus a
|
|
90
|
+
* human message that embeds the NORMALIZED command inside an XML `<command_to_evaluate>` tag and
|
|
91
|
+
* (optionally) notes the script-env-leak preflight flag. The command text is only ever DATA in
|
|
92
|
+
* the tag — the builder never executes or interpolates it as instructions.
|
|
93
|
+
*
|
|
94
|
+
* Exposed (and returning plain strings) so tests can assert the structure: the tag is present,
|
|
95
|
+
* the untrusted-input preamble is present, and an injection string inside the command lands
|
|
96
|
+
* inside the tag rather than being acted on.
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildJudgePrompt(command: string, options?: {
|
|
99
|
+
home?: string;
|
|
100
|
+
}): {
|
|
101
|
+
system: string;
|
|
102
|
+
user: string;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Vet a single shell command with the judge model and return a structured {@link ShellSafetyVerdict}.
|
|
106
|
+
*
|
|
107
|
+
* - Builds an injection-hardened, normalized prompt ({@link buildJudgePrompt}).
|
|
108
|
+
* - Calls the judge model (defaults to `config.llm`) via `withStructuredOutput(schema)`.
|
|
109
|
+
* - Races the call against {@link JUDGE_DEFAULT_TIMEOUT_MS}.
|
|
110
|
+
* - **Fail-closed:** any throw / timeout / parse failure returns {@link FAIL_CLOSED_VERDICT}
|
|
111
|
+
* (`high`/escalate), never an auto-approve.
|
|
112
|
+
*
|
|
113
|
+
* Note: this only produces a verdict; the auto-approve / escalate / reject decision (including
|
|
114
|
+
* fail-closed-on-ambiguity) is made by {@link mapVerdictToAction} in the runner.
|
|
115
|
+
*/
|
|
116
|
+
export declare function judgeShellCommand(command: string, config: GthConfig, options?: {
|
|
117
|
+
model?: BaseChatModel;
|
|
118
|
+
home?: string;
|
|
119
|
+
timeoutMs?: number;
|
|
120
|
+
}): Promise<ShellSafetyVerdict>;
|
|
121
|
+
/**
|
|
122
|
+
* The action the judge gate resolves to for a single command, BEFORE the human prompt.
|
|
123
|
+
* - `auto-approve` — clearly safe; approve once, do NOT touch the human or the allow-list.
|
|
124
|
+
* - `escalate` — fall through to the existing human approval callback (carrying the verdict).
|
|
125
|
+
* - `reject` — refuse outright without prompting (reserved for clearly-catastrophic verdicts).
|
|
126
|
+
*/
|
|
127
|
+
export type JudgeAction = 'auto-approve' | 'escalate' | 'reject';
|
|
128
|
+
/**
|
|
129
|
+
* Behaviour knobs for the decision mapping, derived from config with safe defaults.
|
|
130
|
+
*/
|
|
131
|
+
export interface JudgeDecisionOptions {
|
|
132
|
+
/** Auto-approve `low`-risk, non-ambiguous, non-flagged commands. Default true. */
|
|
133
|
+
autoApproveLow: boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Reject (without prompting) a clearly-catastrophic verdict (`high` + `destructive`). Default
|
|
136
|
+
* false — keep the gate conservative; EXT-9's hardline floor already refuses truly
|
|
137
|
+
* catastrophic commands at exec time, so the judge's main jobs are auto-approve-low + escalate.
|
|
138
|
+
*/
|
|
139
|
+
blockHigh: boolean;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Pure, testable mapping from a {@link ShellSafetyVerdict} + ambiguity to a {@link JudgeAction}.
|
|
143
|
+
*
|
|
144
|
+
* Order of precedence (fail-closed first):
|
|
145
|
+
* 1. **Fail-closed on ambiguity:** when {@link classifyCommand} returns null — the command
|
|
146
|
+
* composes / substitutes / redirects so its target can't be statically resolved — NEVER
|
|
147
|
+
* auto-approve. Escalate (or reject if `blockHigh` and the verdict is catastrophic). This is
|
|
148
|
+
* enforced regardless of what the judge said, so an unresolvable command can't be slipped
|
|
149
|
+
* through by a manipulated `low` verdict.
|
|
150
|
+
* 2. **Script-env-leak preflight:** if the (normalized) command leaks an ALL_CAPS env var into a
|
|
151
|
+
* script/interpreter, never auto-approve — escalate.
|
|
152
|
+
* 3. `blockHigh` + catastrophic (`high` + `destructive`) → reject.
|
|
153
|
+
* 4. `low` + autoApproveLow + not ambiguous + not flagged → auto-approve.
|
|
154
|
+
* 5. otherwise → escalate.
|
|
155
|
+
*
|
|
156
|
+
* @param command The raw command string (used to recompute ambiguity + preflight independently
|
|
157
|
+
* of the judge, so the gate is robust even if the judge is wrong).
|
|
158
|
+
* @param verdict The judge's verdict (or the fail-closed verdict).
|
|
159
|
+
* @param opts Behaviour knobs.
|
|
160
|
+
*/
|
|
161
|
+
export declare function mapVerdictToAction(command: string, verdict: ShellSafetyVerdict, opts: JudgeDecisionOptions): JudgeAction;
|