@gaunt-sloth/core 2.0.0-alpha.2 → 2.0.0-alpha.3
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.d.ts +175 -1
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +24 -1
- package/dist/core/GthAbstractAgent.js +77 -3
- 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 +68 -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/package.json +6 -5
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ToolApprovalScope } from '#src/core/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Approval persistence scopes, widest-lived last. Alias of the canonical
|
|
4
|
+
* {@link ToolApprovalScope} so the allow-list engine and the decision vocabulary stay in sync.
|
|
5
|
+
*/
|
|
6
|
+
export type ApprovalScope = ToolApprovalScope;
|
|
7
|
+
/**
|
|
8
|
+
* Decide whether a candidate argv contains a flag that *widens* the approved operation.
|
|
9
|
+
*
|
|
10
|
+
* Rule implemented (documented for the coordinator):
|
|
11
|
+
* - Re-derive the meaningful prefix from the candidate's actual argv.
|
|
12
|
+
* - Inspect every token of the candidate that is a flag (starts with `-`).
|
|
13
|
+
* - If any flag token — normalized by stripping a trailing `=value` and lowercasing —
|
|
14
|
+
* is in {@link WIDENING_FLAGS}, the command is considered a widening of the approved
|
|
15
|
+
* operation and the match is REFUSED (returns true).
|
|
16
|
+
*
|
|
17
|
+
* This is purposely a deny-list of operation-changing flags rather than an allow-list of
|
|
18
|
+
* benign flags: benign flag variants (`-b`, `--oneline`, `-la`) are exactly what we WANT
|
|
19
|
+
* to keep auto-approving, while the handful of "run-an-arbitrary-program / redirect-the-
|
|
20
|
+
* transport" flags are what an injected approval must never silently enable.
|
|
21
|
+
*/
|
|
22
|
+
export declare function hasWideningFlag(argv: string[]): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* A holder of approved prefixes with set semantics. Used for both the in-memory session
|
|
25
|
+
* store and the loaded persisted store. Pure data + membership; persistence is layered on
|
|
26
|
+
* top by {@link PersistedAllowlist}.
|
|
27
|
+
*/
|
|
28
|
+
export declare class AllowlistStore {
|
|
29
|
+
private readonly prefixes;
|
|
30
|
+
constructor(initial?: Iterable<string>);
|
|
31
|
+
has(prefix: string): boolean;
|
|
32
|
+
add(prefix: string): void;
|
|
33
|
+
list(): string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The persisted (`always`) allow-list, backed by a JSON file. The path is injected (the
|
|
37
|
+
* runner resolves it via fileUtils → `.gsloth/.gsloth-settings/shell-allowlist.json`) so
|
|
38
|
+
* tests can point it at a temp dir. Loads lazily/defensively: a missing or malformed file
|
|
39
|
+
* yields an empty store rather than throwing (fail-open on READ is safe — an empty
|
|
40
|
+
* allow-list just means "prompt"; it never auto-approves anything).
|
|
41
|
+
*/
|
|
42
|
+
export declare class PersistedAllowlist {
|
|
43
|
+
private readonly store;
|
|
44
|
+
private readonly filePath;
|
|
45
|
+
constructor(filePath: string);
|
|
46
|
+
private static load;
|
|
47
|
+
has(prefix: string): boolean;
|
|
48
|
+
list(): string[];
|
|
49
|
+
/** Add a prefix and persist the whole set to disk. */
|
|
50
|
+
add(prefix: string): void;
|
|
51
|
+
private persist;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read-only view over the stores consulted for an auto-approval check. The runner passes
|
|
55
|
+
* its per-instance session store and (optionally) the persisted store.
|
|
56
|
+
*/
|
|
57
|
+
export interface ApprovalStores {
|
|
58
|
+
session: Pick<AllowlistStore, 'has'>;
|
|
59
|
+
always?: Pick<PersistedAllowlist, 'has'>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decide whether `command` is already approved by the given stores — the gate the runner
|
|
63
|
+
* consults BEFORE prompting the human.
|
|
64
|
+
*
|
|
65
|
+
* Returns true ONLY when ALL of the following hold:
|
|
66
|
+
* 1. {@link classifyCommand} returns a non-null classification (so composition /
|
|
67
|
+
* substitution / redirection commands can never match — anti-injection layer 1);
|
|
68
|
+
* 2. the classified prefix is present in the session OR persisted (`always`) store; AND
|
|
69
|
+
* 3. the safe-bin re-validation passes: the candidate's actual argv contains no
|
|
70
|
+
* operation-widening flag (anti-injection layer 2 — see {@link hasWideningFlag}).
|
|
71
|
+
*
|
|
72
|
+
* Anything else (unclassifiable, unknown prefix, or a widening flag) → false → the human
|
|
73
|
+
* is prompted. Fail-closed by construction.
|
|
74
|
+
*/
|
|
75
|
+
export declare function matchesApproval(command: string, stores: ApprovalStores): boolean;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/shell/allowlist
|
|
3
|
+
*
|
|
4
|
+
* EXT-9 Tier-2: the persisted + session-scoped allow-list engine for the opt-in
|
|
5
|
+
* `run_shell_command` tool. Once a human approves a command at `session` or `always`
|
|
6
|
+
* scope, future commands with the same classified prefix ({@link classifyCommand}) are
|
|
7
|
+
* auto-approved without re-prompting — the ergonomics improvement mature agents
|
|
8
|
+
* (opencode/openclaw/hermes) ship.
|
|
9
|
+
*
|
|
10
|
+
* SECURITY — a naive "remember the prefix" allow-list is an injection vector. Two layers
|
|
11
|
+
* guard it:
|
|
12
|
+
* 1. **Classification fail-closed** (arity.ts): any command with shell composition,
|
|
13
|
+
* substitution, or redirection returns `null` and can NEVER match, so
|
|
14
|
+
* `git checkout x; rm -rf /` does not ride an approved `git checkout *`.
|
|
15
|
+
* 2. **Safe-bin anti-widening re-validation** (here, after opencode/openclaw): a stored
|
|
16
|
+
* approval is matched ONLY if the candidate command's first non-flag operand region
|
|
17
|
+
* does not introduce a flag that *widens or redirects* the approved operation. The
|
|
18
|
+
* exact rule is documented on {@link matchesApproval} / {@link hasWideningFlag}.
|
|
19
|
+
*
|
|
20
|
+
* No module-global mutable state: the session store is a per-instance class so concurrent
|
|
21
|
+
* sessions (ACP / AG-UI multi-session) cannot stomp each other. The persisted (`always`)
|
|
22
|
+
* store is a small JSON file the runner loads once per instance.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { classifyCommand, tokenize } from '#src/core/shell/arity.js';
|
|
26
|
+
import { normalizeCommand } from '#src/core/shell/normalize.js';
|
|
27
|
+
const PERSISTED_VERSION = 1;
|
|
28
|
+
/**
|
|
29
|
+
* Flags that, if present in a candidate command but not implied by the approved prefix,
|
|
30
|
+
* could change the *operation* the human approved (point it at a different transport,
|
|
31
|
+
* exec a hook, follow an attacker-controlled URL). Approving `git clone` must not
|
|
32
|
+
* green-light `git clone --upload-pack=evil`. This list is intentionally conservative:
|
|
33
|
+
* it is the openclaw "safe-bin" idea reduced to a deny-set of operation-changing flags
|
|
34
|
+
* that commonly appear in shell-injection / supply-chain abuse.
|
|
35
|
+
*
|
|
36
|
+
* Matched against the flag token with any `=value` stripped, case-insensitively.
|
|
37
|
+
*/
|
|
38
|
+
const WIDENING_FLAGS = new Set([
|
|
39
|
+
// git: remote command / hook overrides.
|
|
40
|
+
'--upload-pack',
|
|
41
|
+
'--receive-pack',
|
|
42
|
+
'--exec',
|
|
43
|
+
'-u', // git clone -u <upload-pack>
|
|
44
|
+
'--config', // git -c is the short form; --config can inject core.sshCommand etc.
|
|
45
|
+
'-c', // git -c core.sshCommand='...' — arbitrary command execution
|
|
46
|
+
// generic "run this program" escape hatches across tools.
|
|
47
|
+
'--exec-path',
|
|
48
|
+
'-exec', // find -exec <cmd>
|
|
49
|
+
'--use-askpass',
|
|
50
|
+
'--ssh-command',
|
|
51
|
+
// package managers: lifecycle-script / arbitrary-script toggles.
|
|
52
|
+
'--unsafe-perm',
|
|
53
|
+
'--ignore-scripts=false',
|
|
54
|
+
'--allow-scripts',
|
|
55
|
+
// curl/wget style follow/exec (in case a bare binary is approved).
|
|
56
|
+
'-o', // write to arbitrary path
|
|
57
|
+
'--output',
|
|
58
|
+
'-T', // upload
|
|
59
|
+
'--upload-file',
|
|
60
|
+
]);
|
|
61
|
+
/**
|
|
62
|
+
* Decide whether a candidate argv contains a flag that *widens* the approved operation.
|
|
63
|
+
*
|
|
64
|
+
* Rule implemented (documented for the coordinator):
|
|
65
|
+
* - Re-derive the meaningful prefix from the candidate's actual argv.
|
|
66
|
+
* - Inspect every token of the candidate that is a flag (starts with `-`).
|
|
67
|
+
* - If any flag token — normalized by stripping a trailing `=value` and lowercasing —
|
|
68
|
+
* is in {@link WIDENING_FLAGS}, the command is considered a widening of the approved
|
|
69
|
+
* operation and the match is REFUSED (returns true).
|
|
70
|
+
*
|
|
71
|
+
* This is purposely a deny-list of operation-changing flags rather than an allow-list of
|
|
72
|
+
* benign flags: benign flag variants (`-b`, `--oneline`, `-la`) are exactly what we WANT
|
|
73
|
+
* to keep auto-approving, while the handful of "run-an-arbitrary-program / redirect-the-
|
|
74
|
+
* transport" flags are what an injected approval must never silently enable.
|
|
75
|
+
*/
|
|
76
|
+
export function hasWideningFlag(argv) {
|
|
77
|
+
for (const tok of argv) {
|
|
78
|
+
if (!tok.startsWith('-'))
|
|
79
|
+
continue;
|
|
80
|
+
const bare = tok.split('=', 1)[0].toLowerCase();
|
|
81
|
+
if (WIDENING_FLAGS.has(bare))
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A holder of approved prefixes with set semantics. Used for both the in-memory session
|
|
88
|
+
* store and the loaded persisted store. Pure data + membership; persistence is layered on
|
|
89
|
+
* top by {@link PersistedAllowlist}.
|
|
90
|
+
*/
|
|
91
|
+
export class AllowlistStore {
|
|
92
|
+
prefixes;
|
|
93
|
+
constructor(initial = []) {
|
|
94
|
+
this.prefixes = new Set(initial);
|
|
95
|
+
}
|
|
96
|
+
has(prefix) {
|
|
97
|
+
return this.prefixes.has(prefix);
|
|
98
|
+
}
|
|
99
|
+
add(prefix) {
|
|
100
|
+
this.prefixes.add(prefix);
|
|
101
|
+
}
|
|
102
|
+
list() {
|
|
103
|
+
return [...this.prefixes];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The persisted (`always`) allow-list, backed by a JSON file. The path is injected (the
|
|
108
|
+
* runner resolves it via fileUtils → `.gsloth/.gsloth-settings/shell-allowlist.json`) so
|
|
109
|
+
* tests can point it at a temp dir. Loads lazily/defensively: a missing or malformed file
|
|
110
|
+
* yields an empty store rather than throwing (fail-open on READ is safe — an empty
|
|
111
|
+
* allow-list just means "prompt"; it never auto-approves anything).
|
|
112
|
+
*/
|
|
113
|
+
export class PersistedAllowlist {
|
|
114
|
+
store;
|
|
115
|
+
filePath;
|
|
116
|
+
constructor(filePath) {
|
|
117
|
+
this.filePath = filePath;
|
|
118
|
+
this.store = new AllowlistStore(PersistedAllowlist.load(filePath));
|
|
119
|
+
}
|
|
120
|
+
static load(filePath) {
|
|
121
|
+
try {
|
|
122
|
+
if (!existsSync(filePath))
|
|
123
|
+
return [];
|
|
124
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
125
|
+
const parsed = JSON.parse(raw);
|
|
126
|
+
if (!parsed || !Array.isArray(parsed.prefixes))
|
|
127
|
+
return [];
|
|
128
|
+
return parsed.prefixes.filter((p) => typeof p === 'string');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Corrupt/unreadable file → behave as empty (fail-closed on auto-approval).
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
has(prefix) {
|
|
136
|
+
return this.store.has(prefix);
|
|
137
|
+
}
|
|
138
|
+
list() {
|
|
139
|
+
return this.store.list();
|
|
140
|
+
}
|
|
141
|
+
/** Add a prefix and persist the whole set to disk. */
|
|
142
|
+
add(prefix) {
|
|
143
|
+
if (this.store.has(prefix))
|
|
144
|
+
return;
|
|
145
|
+
this.store.add(prefix);
|
|
146
|
+
this.persist();
|
|
147
|
+
}
|
|
148
|
+
persist() {
|
|
149
|
+
const file = {
|
|
150
|
+
version: PERSISTED_VERSION,
|
|
151
|
+
prefixes: this.store.list().sort(),
|
|
152
|
+
};
|
|
153
|
+
writeFileSync(this.filePath, JSON.stringify(file, null, 2) + '\n', 'utf8');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Decide whether `command` is already approved by the given stores — the gate the runner
|
|
158
|
+
* consults BEFORE prompting the human.
|
|
159
|
+
*
|
|
160
|
+
* Returns true ONLY when ALL of the following hold:
|
|
161
|
+
* 1. {@link classifyCommand} returns a non-null classification (so composition /
|
|
162
|
+
* substitution / redirection commands can never match — anti-injection layer 1);
|
|
163
|
+
* 2. the classified prefix is present in the session OR persisted (`always`) store; AND
|
|
164
|
+
* 3. the safe-bin re-validation passes: the candidate's actual argv contains no
|
|
165
|
+
* operation-widening flag (anti-injection layer 2 — see {@link hasWideningFlag}).
|
|
166
|
+
*
|
|
167
|
+
* Anything else (unclassifiable, unknown prefix, or a widening flag) → false → the human
|
|
168
|
+
* is prompted. Fail-closed by construction.
|
|
169
|
+
*/
|
|
170
|
+
export function matchesApproval(command, stores) {
|
|
171
|
+
const classification = classifyCommand(command, normalizeCommand);
|
|
172
|
+
if (!classification)
|
|
173
|
+
return false; // layer 1: composition/redirection/substitution.
|
|
174
|
+
const approved = stores.session.has(classification.prefix) ||
|
|
175
|
+
(stores.always?.has(classification.prefix) ?? false);
|
|
176
|
+
if (!approved)
|
|
177
|
+
return false;
|
|
178
|
+
// Layer 2: re-derive argv from the normalized command and refuse if an operation-
|
|
179
|
+
// widening flag is present that the human's prefix-level approval never implied.
|
|
180
|
+
const argv = tokenize(normalizeCommand(command));
|
|
181
|
+
if (!argv)
|
|
182
|
+
return false; // unbalanced quoting → fail-closed.
|
|
183
|
+
if (hasWideningFlag(argv))
|
|
184
|
+
return false;
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=allowlist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allowlist.js","sourceRoot":"","sources":["../../../src/core/shell/allowlist.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|