@gaunt-sloth/core 2.0.0-beta.0 → 2.0.0-beta.2
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/LICENSE +6 -6
- package/dist/config/loader.d.ts +13 -0
- package/dist/config/loader.js +55 -11
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +10 -0
- package/dist/config/schema.js +46 -24
- package/dist/config/schema.js.map +1 -1
- package/dist/config/shell-policy.d.ts +142 -1
- package/dist/config/shell-policy.js +114 -7
- package/dist/config/shell-policy.js.map +1 -1
- package/dist/config/types.d.ts +19 -0
- package/dist/config/types.js.map +1 -1
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/GthAbstractAgent.d.ts +31 -0
- package/dist/core/GthAbstractAgent.js +159 -4
- package/dist/core/GthAbstractAgent.js.map +1 -1
- package/dist/core/GthAgentRunner.d.ts +314 -18
- package/dist/core/GthAgentRunner.js +1032 -101
- package/dist/core/GthAgentRunner.js.map +1 -1
- package/dist/core/approvals/approvalRequest.d.ts +247 -0
- package/dist/core/approvals/approvalRequest.js +315 -0
- package/dist/core/approvals/approvalRequest.js.map +1 -0
- package/dist/core/approvals/grants.d.ts +165 -9
- package/dist/core/approvals/grants.js +702 -57
- package/dist/core/approvals/grants.js.map +1 -1
- package/dist/core/plainToolIndication.d.ts +11 -1
- package/dist/core/plainToolIndication.js +25 -10
- package/dist/core/plainToolIndication.js.map +1 -1
- package/dist/core/shell/abstention.d.ts +1 -1
- package/dist/core/shell/abstention.js +65 -11
- package/dist/core/shell/abstention.js.map +1 -1
- package/dist/core/shell/alignment.d.ts +491 -0
- package/dist/core/shell/alignment.js +687 -0
- package/dist/core/shell/alignment.js.map +1 -0
- package/dist/core/shell/approvalCapture.d.ts +53 -41
- package/dist/core/shell/approvalCapture.js +0 -42
- package/dist/core/shell/approvalCapture.js.map +1 -1
- package/dist/core/shell/approvalStop.d.ts +121 -3
- package/dist/core/shell/approvalStop.js +153 -90
- package/dist/core/shell/approvalStop.js.map +1 -1
- package/dist/core/shell/hardline.d.ts +26 -4
- package/dist/core/shell/hardline.js +321 -16
- package/dist/core/shell/hardline.js.map +1 -1
- package/dist/core/shell/negotiation.d.ts +352 -63
- package/dist/core/shell/negotiation.js +295 -123
- package/dist/core/shell/negotiation.js.map +1 -1
- package/dist/core/shell/openWorld.d.ts +135 -0
- package/dist/core/shell/openWorld.js +572 -35
- package/dist/core/shell/openWorld.js.map +1 -1
- package/dist/core/shell/provenance.d.ts +91 -0
- package/dist/core/shell/provenance.js +136 -0
- package/dist/core/shell/provenance.js.map +1 -0
- package/dist/core/shell/rater.d.ts +394 -163
- package/dist/core/shell/rater.js +536 -238
- package/dist/core/shell/rater.js.map +1 -1
- package/dist/core/shell/raterHealth.d.ts +101 -0
- package/dist/core/shell/raterHealth.js +121 -0
- package/dist/core/shell/raterHealth.js.map +1 -0
- package/dist/core/shell/raterModel.d.ts +17 -8
- package/dist/core/shell/raterModel.js +11 -8
- package/dist/core/shell/raterModel.js.map +1 -1
- package/dist/core/shell/rejection.d.ts +20 -5
- package/dist/core/shell/rejection.js +18 -4
- package/dist/core/shell/rejection.js.map +1 -1
- package/dist/core/toolDisplay.d.ts +42 -0
- package/dist/core/toolDisplay.js +31 -0
- package/dist/core/toolDisplay.js.map +1 -1
- package/dist/core/types.d.ts +134 -16
- package/dist/core/types.js.map +1 -1
- package/dist/providers/configurationPassthrough.d.ts +36 -6
- package/dist/providers/configurationPassthrough.js +40 -28
- package/dist/providers/configurationPassthrough.js.map +1 -1
- package/dist/providers/openrouter.js +36 -6
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/vertexai.js +21 -0
- package/dist/providers/vertexai.js.map +1 -1
- package/dist/utils/binaryOutputUtils.js +103 -21
- package/dist/utils/binaryOutputUtils.js.map +1 -1
- package/dist/utils/fileUtils.d.ts +7 -1
- package/dist/utils/fileUtils.js +17 -3
- package/dist/utils/fileUtils.js.map +1 -1
- package/dist/utils/globalConfigUtils.d.ts +5 -2
- package/dist/utils/globalConfigUtils.js +14 -3
- package/dist/utils/globalConfigUtils.js.map +1 -1
- package/dist/utils/vertexaiUtils.js +61 -3
- package/dist/utils/vertexaiUtils.js.map +1 -1
- package/package.json +4 -4
- package/schema/gsloth-config.schema.json +34 -0
|
@@ -1,94 +1,96 @@
|
|
|
1
|
+
import { UNRESOLVED_MCP_SERVER } from '#src/core/approvals/mcpSubjects.js';
|
|
2
|
+
import { renderApprovalEntryObject } from '#src/config/schema.js';
|
|
3
|
+
import { frameUntrustedCommand, frameUntrustedText, frameWidthFor, narrowTerminalNotice, neutralizeToOneLine, neutralizeUntrustedText, } from '#src/core/shell/framing.js';
|
|
1
4
|
/**
|
|
2
|
-
* @
|
|
5
|
+
* [[EXT-115]] — **the parts that name WHAT was gated**, branched on the {@link ApprovalSubject}
|
|
6
|
+
* kind the gate itself decided the call on.
|
|
3
7
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
+
* Both stops carried the label `Command` unconditionally, so a gated `write_file` in CI read as
|
|
9
|
+
* `Command: write_file` and a gated MCP call read the same way — the framing [[TUI-C67]] removed
|
|
10
|
+
* from the two terminal prompts, surviving on the path with nobody to ask, where the message is the
|
|
11
|
+
* only thing anyone sees. It lives here rather than in either subclass because both stops make the
|
|
12
|
+
* identical claim about the identical subject, and a branch written twice is a branch that comes to
|
|
13
|
+
* disagree with itself.
|
|
8
14
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
15
|
+
* **The identifier stays in a `value`/`command` part and is NEVER interpolated into a sentence.**
|
|
16
|
+
* This is the one line the fix must not cross, and it is why the prose form
|
|
17
|
+
* `core/approvals/promptHeader` renders is deliberately not imported here: an `own` part is painted
|
|
18
|
+
* as-is *precisely because nothing can forge it*, everything else goes through the shared renderer,
|
|
19
|
+
* and the label sits on its own row for the reason {@link approvalStopRows} states. On the `mcpTool`
|
|
20
|
+
* arm the name comes from a third-party server's own tool listing, so a sentence carrying it would
|
|
21
|
+
* put attacker-supplied bytes into the gate's own voice — the forgery TUI-C26 closed.
|
|
12
22
|
*
|
|
13
|
-
*
|
|
14
|
-
* agent loop. It is not a rejection the model can respond to, and no rung except `bypass` can
|
|
15
|
-
* turn it into anything else."
|
|
16
|
-
* - **{@link NonInteractiveEscalationError}** (§6.2) — where no human can answer, *every*
|
|
17
|
-
* escalation is an immediate non-zero exit carrying the command, the rating and its reason.
|
|
18
|
-
* There is no prompt, no waiting, and never a timeout into approval. Teams that need specific
|
|
19
|
-
* commands to run unattended declare them in `approvals.allow` (§3), which is consulted before
|
|
20
|
-
* the rater and therefore never escalates.
|
|
23
|
+
* The arms:
|
|
21
24
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
25
|
+
* - `shell` — `Command`, on a `command` part. Unchanged, and unchanged deliberately: that part kind
|
|
26
|
+
* is what runs the site extraction, which is the framing a shell command's composition and
|
|
27
|
+
* substitution boundaries are shown through.
|
|
28
|
+
* - `mcpTool` with a nameable server — `MCP tool`, plus a second `MCP server` part. Naming the
|
|
29
|
+
* server is the point rather than a side effect: which server a call reaches is the load-bearing
|
|
30
|
+
* fact the old label hid, and it is the user's own `mcpServers` key (§4.7.5).
|
|
31
|
+
* - everything else — `Tool`, on a `value` part.
|
|
26
32
|
*
|
|
27
|
-
*
|
|
33
|
+
* **A `mcpTool` call whose server would render blank falls to the `Tool` arm**, mirroring
|
|
34
|
+
* `approvalPromptHeader`'s own fallback so the two cannot describe one call two ways. The guard is
|
|
35
|
+
* on the **rendered** server, not the raw one: {@link UNRESOLVED_MCP_SERVER} is the empty string,
|
|
36
|
+
* but a server key of whitespace alone — which `z.string().min(1)` admits — is also empty once
|
|
37
|
+
* neutralised, and it would otherwise render an `MCP server` row promising an identity that is not
|
|
38
|
+
* there. `text` already carries the full registered name in that case, which still shows the
|
|
39
|
+
* `mcp__` namespace.
|
|
28
40
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* **Because half of it is the surface's job, the surfaces are enumerated rather than assumed.**
|
|
57
|
-
* Every place that catches an `ApprovalStopError` and prints it calls {@link approvalStopRows} (or
|
|
58
|
-
* the Ink component): `runtime/singleShot.ts`, `runtime/conversation.ts`,
|
|
59
|
-
* `agent/modules/interactiveSessionModule.ts` (both its per-turn catch and its outermost one, which
|
|
60
|
-
* is where the `-m` path lands), `review/modules/reviewModule.ts`, `app/commands/prCommand.ts`
|
|
61
|
-
* (which catches what the PR-discovery agent throws) and `app/tui/components/App.tsx`.
|
|
62
|
-
* **Adding a surface that catches a stop means adding it to that list.**
|
|
41
|
+
* **A stop built with no subject keeps the shell shape**, which is the opposite of
|
|
42
|
+
* `approvalPromptHeader`'s fail-to-vague and is chosen for a reason that does not apply there. That
|
|
43
|
+
* function picks between three sentences, so it picks the one true of every call; this one also
|
|
44
|
+
* picks the PART KIND, and falling back to `value` would silently drop the extracted-site notice
|
|
45
|
+
* from a shell stop — `framing.ts` calls that the most decision-relevant thing in a command. Every
|
|
46
|
+
* production throw site passes a subject (`GthAgentRunner.decideToolApprovalInner` and
|
|
47
|
+
* `haltOrRunAnyway` both have the decision's own subject in hand), so this fallback is reached only
|
|
48
|
+
* by a hand-built error, where showing more is better than labelling more cautiously.
|
|
49
|
+
*/
|
|
50
|
+
function subjectParts(text, subject) {
|
|
51
|
+
if (subject === undefined || subject.kind === 'shell') {
|
|
52
|
+
return [{ kind: 'command', label: 'Command', text }];
|
|
53
|
+
}
|
|
54
|
+
if (subject.kind === 'mcpTool' && neutralizeToOneLine(subject.server) !== UNRESOLVED_MCP_SERVER) {
|
|
55
|
+
return [
|
|
56
|
+
{ kind: 'value', label: 'MCP tool', text },
|
|
57
|
+
{ kind: 'value', label: 'MCP server', text: subject.server },
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
return [{ kind: 'value', label: 'Tool', text }];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* [[EXT-115]] — **the `approvals.allow` entry the general recovery offers as an example**, one per
|
|
64
|
+
* subject kind, rendered by the one renderer that spells an entry ({@link
|
|
65
|
+
* renderApprovalEntryObject}) so an example and a stored grant cannot come to disagree about the
|
|
66
|
+
* grammar.
|
|
63
67
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* without any of its code ever seeing this class. That one frames the text itself. So the honest
|
|
69
|
-
* statement is not "every other consumer is a non-terminal" but: **a consumer that prints to a
|
|
70
|
-
* terminal frames what it prints, whether it holds the error or only its text**, and the ones that
|
|
71
|
-
* legitimately do neither are the file and protocol sinks (the approvals archive, the JUnit/JSON
|
|
72
|
-
* reports, the AG-UI event) plus `reviewModule`'s `displayDebug` of the raw error, which is a debug
|
|
73
|
-
* channel. All of them still inherit the neutralisation, which is what keeps the worst case a
|
|
74
|
-
* wrapped line rather than a repainted screen.
|
|
68
|
+
* The kinds line up one for one with {@link ApprovalEntryType}, which is why this needs no mapping:
|
|
69
|
+
* a `tool` call's remedy is a `tool` entry, and a `shell` entry can never match one (`matchEntry`
|
|
70
|
+
* refuses a type mismatch outright), so the shell example this used to print unconditionally was a
|
|
71
|
+
* line the reader could paste and watch do nothing.
|
|
75
72
|
*
|
|
76
|
-
* **
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* the truth rather than a screen; the message is the *presentation*, and a presentation that can
|
|
81
|
-
* repaint the terminal is not one. The render path frames from those raw fields, so its gutter
|
|
82
|
-
* numbers the command's real lines rather than one escaped line.
|
|
73
|
+
* **Only the kind is interpolated — never a name, a server or a command.** These are the gate's own
|
|
74
|
+
* `own`-part sentences, the one part class a surface may paint raw, and a subject-derived
|
|
75
|
+
* identifier reaching one is exactly the forgery route the part tagging exists to close. The kind is
|
|
76
|
+
* a closed three-literal union, so nothing model-authored can ride in on it.
|
|
83
77
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* through the shared renderer.
|
|
78
|
+
* **`*` on the `mcpTool` example, deliberately.** In production this general form is reached for an
|
|
79
|
+
* `mcpTool` subject only when the server could NOT be attributed — a specific entry is derived
|
|
80
|
+
* instead whenever one can be (`toolGrantEntry`) — and §4.7.5 makes `*` the reserved literal that is
|
|
81
|
+
* the only thing able to match such a call. Naming a plausible server there would hand the reader a
|
|
82
|
+
* line that cannot match the call they were just refused.
|
|
90
83
|
*/
|
|
91
|
-
|
|
84
|
+
const GENERAL_ALLOW_EXAMPLE = Object.freeze({
|
|
85
|
+
shell: renderApprovalEntryObject({ type: 'shell', matcher: 'exact', pattern: 'npm test' }),
|
|
86
|
+
tool: renderApprovalEntryObject({ type: 'tool', matcher: 'exact', pattern: 'gth_web_fetch' }),
|
|
87
|
+
mcpTool: renderApprovalEntryObject({
|
|
88
|
+
type: 'mcpTool',
|
|
89
|
+
server: '*',
|
|
90
|
+
matcher: 'exact',
|
|
91
|
+
pattern: 'search_issues',
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
92
94
|
/**
|
|
93
95
|
* The parts as the one string an `Error.message` has to be, with every untrusted part neutralised.
|
|
94
96
|
*
|
|
@@ -154,7 +156,11 @@ export function approvalStopRows(parts, options) {
|
|
|
154
156
|
* cannot shows {@link message}, which is already neutralised and is already the whole explanation.
|
|
155
157
|
*/
|
|
156
158
|
export class ApprovalStopError extends Error {
|
|
157
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* What ended the run, exactly as the agent proposed it: the command for a shell subject, and the
|
|
161
|
+
* registered tool name for a gated tool or MCP call. The field name predates the gate widening
|
|
162
|
+
* past the shell; {@link subjectParts} is what decides the word the MESSAGE calls it.
|
|
163
|
+
*/
|
|
158
164
|
command;
|
|
159
165
|
/** The message's pieces, tagged with who wrote each — see {@link ApprovalStopPart}. */
|
|
160
166
|
parts;
|
|
@@ -167,6 +173,21 @@ export class ApprovalStopError extends Error {
|
|
|
167
173
|
this.name = new.target.name;
|
|
168
174
|
}
|
|
169
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* The **blunter** of the two recoveries a stop may name, and it is named second wherever both
|
|
178
|
+
* appear.
|
|
179
|
+
*
|
|
180
|
+
* §4.2 makes `approvals.allow` the supported way to run a specific command unattended: it is
|
|
181
|
+
* consulted before the rater and therefore before §4.6's deterministic preflight, so a command
|
|
182
|
+
* declared there never reaches a halt or an escalation at all. `bypass` also works and is far
|
|
183
|
+
* blunter — it turns off the rater, the prompts and the halt together, for every command, for the
|
|
184
|
+
* whole run. The ordering is the message: the answer first, the last resort after it, with what it
|
|
185
|
+
* costs stated rather than implied.
|
|
186
|
+
*
|
|
187
|
+
* One sentence, shared, so the two stops that offer it cannot come to describe it differently.
|
|
188
|
+
*/
|
|
189
|
+
const BYPASS_LAST_RESORT = `Dropping to approvals "bypass" also works, but it turns off the rater, ` +
|
|
190
|
+
`the prompts and the halt for every command in the run.`;
|
|
170
191
|
/**
|
|
171
192
|
* §4.2 — an `attack` outcome: the command's own **structure** evidenced compromise (§4.1.1 —
|
|
172
193
|
* credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
|
|
@@ -187,20 +208,19 @@ export class ApprovalStopError extends Error {
|
|
|
187
208
|
export class AttackHaltError extends ApprovalStopError {
|
|
188
209
|
/** The rater's explanation of what the command's structure showed. */
|
|
189
210
|
reason;
|
|
190
|
-
constructor(command, reason) {
|
|
211
|
+
constructor(command, reason, subject) {
|
|
191
212
|
super([
|
|
192
213
|
{
|
|
193
214
|
kind: 'own',
|
|
194
215
|
text: 'Run halted: the auto-rater rated this command as an attack, which ends the run.',
|
|
195
216
|
},
|
|
196
|
-
|
|
217
|
+
...subjectParts(command, subject),
|
|
197
218
|
{ kind: 'value', label: 'Reason', text: reason },
|
|
198
219
|
{
|
|
199
220
|
kind: 'own',
|
|
200
221
|
text: `This is not negotiable. If this command is legitimate and you need it to run, declare ` +
|
|
201
222
|
`it in approvals.allow — that list is consulted before the auto-rater, so it never ` +
|
|
202
|
-
`reaches a halt.
|
|
203
|
-
`the prompts and the halt for every command in the run.`,
|
|
223
|
+
`reaches a halt. ${BYPASS_LAST_RESORT}`,
|
|
204
224
|
},
|
|
205
225
|
], command);
|
|
206
226
|
this.reason = reason;
|
|
@@ -230,10 +250,35 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
|
|
|
230
250
|
* the last command and give no hint that the agent had already been told twice what to fix.
|
|
231
251
|
*/
|
|
232
252
|
negotiation;
|
|
233
|
-
|
|
253
|
+
/**
|
|
254
|
+
* [[EXT-106]] §4 — **the `approvals.allow` entry that would let THIS command run**, rendered in the
|
|
255
|
+
* object form a user writes in a config file, or `undefined` when none can soundly be derived.
|
|
256
|
+
*
|
|
257
|
+
* A refusal that stands has to say what would lift it, and a generic example leaves the reader to
|
|
258
|
+
* translate it — for a command the deterministic preflight floors, that translation is the entire
|
|
259
|
+
* remedy. `approvals.allow` is consulted before the rater and therefore before §4.6's preflight,
|
|
260
|
+
* so it is the one thing that lifts a floor the agent cannot argue past.
|
|
261
|
+
*
|
|
262
|
+
* **Derived by the caller, never here.** The caller holds what an entry has to be derived from —
|
|
263
|
+
* the normalized command for a shell subject (`shellApprovalEntryFor`), the tool's identity and
|
|
264
|
+
* host for a tool or MCP one (`toolGrantEntry`) — and it is the same derivation the escalation
|
|
265
|
+
* menu's *always approve* stores, so a message and a menu cannot promise different things about
|
|
266
|
+
* one call. `undefined` falls back to the general form, which is the right answer whenever a
|
|
267
|
+
* specific one would be wrong — an entry that is subtly too broad, or simply inert, is worse than
|
|
268
|
+
* none. That is what a call whose MCP server could not be attributed gets: `toolGrantEntry`
|
|
269
|
+
* returns `null` for it, because `server` cannot hold the unresolved sentinel.
|
|
270
|
+
*
|
|
271
|
+
* **It is untrusted text.** It embeds the command the model proposed, so it is carried as a
|
|
272
|
+
* labelled `value` part and neutralised with every other untrusted string; putting it in an `own`
|
|
273
|
+
* part would route model-authored bytes into the one part class a surface may paint raw.
|
|
274
|
+
*/
|
|
275
|
+
allowEntry;
|
|
276
|
+
constructor(command, outcome, reason, escalatedBy, negotiation, allowEntry, subject) {
|
|
234
277
|
const parts = [
|
|
278
|
+
// [[EXT-115]] — the lead sentence takes NO branch: it is already kind-neutral and true of a
|
|
279
|
+
// shell command, a tool call and an MCP call alike. Only the subject's own label moves.
|
|
235
280
|
{ kind: 'own', text: 'Approval required, but this session has no one to ask.' },
|
|
236
|
-
|
|
281
|
+
...subjectParts(command, subject),
|
|
237
282
|
];
|
|
238
283
|
if (outcome)
|
|
239
284
|
parts.push({ kind: 'value', label: 'Rating', text: outcome });
|
|
@@ -250,13 +295,30 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
|
|
|
250
295
|
`should run unattended.`,
|
|
251
296
|
});
|
|
252
297
|
}
|
|
298
|
+
else if (allowEntry) {
|
|
299
|
+
// [[EXT-106]] §4 — the same recovery `AttackHaltError` names, in the same order (the
|
|
300
|
+
// allow-list, then `bypass` as the blunter last resort), with the entry filled in for the
|
|
301
|
+
// command in hand. "Before the auto-rater" is stated as reaching further than the rater
|
|
302
|
+
// because that is the fact a floored command's reader needs: §4.6's preflight runs inside the
|
|
303
|
+
// rating decision, so an allow match is ahead of the floor as well as ahead of the model.
|
|
304
|
+
parts.push({
|
|
305
|
+
kind: 'own',
|
|
306
|
+
text: `Declare the commands this run is allowed to execute in approvals.allow — that list is ` +
|
|
307
|
+
`consulted before the auto-rater, ahead of its deterministic preflights, and never ` +
|
|
308
|
+
`escalates. For this command, add:`,
|
|
309
|
+
});
|
|
310
|
+
parts.push({ kind: 'value', label: 'approvals.allow entry', text: allowEntry });
|
|
311
|
+
parts.push({ kind: 'own', text: BYPASS_LAST_RESORT });
|
|
312
|
+
}
|
|
253
313
|
else {
|
|
314
|
+
// [[EXT-115]] — the example is of the SUBJECT'S OWN KIND. `matchEntry` refuses a type
|
|
315
|
+
// mismatch outright, so the `shell` example this used to print unconditionally was, for a
|
|
316
|
+
// gated tool or MCP call, a line the reader could paste and watch match nothing.
|
|
254
317
|
parts.push({
|
|
255
318
|
kind: 'own',
|
|
256
319
|
text: `Declare the commands this run is allowed to execute in approvals.allow — write each ` +
|
|
257
|
-
`one as an explicit entry, for example {
|
|
258
|
-
|
|
259
|
-
`escalates.`,
|
|
320
|
+
`one as an explicit entry, for example ${GENERAL_ALLOW_EXAMPLE[subject?.kind ?? 'shell']}` +
|
|
321
|
+
`. That list is consulted before the auto-rater and never escalates.`,
|
|
260
322
|
});
|
|
261
323
|
}
|
|
262
324
|
super(parts, command);
|
|
@@ -264,6 +326,7 @@ export class NonInteractiveEscalationError extends ApprovalStopError {
|
|
|
264
326
|
this.reason = reason;
|
|
265
327
|
this.escalatedBy = escalatedBy;
|
|
266
328
|
this.negotiation = negotiation;
|
|
329
|
+
this.allowEntry = allowEntry;
|
|
267
330
|
}
|
|
268
331
|
}
|
|
269
332
|
//# sourceMappingURL=approvalStop.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"AA2FA,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAE3E,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,4BAA4B,CAAC;AAsCpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,SAAS,YAAY,CAAC,IAAY,EAAE,OAAoC;IACtE,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QACtD,OAAO,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,mBAAmB,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,qBAAqB,EAAE,CAAC;QAChG,OAAO;YACL,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;YAC1C,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE;SAC7D,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,qBAAqB,GAAgD,MAAM,CAAC,MAAM,CAAC;IACvF,KAAK,EAAE,yBAAyB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;IAC1F,IAAI,EAAE,yBAAyB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAC7F,OAAO,EAAE,yBAAyB,CAAC;QACjC,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,OAAO;QAChB,OAAO,EAAE,eAAe;KACzB,CAAC;CACH,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,SAAS,cAAc,CAAC,KAAkC;IACxD,OAAO,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,cAAc,CAAC,IAAsB;IAC5C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,KAAK;YACR,OAAO,IAAI,CAAC,IAAI,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvE;YACE,OAAO,KAAK,IAAI,CAAC,KAAK,KAAK,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACpE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAkC,EAClC,OAA8B;IAE9B,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9C,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACzD,IAAI,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrB,SAAS;QACX,CAAC;QACD,+FAA+F;QAC/F,+FAA+F;QAC/F,kEAAkE;QAClE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACzD,MAAM,MAAM,GACV,IAAI,CAAC,IAAI,KAAK,SAAS;YACrB,CAAC,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC;YAC7C,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAgB,iBAAkB,SAAQ,KAAK;IACnD;;;;OAIG;IACM,OAAO,CAAS;IAEzB,uFAAuF;IAC9E,KAAK,CAA8B;IAE5C,YAAsB,KAAkC,EAAE,OAAe;QACvE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,+FAA+F;QAC/F,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,IAAI,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,kBAAkB,GACtB,yEAAyE;IACzE,wDAAwD,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACpD,sEAAsE;IAC7D,MAAM,CAAS;IAExB,YAAY,OAAe,EAAE,MAAc,EAAE,OAAyB;QACpE,KAAK,CACH;YACE;gBACE,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,iFAAiF;aACxF;YACD,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC;YACjC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE;YAChD;gBACE,IAAI,EAAE,KAAK;gBACX,IAAI,EACF,wFAAwF;oBACxF,oFAAoF;oBACpF,mBAAmB,kBAAkB,EAAE;aAC1C;SACF,EACD,OAAO,CACR,CAAC;QACF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,6BAA8B,SAAQ,iBAAiB;IAClE,gFAAgF;IACvE,OAAO,CAAqB;IACrC,sDAAsD;IAC7C,MAAM,CAAqB;IACpC;;;;;OAKG;IACM,WAAW,CAAqB;IAEzC;;;;;;OAMG;IACM,WAAW,CAAqB;IAEzC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACM,UAAU,CAAqB;IAExC,YACE,OAAe,EACf,OAAgB,EAChB,MAAe,EACf,WAAoB,EACpB,WAAoB,EACpB,UAAmB,EACnB,OAAyB;QAEzB,MAAM,KAAK,GAAuB;YAChC,4FAA4F;YAC5F,wFAAwF;YACxF,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,wDAAwD,EAAE;YAC/E,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC;SAClC,CAAC;QACF,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3E,IAAI,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACzE,IAAI,WAAW;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QAClE,IAAI,WAAW,EAAE,CAAC;YAChB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,4BAA4B,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;YACtF,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,KAAK;gBACX,IAAI,EACF,kFAAkF;oBAClF,oFAAoF;oBACpF,wBAAwB;aAC3B,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,UAAU,EAAE,CAAC;YACtB,qFAAqF;YACrF,0FAA0F;YAC1F,wFAAwF;YACxF,8FAA8F;YAC9F,0FAA0F;YAC1F,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,KAAK;gBACX,IAAI,EACF,wFAAwF;oBACxF,oFAAoF;oBACpF,mCAAmC;aACtC,CAAC,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;YAChF,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,sFAAsF;YACtF,0FAA0F;YAC1F,iFAAiF;YACjF,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,KAAK;gBACX,IAAI,EACF,sFAAsF;oBACtF,yCAAyC,qBAAqB,CAAC,OAAO,EAAE,IAAI,IAAI,OAAO,CAAC,EAAE;oBAC1F,qEAAqE;aACxE,CAAC,CAAC;QACL,CAAC;QACD,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
* asked for no gate. A false positive in this layer has no recovery at any rung; a miss still has
|
|
20
20
|
* the layers above it. The floor stays narrow and accepts the misses.
|
|
21
21
|
*
|
|
22
|
-
* Two patterns
|
|
23
|
-
* `>`-redirect-to-device arm
|
|
24
|
-
*
|
|
22
|
+
* Two patterns carry no {@link CMD_POS} anchor, because neither is a command-position construct.
|
|
23
|
+
* The `>`-redirect-to-device arm is anchored differently instead — see
|
|
24
|
+
* {@link OUTSIDE_SINGLE_QUOTES} — because a redirection operator appears mid-command by definition,
|
|
25
|
+
* so the discrimination available to it is whether the `>` is QUOTED, not where it sits. The
|
|
26
|
+
* fork-bomb literal is anchored by nothing at all: the string *is* the fork bomb, so prose quoting
|
|
27
|
+
* it is refused, and that false positive is accepted rather than fixed.
|
|
25
28
|
*/
|
|
26
29
|
export declare const HARDLINE_PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
|
|
27
30
|
/**
|
|
@@ -82,12 +85,31 @@ export interface HardlineMatch {
|
|
|
82
85
|
* exfiltration test, which decides per pipeline rather than by one regex.
|
|
83
86
|
*/
|
|
84
87
|
export declare const EXFILTRATION_ARM = "deterministic-exfiltration";
|
|
88
|
+
/** Options for {@link checkHardline}. */
|
|
89
|
+
export interface HardlineOptions {
|
|
90
|
+
/**
|
|
91
|
+
* The host platform to decide the {@link WINDOWS_HARDLINE_PATTERNS} arm against, in
|
|
92
|
+
* `process.platform`'s vocabulary. Defaults to `process.platform`, which is what every
|
|
93
|
+
* production caller wants.
|
|
94
|
+
*
|
|
95
|
+
* **It exists so the Windows arm is testable from any host, and that is not a convenience.**
|
|
96
|
+
* Without it the arm could only ever be exercised on a Windows CI cell: nobody could develop it
|
|
97
|
+
* on this repo's usual machines, and a regression would be invisible on four of the unit
|
|
98
|
+
* matrix's five cells — the majority of the signal, including every local run before a merge.
|
|
99
|
+
* With it, both arms run everywhere, and the Windows cells additionally prove that the real
|
|
100
|
+
* `process.platform` branch selects the arm the injected value stands in for.
|
|
101
|
+
*/
|
|
102
|
+
platform?: NodeJS.Platform;
|
|
103
|
+
}
|
|
85
104
|
/**
|
|
86
105
|
* Check a raw command against the hardline blocklist. Normalizes first so
|
|
87
106
|
* obfuscated variants are caught. Returns the match (with a description) when the
|
|
88
107
|
* command is catastrophic, or `null` when it is allowed to proceed.
|
|
108
|
+
*
|
|
109
|
+
* The shared patterns are tried on every host. The win32 arm is tried in addition on Windows —
|
|
110
|
+
* never instead of, because Git Bash, MSYS and WSL run the POSIX shapes on a Windows box too.
|
|
89
111
|
*/
|
|
90
|
-
export declare function checkHardline(command: string): HardlineMatch | null;
|
|
112
|
+
export declare function checkHardline(command: string, options?: HardlineOptions): HardlineMatch | null;
|
|
91
113
|
/**
|
|
92
114
|
* The refusal a floor match produces, shared by both call sites (§8, §4.2).
|
|
93
115
|
*
|