@gaunt-sloth/core 2.0.0-alpha.24 → 2.0.0-alpha.26

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.
Files changed (109) hide show
  1. package/README.md +1 -1
  2. package/dist/config/colour.d.ts +38 -0
  3. package/dist/config/colour.js +36 -0
  4. package/dist/config/colour.js.map +1 -0
  5. package/dist/config/defaults.d.ts +1 -0
  6. package/dist/config/defaults.js +1 -0
  7. package/dist/config/defaults.js.map +1 -1
  8. package/dist/config/loader.d.ts +13 -6
  9. package/dist/config/loader.js +171 -28
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/mouse.d.ts +50 -0
  12. package/dist/config/mouse.js +44 -0
  13. package/dist/config/mouse.js.map +1 -0
  14. package/dist/config/schema.d.ts +2372 -114
  15. package/dist/config/schema.js +845 -16
  16. package/dist/config/schema.js.map +1 -1
  17. package/dist/config/shell-policy.d.ts +466 -116
  18. package/dist/config/shell-policy.js +273 -62
  19. package/dist/config/shell-policy.js.map +1 -1
  20. package/dist/config/tool-descriptions.d.ts +165 -0
  21. package/dist/config/tool-descriptions.js +229 -0
  22. package/dist/config/tool-descriptions.js.map +1 -0
  23. package/dist/config/types.d.ts +36 -1
  24. package/dist/config/types.js.map +1 -1
  25. package/dist/config.d.ts +3 -0
  26. package/dist/config.js +3 -0
  27. package/dist/config.js.map +1 -1
  28. package/dist/core/GthAbstractAgent.d.ts +59 -1
  29. package/dist/core/GthAbstractAgent.js +72 -0
  30. package/dist/core/GthAbstractAgent.js.map +1 -1
  31. package/dist/core/GthAgentRunner.d.ts +341 -58
  32. package/dist/core/GthAgentRunner.js +817 -149
  33. package/dist/core/GthAgentRunner.js.map +1 -1
  34. package/dist/core/GthLangChainAgent.d.ts +16 -0
  35. package/dist/core/GthLangChainAgent.js +72 -2
  36. package/dist/core/GthLangChainAgent.js.map +1 -1
  37. package/dist/core/approvals/annotations.d.ts +122 -0
  38. package/dist/core/approvals/annotations.js +137 -0
  39. package/dist/core/approvals/annotations.js.map +1 -0
  40. package/dist/core/approvals/grants.d.ts +216 -0
  41. package/dist/core/approvals/grants.js +469 -0
  42. package/dist/core/approvals/grants.js.map +1 -0
  43. package/dist/core/approvals/matcher.d.ts +202 -0
  44. package/dist/core/approvals/matcher.js +267 -0
  45. package/dist/core/approvals/matcher.js.map +1 -0
  46. package/dist/core/approvals/mcpSubjects.d.ts +40 -0
  47. package/dist/core/approvals/mcpSubjects.js +99 -0
  48. package/dist/core/approvals/mcpSubjects.js.map +1 -0
  49. package/dist/core/approvals/toolAnnotationSources.d.ts +105 -0
  50. package/dist/core/approvals/toolAnnotationSources.js +277 -0
  51. package/dist/core/approvals/toolAnnotationSources.js.map +1 -0
  52. package/dist/core/approvals/toolHost.d.ts +46 -0
  53. package/dist/core/approvals/toolHost.js +108 -0
  54. package/dist/core/approvals/toolHost.js.map +1 -0
  55. package/dist/core/launchBanner.d.ts +120 -0
  56. package/dist/core/launchBanner.js +399 -0
  57. package/dist/core/launchBanner.js.map +1 -0
  58. package/dist/core/plainToolIndication.js +13 -7
  59. package/dist/core/plainToolIndication.js.map +1 -1
  60. package/dist/core/shell/approvalStop.d.ts +75 -0
  61. package/dist/core/shell/approvalStop.js +108 -0
  62. package/dist/core/shell/approvalStop.js.map +1 -0
  63. package/dist/core/shell/arity.d.ts +6 -0
  64. package/dist/core/shell/arity.js +20 -6
  65. package/dist/core/shell/arity.js.map +1 -1
  66. package/dist/core/shell/denylist.d.ts +11 -0
  67. package/dist/core/shell/denylist.js +37 -0
  68. package/dist/core/shell/denylist.js.map +1 -0
  69. package/dist/core/shell/normalize.d.ts +41 -1
  70. package/dist/core/shell/normalize.js +58 -4
  71. package/dist/core/shell/normalize.js.map +1 -1
  72. package/dist/core/shell/openWorld.d.ts +138 -0
  73. package/dist/core/shell/openWorld.js +523 -0
  74. package/dist/core/shell/openWorld.js.map +1 -0
  75. package/dist/core/shell/rater.d.ts +560 -0
  76. package/dist/core/shell/rater.js +895 -0
  77. package/dist/core/shell/rater.js.map +1 -0
  78. package/dist/core/shell/raterModel.d.ts +41 -0
  79. package/dist/core/shell/raterModel.js +51 -0
  80. package/dist/core/shell/raterModel.js.map +1 -0
  81. package/dist/core/shell/rejection.d.ts +66 -0
  82. package/dist/core/shell/rejection.js +38 -0
  83. package/dist/core/shell/rejection.js.map +1 -0
  84. package/dist/core/types.d.ts +71 -9
  85. package/dist/core/types.js.map +1 -1
  86. package/dist/runtime/askStructured.d.ts +8 -7
  87. package/dist/runtime/askStructured.js +8 -7
  88. package/dist/runtime/askStructured.js.map +1 -1
  89. package/dist/runtime/conversation.js +89 -76
  90. package/dist/runtime/conversation.js.map +1 -1
  91. package/dist/runtime/singleShot.js +71 -60
  92. package/dist/runtime/singleShot.js.map +1 -1
  93. package/dist/utils/ProgressIndicator.d.ts +21 -0
  94. package/dist/utils/ProgressIndicator.js +30 -3
  95. package/dist/utils/ProgressIndicator.js.map +1 -1
  96. package/dist/utils/consoleUtils.d.ts +30 -0
  97. package/dist/utils/consoleUtils.js +27 -2
  98. package/dist/utils/consoleUtils.js.map +1 -1
  99. package/dist/utils/systemUtils.d.ts +17 -0
  100. package/dist/utils/systemUtils.js +17 -0
  101. package/dist/utils/systemUtils.js.map +1 -1
  102. package/package.json +5 -2
  103. package/schema/gsloth-config.schema.json +1216 -417
  104. package/dist/core/shell/allowlist.d.ts +0 -75
  105. package/dist/core/shell/allowlist.js +0 -187
  106. package/dist/core/shell/allowlist.js.map +0 -1
  107. package/dist/core/shell/judge.d.ts +0 -161
  108. package/dist/core/shell/judge.js +0 -261
  109. package/dist/core/shell/judge.js.map +0 -1
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @module core/shell/approvalStop
3
+ *
4
+ * CFG-27 — the two ways the approvals gate **ends a run** rather than answering a tool call.
5
+ * Both are thrown from `GthAgentRunner.decideToolApproval`, both carry the command and the reason
6
+ * a person needs to see, and both are re-thrown UNCHANGED by `processMessages` (which otherwise
7
+ * wraps failures as `Agent processing failed: …`) so the explanation reaches the user intact.
8
+ *
9
+ * They are errors rather than {@link ../types.js ToolApprovalDecision}s on purpose. A decision is
10
+ * something the model observes as a `ToolMessage` and can respond to; these two are precisely the
11
+ * cases where the spec says the model gets no move at all:
12
+ *
13
+ * - **{@link AttackHaltError}** (§4.2) — an `attack` outcome *halts the run*. "A halt ends the
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.
21
+ *
22
+ * Exit code: neither class sets one. The single-shot runtime (`runSingleShot`) already reports a
23
+ * thrown run as `ok: false`, and each command entry point turns that into `setExitCode(1)` — so
24
+ * "immediate non-zero exit carrying the explanation" is what a caller already gets, without a new
25
+ * exit path that could diverge from the existing one.
26
+ */
27
+ /**
28
+ * Base class for the two run-ending approvals outcomes, so a caller that wants to present them as
29
+ * an ending rather than a crash can catch both with one `instanceof`. Nothing does that today —
30
+ * every surface currently shows the message as-is, which is already the whole explanation — and
31
+ * [[TUI-C26]] is the node that will catch it to render the §6.1 banner.
32
+ */
33
+ export declare abstract class ApprovalStopError extends Error {
34
+ /** The command that ended the run. */
35
+ readonly command: string;
36
+ protected constructor(message: string, command: string);
37
+ }
38
+ /**
39
+ * §4.2 — an `attack` outcome: the command's own **structure** evidenced compromise (§4.1.1 —
40
+ * credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
41
+ * loop; the model is told nothing and offered nothing.
42
+ *
43
+ * [[TUI-C26]] will present this interactively as the §6.1 red banner — any key stops, typing
44
+ * `run anyway` runs this one command — BEFORE the throw; until then the run simply ends with this
45
+ * message, which is what a non-interactive session gets either way (§6.2).
46
+ *
47
+ * The recovery this message names is deliberately the **allow-list**, not `bypass`. §4.2 makes
48
+ * `approvals.allow` the supported way to run such a command unattended (it is consulted before the
49
+ * rater, so it never reaches a halt at all); `bypass` also works and is far blunter — it turns off
50
+ * the rater, the escalation and the halt together, for every command, for the whole run. It is a
51
+ * last resort, not the answer, and the wording says so in that order.
52
+ */
53
+ export declare class AttackHaltError extends ApprovalStopError {
54
+ /** The rater's explanation of what the command's structure showed. */
55
+ readonly reason: string;
56
+ constructor(command: string, reason: string);
57
+ }
58
+ /**
59
+ * §6.2 — an escalation with nobody to ask (CI, one-shot runs, servers). Fails the build loudly,
60
+ * with everything a person needs in order to see why.
61
+ */
62
+ export declare class NonInteractiveEscalationError extends ApprovalStopError {
63
+ /** The rater's outcome, when a rating existed (the unrated rungs have none). */
64
+ readonly outcome: string | undefined;
65
+ /** The rater's explanation, when a rating existed. */
66
+ readonly reason: string | undefined;
67
+ /**
68
+ * EXT-71 §3.2 — the declared `approvals.escalate` entry that sent this call to a human, when one
69
+ * did. It changes the recovery the message names: pointing someone at `approvals.allow` when they
70
+ * themselves wrote an escalate entry sends them to a list that cannot win, since a match on
71
+ * `escalate` outranks a match on `allow`.
72
+ */
73
+ readonly escalatedBy: string | undefined;
74
+ constructor(command: string, outcome?: string, reason?: string, escalatedBy?: string);
75
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * @module core/shell/approvalStop
3
+ *
4
+ * CFG-27 — the two ways the approvals gate **ends a run** rather than answering a tool call.
5
+ * Both are thrown from `GthAgentRunner.decideToolApproval`, both carry the command and the reason
6
+ * a person needs to see, and both are re-thrown UNCHANGED by `processMessages` (which otherwise
7
+ * wraps failures as `Agent processing failed: …`) so the explanation reaches the user intact.
8
+ *
9
+ * They are errors rather than {@link ../types.js ToolApprovalDecision}s on purpose. A decision is
10
+ * something the model observes as a `ToolMessage` and can respond to; these two are precisely the
11
+ * cases where the spec says the model gets no move at all:
12
+ *
13
+ * - **{@link AttackHaltError}** (§4.2) — an `attack` outcome *halts the run*. "A halt ends the
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.
21
+ *
22
+ * Exit code: neither class sets one. The single-shot runtime (`runSingleShot`) already reports a
23
+ * thrown run as `ok: false`, and each command entry point turns that into `setExitCode(1)` — so
24
+ * "immediate non-zero exit carrying the explanation" is what a caller already gets, without a new
25
+ * exit path that could diverge from the existing one.
26
+ */
27
+ /**
28
+ * Base class for the two run-ending approvals outcomes, so a caller that wants to present them as
29
+ * an ending rather than a crash can catch both with one `instanceof`. Nothing does that today —
30
+ * every surface currently shows the message as-is, which is already the whole explanation — and
31
+ * [[TUI-C26]] is the node that will catch it to render the §6.1 banner.
32
+ */
33
+ export class ApprovalStopError extends Error {
34
+ /** The command that ended the run. */
35
+ command;
36
+ constructor(message, command) {
37
+ super(message);
38
+ this.command = command;
39
+ // Restore the prototype chain across the ES5 `extends Error` downlevel, so `instanceof` works.
40
+ Object.setPrototypeOf(this, new.target.prototype);
41
+ this.name = new.target.name;
42
+ }
43
+ }
44
+ /**
45
+ * §4.2 — an `attack` outcome: the command's own **structure** evidenced compromise (§4.1.1 —
46
+ * credential targeting, privilege escalation, persistence, deception, obfuscation). Ends the agent
47
+ * loop; the model is told nothing and offered nothing.
48
+ *
49
+ * [[TUI-C26]] will present this interactively as the §6.1 red banner — any key stops, typing
50
+ * `run anyway` runs this one command — BEFORE the throw; until then the run simply ends with this
51
+ * message, which is what a non-interactive session gets either way (§6.2).
52
+ *
53
+ * The recovery this message names is deliberately the **allow-list**, not `bypass`. §4.2 makes
54
+ * `approvals.allow` the supported way to run such a command unattended (it is consulted before the
55
+ * rater, so it never reaches a halt at all); `bypass` also works and is far blunter — it turns off
56
+ * the rater, the escalation and the halt together, for every command, for the whole run. It is a
57
+ * last resort, not the answer, and the wording says so in that order.
58
+ */
59
+ export class AttackHaltError extends ApprovalStopError {
60
+ /** The rater's explanation of what the command's structure showed. */
61
+ reason;
62
+ constructor(command, reason) {
63
+ super(`Run halted: the auto-rater rated this command as an attack, which ends the run.\n` +
64
+ ` Command: ${command}\n` +
65
+ ` Reason: ${reason}\n` +
66
+ `This is not negotiable. If this command is legitimate and you need it to run, declare ` +
67
+ `it in approvals.allow — that list is consulted before the auto-rater, so it never ` +
68
+ `reaches a halt. Dropping to approvals "bypass" also works, but it turns off the rater, ` +
69
+ `the prompts and the halt for every command in the run.`, command);
70
+ this.reason = reason;
71
+ }
72
+ }
73
+ /**
74
+ * §6.2 — an escalation with nobody to ask (CI, one-shot runs, servers). Fails the build loudly,
75
+ * with everything a person needs in order to see why.
76
+ */
77
+ export class NonInteractiveEscalationError extends ApprovalStopError {
78
+ /** The rater's outcome, when a rating existed (the unrated rungs have none). */
79
+ outcome;
80
+ /** The rater's explanation, when a rating existed. */
81
+ reason;
82
+ /**
83
+ * EXT-71 §3.2 — the declared `approvals.escalate` entry that sent this call to a human, when one
84
+ * did. It changes the recovery the message names: pointing someone at `approvals.allow` when they
85
+ * themselves wrote an escalate entry sends them to a list that cannot win, since a match on
86
+ * `escalate` outranks a match on `allow`.
87
+ */
88
+ escalatedBy;
89
+ constructor(command, outcome, reason, escalatedBy) {
90
+ super(`Approval required, but this session has no one to ask.\n` +
91
+ ` Command: ${command}\n` +
92
+ (outcome ? ` Rating: ${outcome}\n` : '') +
93
+ (reason ? ` Reason: ${reason}\n` : '') +
94
+ (escalatedBy
95
+ ? ` Matched approvals.escalate: ${escalatedBy}\n` +
96
+ `An escalate entry always asks a human, whatever the rung would have done, so no ` +
97
+ `entry in approvals.allow can answer it. Remove the escalate entry if this command ` +
98
+ `should run unattended.`
99
+ : `Declare the commands this run is allowed to execute in approvals.allow — write each ` +
100
+ `one as an explicit entry, for example { "type": "shell", "matcher": "exact", ` +
101
+ `"pattern": "npm test" }. That list is consulted before the auto-rater and never ` +
102
+ `escalates.`), command);
103
+ this.outcome = outcome;
104
+ this.reason = reason;
105
+ this.escalatedBy = escalatedBy;
106
+ }
107
+ }
108
+ //# sourceMappingURL=approvalStop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvalStop.js","sourceRoot":"","sources":["../../../src/core/shell/approvalStop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;GAKG;AACH,MAAM,OAAgB,iBAAkB,SAAQ,KAAK;IACnD,sCAAsC;IAC7B,OAAO,CAAS;IAEzB,YAAsB,OAAe,EAAE,OAAe;QACpD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,+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;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACpD,sEAAsE;IAC7D,MAAM,CAAS;IAExB,YAAY,OAAe,EAAE,MAAc;QACzC,KAAK,CACH,mFAAmF;YACjF,cAAc,OAAO,IAAI;YACzB,aAAa,MAAM,IAAI;YACvB,wFAAwF;YACxF,oFAAoF;YACpF,yFAAyF;YACzF,wDAAwD,EAC1D,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,YAAY,OAAe,EAAE,OAAgB,EAAE,MAAe,EAAE,WAAoB;QAClF,KAAK,CACH,0DAA0D;YACxD,cAAc,OAAO,IAAI;YACzB,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,CAAC,WAAW;gBACV,CAAC,CAAC,iCAAiC,WAAW,IAAI;oBAChD,kFAAkF;oBAClF,oFAAoF;oBACpF,wBAAwB;gBAC1B,CAAC,CAAC,sFAAsF;oBACtF,+EAA+E;oBAC/E,kFAAkF;oBAClF,YAAY,CAAC,EACnB,OAAO,CACR,CAAC;QACF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF"}
@@ -18,6 +18,12 @@
18
18
  * redirections. Such commands NEVER auto-match an allow-list entry — they always go to
19
19
  * fresh human approval. This is what stops `git checkout x; rm -rf /` from matching an
20
20
  * approved `git checkout *`.
21
+ *
22
+ * EXT-55: "newlines" in that list was aspirational until this node. `normalizeCommand` folded
23
+ * a line break to a SPACE, so the check below never saw one and `ls -la\nrm -rf /` classified
24
+ * as the single command `ls`. The separator set now lives in ONE place
25
+ * ({@link import('./normalize.js').COMMAND_SEPARATOR_CLASS}) and a line break survives
26
+ * normalization, so a multi-line command is categorically ambiguous — exactly like `;`.
21
27
  */
22
28
  /**
23
29
  * Result of classifying a command for allow-list matching.
@@ -18,7 +18,14 @@
18
18
  * redirections. Such commands NEVER auto-match an allow-list entry — they always go to
19
19
  * fresh human approval. This is what stops `git checkout x; rm -rf /` from matching an
20
20
  * approved `git checkout *`.
21
+ *
22
+ * EXT-55: "newlines" in that list was aspirational until this node. `normalizeCommand` folded
23
+ * a line break to a SPACE, so the check below never saw one and `ls -la\nrm -rf /` classified
24
+ * as the single command `ls`. The separator set now lives in ONE place
25
+ * ({@link import('./normalize.js').COMMAND_SEPARATOR_CLASS}) and a line break survives
26
+ * normalization, so a multi-line command is categorically ambiguous — exactly like `;`.
21
27
  */
28
+ import { COMMAND_SEPARATOR_RE, LINE_BREAK_RE } from '#src/core/shell/normalize.js';
22
29
  /**
23
30
  * Arity table: command-prefix string → number of leading tokens (binary + subcommands,
24
31
  * flags excluded) that define the "human-understandable command". Longest matching
@@ -181,11 +188,9 @@ const ARITY = {
181
188
  * Note: `&&`/`||`/`|` are covered by the bare `&`/`|` character scan; listed conceptually.
182
189
  */
183
190
  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))
191
+ // Shell control / separator operators, background, AND line breaks one shared definition of
192
+ // "a new command begins here" (EXT-55), so this can never drift from the hardline floor's.
193
+ if (COMMAND_SEPARATOR_RE.test(normalized))
189
194
  return true;
190
195
  // Command substitution: $(...) or `...`.
191
196
  if (/\$\(/.test(normalized))
@@ -235,7 +240,9 @@ export function tokenize(command) {
235
240
  inToken = true;
236
241
  continue;
237
242
  }
238
- if (ch === ' ' || ch === '\t') {
243
+ // EXT-55: line breaks are whitespace here too, so a token can never be glued across a line
244
+ // (`ls\n-o` must tokenize as `ls` + `-o`, or a caller inspecting the argv would miss the flag).
245
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
239
246
  if (inToken) {
240
247
  tokens.push(current);
241
248
  current = '';
@@ -294,6 +301,13 @@ export function meaningfulPrefixTokens(argv) {
294
301
  * @param normalize Normalizer to apply for the detection form (inject normalizeCommand).
295
302
  */
296
303
  export function classifyCommand(command, normalize) {
304
+ // EXT-55 — the boundary question is answered HERE, not delegated to the injected normalizer.
305
+ // `normalize` is a parameter, so this function cannot assume it preserved the command
306
+ // separators (folding them away is exactly the bug this node fixed). A line break anywhere
307
+ // INSIDE the command means more than one command → never classifiable. `.trim()` first: a
308
+ // purely leading/trailing break separates nothing, and models routinely emit `"npm test\n"`.
309
+ if (LINE_BREAK_RE.test(command.trim()))
310
+ return null;
297
311
  const normalized = normalize(command);
298
312
  if (!normalized)
299
313
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"arity.js","sourceRoot":"","sources":["../../../src/core/shell/arity.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"arity.js","sourceRoot":"","sources":["../../../src/core/shell/arity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Split a raw command into the segments a shell would run, normalized and **case preserved**.
3
+ *
4
+ * Substitution bodies are segments in their own right: `echo $(npm publish)` runs `npm publish`,
5
+ * so a deny entry for `npm publish` must see it. Splitting on `$(`, `` ` `` and `)` yields the
6
+ * body as its own segment (and leaves harmless empty fragments, which are dropped).
7
+ *
8
+ * Case is preserved because the matcher folds it per list and per matcher — a `regexp` entry is
9
+ * compiled exactly as the user wrote it, and folding here would decide that for it.
10
+ */
11
+ export declare function commandSegments(command: string): string[];
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @module core/shell/denylist
3
+ *
4
+ * **Command segmentation** — splitting a command into every command a shell would actually run.
5
+ *
6
+ * This is what lets a restrictive rule (`deny`, `escalate`) see inside a compound command. The
7
+ * asymmetry it serves lives in `core/approvals/matcher.ts`, which is the one comparison engine:
8
+ *
9
+ * - **No allow entry matches a command that does not statically resolve.** Composition,
10
+ * substitution and redirection are a non-match for every allow entry, so a grant can never be
11
+ * extended with a `; rm -rf /`.
12
+ * - **A deny or escalate entry MAY match one**, and is compared against every segment as well as
13
+ * the whole string, because a prohibition that catches something unresolvable errs in the
14
+ * direction that costs nothing. Without that, `git push --force; ls` would sail straight past a
15
+ * declared deny entry for `git push --force` — a prohibition any trailing `; ls` defeats is not a
16
+ * prohibition.
17
+ */
18
+ import { COMMAND_SEPARATOR_CLASS, normalizeCommand } from '#src/core/shell/normalize.js';
19
+ /** Splits the normalized command at every point where a shell would begin a NEW command. */
20
+ const SEGMENT_SPLIT_RE = new RegExp(`[${COMMAND_SEPARATOR_CLASS}]|\\$\\(|\\)|\``, 'g');
21
+ /**
22
+ * Split a raw command into the segments a shell would run, normalized and **case preserved**.
23
+ *
24
+ * Substitution bodies are segments in their own right: `echo $(npm publish)` runs `npm publish`,
25
+ * so a deny entry for `npm publish` must see it. Splitting on `$(`, `` ` `` and `)` yields the
26
+ * body as its own segment (and leaves harmless empty fragments, which are dropped).
27
+ *
28
+ * Case is preserved because the matcher folds it per list and per matcher — a `regexp` entry is
29
+ * compiled exactly as the user wrote it, and folding here would decide that for it.
30
+ */
31
+ export function commandSegments(command) {
32
+ return normalizeCommand(command)
33
+ .split(SEGMENT_SPLIT_RE)
34
+ .map((segment) => segment.trim())
35
+ .filter((segment) => segment.length > 0);
36
+ }
37
+ //# sourceMappingURL=denylist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"denylist.js","sourceRoot":"","sources":["../../../src/core/shell/denylist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEzF,4FAA4F;AAC5F,MAAM,gBAAgB,GAAG,IAAI,MAAM,CAAC,IAAI,uBAAuB,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAEvF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,OAAO,gBAAgB,CAAC,OAAO,CAAC;SAC7B,KAAK,CAAC,gBAAgB,CAAC;SACvB,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;SAChC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC7C,CAAC"}
@@ -10,6 +10,34 @@
10
10
  *
11
11
  * Patterned after hermes-agent `tools/approval.py:_normalize_command_for_detection`.
12
12
  */
13
+ /**
14
+ * SECURITY / EXT-55 — the single source of truth for **where a new command begins**.
15
+ *
16
+ * This is a regex character-class BODY (embed it as `` `[${COMMAND_SEPARATOR_CLASS}]` ``) listing
17
+ * every character at which the shell stops one command and starts the next: `;`, `&` (hence
18
+ * `&&`), `|` (hence `||`), and a LINE BREAK. Both consumers of the normalized form build their
19
+ * patterns from it — the allow-list classifier's fail-closed check
20
+ * ({@link import('./arity.js').classifyCommand}) and the agent hardline blocklist's pattern
21
+ * terminators (`@gaunt-sloth/agent` `tools/shell/hardline`) — so the two layers can never again
22
+ * disagree about what a separator is.
23
+ *
24
+ * The layers disagreed before EXT-55: `;`/`&&`/`|` made a command ambiguous (fail-closed) but a
25
+ * newline did not, because {@link normalizeCommand} folded it to a SPACE. `ls -la\nrm -rf /` was
26
+ * therefore classified as the single command `ls`, and an ordinary `ls` grant auto-approved it.
27
+ *
28
+ * `\r` is listed defensively: {@link normalizeCommand} canonicalizes CR/CRLF to LF, so a
29
+ * normalized string never contains one — but a pattern matched against a RAW command still fails
30
+ * closed.
31
+ */
32
+ export declare const COMMAND_SEPARATOR_CLASS = ";&|\\n\\r";
33
+ /** {@link COMMAND_SEPARATOR_CLASS} as a ready-made single-character matcher. */
34
+ export declare const COMMAND_SEPARATOR_RE: RegExp;
35
+ /**
36
+ * Matches any line break — the separator {@link normalizeCommand} used to destroy (EXT-55).
37
+ * Exported so a caller can ask "is this more than one command?" of a RAW string without
38
+ * depending on someone else's normalizer having preserved the boundary.
39
+ */
40
+ export declare const LINE_BREAK_RE: RegExp;
13
41
  /**
14
42
  * Normalize a command string before dangerous-pattern matching.
15
43
  *
@@ -17,9 +45,21 @@
17
45
  * - strip ANSI escape sequences (CSI / OSC / lone-escape),
18
46
  * - drop null bytes,
19
47
  * - Unicode NFKC fold (fullwidth `rm` → `rm`, etc.),
48
+ * - canonicalize every line ending (CRLF / lone CR) to a bare `\n`,
20
49
  * - collapse shell backslash-escapes (`r\m` → `rm`, `\-rf` → `-rf`),
21
50
  * - drop empty-string literals that split tokens (`r''m` / `r""m` → `rm`),
22
- * - fold runs of whitespace (incl. tabs/newlines) to single spaces and trim.
51
+ * - fold runs of HORIZONTAL whitespace (spaces/tabs) to single spaces,
52
+ * - collapse each run of line breaks to a single `\n` — **which survives**, because a line break
53
+ * is a command separator, not padding (EXT-55) — and trim the ends.
54
+ *
55
+ * EXT-55: the last two steps used to be one `\s+ → ' '` fold, which erased the command boundary
56
+ * and let `ls -la\nrm -rf /` be read as the single command `ls`. A line break now reaches every
57
+ * consumer intact, exactly like `;`. Leading/trailing breaks are still trimmed, so the very
58
+ * common `"npm test\n"` tool argument remains ONE command and keeps matching the allow-list.
59
+ *
60
+ * A backslash before a line break (a shell line continuation) is deliberately NOT joined: folding
61
+ * it away would re-open the same hole for `ls \<newline>rm -rf /`. Keeping the boundary makes such
62
+ * a command categorically ambiguous, which costs a prompt, not a failure.
23
63
  *
24
64
  * This is intentionally lossy: the normalized form is ONLY used for detection,
25
65
  * never for execution (the original command is what runs).
@@ -19,6 +19,40 @@ const ANSI_OSC = /\x1b\][\s\S]*?(?:\x07|\x1b\\)/g;
19
19
  const ANSI_LONE = /\x1b[@-Z\\-_]?/g;
20
20
  // Null bytes.
21
21
  const NULL_BYTES = /\x00/g;
22
+ // Any line ending — CRLF, a lone CR, or a lone LF. Canonicalized to `\n` by normalizeCommand.
23
+ const LINE_ENDINGS = /\r\n?/g;
24
+ // A run of HORIZONTAL whitespace: whitespace that is not a line break (spaces, tabs, NBSP, …).
25
+ const HORIZONTAL_WS = /[^\S\n]+/g;
26
+ // A run of line breaks together with any whitespace around it.
27
+ const LINE_BREAK_RUN = /\s*\n\s*/g;
28
+ /**
29
+ * SECURITY / EXT-55 — the single source of truth for **where a new command begins**.
30
+ *
31
+ * This is a regex character-class BODY (embed it as `` `[${COMMAND_SEPARATOR_CLASS}]` ``) listing
32
+ * every character at which the shell stops one command and starts the next: `;`, `&` (hence
33
+ * `&&`), `|` (hence `||`), and a LINE BREAK. Both consumers of the normalized form build their
34
+ * patterns from it — the allow-list classifier's fail-closed check
35
+ * ({@link import('./arity.js').classifyCommand}) and the agent hardline blocklist's pattern
36
+ * terminators (`@gaunt-sloth/agent` `tools/shell/hardline`) — so the two layers can never again
37
+ * disagree about what a separator is.
38
+ *
39
+ * The layers disagreed before EXT-55: `;`/`&&`/`|` made a command ambiguous (fail-closed) but a
40
+ * newline did not, because {@link normalizeCommand} folded it to a SPACE. `ls -la\nrm -rf /` was
41
+ * therefore classified as the single command `ls`, and an ordinary `ls` grant auto-approved it.
42
+ *
43
+ * `\r` is listed defensively: {@link normalizeCommand} canonicalizes CR/CRLF to LF, so a
44
+ * normalized string never contains one — but a pattern matched against a RAW command still fails
45
+ * closed.
46
+ */
47
+ export const COMMAND_SEPARATOR_CLASS = ';&|\\n\\r';
48
+ /** {@link COMMAND_SEPARATOR_CLASS} as a ready-made single-character matcher. */
49
+ export const COMMAND_SEPARATOR_RE = new RegExp(`[${COMMAND_SEPARATOR_CLASS}]`);
50
+ /**
51
+ * Matches any line break — the separator {@link normalizeCommand} used to destroy (EXT-55).
52
+ * Exported so a caller can ask "is this more than one command?" of a RAW string without
53
+ * depending on someone else's normalizer having preserved the boundary.
54
+ */
55
+ export const LINE_BREAK_RE = /[\n\r]/;
22
56
  /**
23
57
  * Normalize a command string before dangerous-pattern matching.
24
58
  *
@@ -26,9 +60,21 @@ const NULL_BYTES = /\x00/g;
26
60
  * - strip ANSI escape sequences (CSI / OSC / lone-escape),
27
61
  * - drop null bytes,
28
62
  * - Unicode NFKC fold (fullwidth `rm` → `rm`, etc.),
63
+ * - canonicalize every line ending (CRLF / lone CR) to a bare `\n`,
29
64
  * - collapse shell backslash-escapes (`r\m` → `rm`, `\-rf` → `-rf`),
30
65
  * - drop empty-string literals that split tokens (`r''m` / `r""m` → `rm`),
31
- * - fold runs of whitespace (incl. tabs/newlines) to single spaces and trim.
66
+ * - fold runs of HORIZONTAL whitespace (spaces/tabs) to single spaces,
67
+ * - collapse each run of line breaks to a single `\n` — **which survives**, because a line break
68
+ * is a command separator, not padding (EXT-55) — and trim the ends.
69
+ *
70
+ * EXT-55: the last two steps used to be one `\s+ → ' '` fold, which erased the command boundary
71
+ * and let `ls -la\nrm -rf /` be read as the single command `ls`. A line break now reaches every
72
+ * consumer intact, exactly like `;`. Leading/trailing breaks are still trimmed, so the very
73
+ * common `"npm test\n"` tool argument remains ONE command and keeps matching the allow-list.
74
+ *
75
+ * A backslash before a line break (a shell line continuation) is deliberately NOT joined: folding
76
+ * it away would re-open the same hole for `ls \<newline>rm -rf /`. Keeping the boundary makes such
77
+ * a command categorically ambiguous, which costs a prompt, not a failure.
32
78
  *
33
79
  * This is intentionally lossy: the normalized form is ONLY used for detection,
34
80
  * never for execution (the original command is what runs).
@@ -41,13 +87,21 @@ export function normalizeCommand(command) {
41
87
  c = c.replace(NULL_BYTES, '');
42
88
  // Unicode compatibility fold (fullwidth → ASCII, etc.).
43
89
  c = c.normalize('NFKC');
90
+ // EXT-55: canonicalize line endings FIRST so CR and CRLF are the same separator as LF for every
91
+ // step below (and for every consumer of the normalized form).
92
+ c = c.replace(LINE_ENDINGS, '\n');
44
93
  // Collapse backslash-escapes: `\x` → `x` (prevents `r\m -rf /` bypass).
45
94
  // Applied before empty-string stripping so `r\m` and `r''m` both fold.
95
+ // `[^\n]` keeps a backslash line-continuation intact — see the docblock.
46
96
  c = c.replace(/\\([^\n])/g, '$1');
47
97
  // Drop empty-string literals used to split a token: `r''m` / `r""m` → `rm`.
48
98
  c = c.replace(/''|""/g, '');
49
- // Fold all whitespace runs (spaces, tabs, newlines) to a single space, trim.
50
- c = c.replace(/\s+/g, ' ').trim();
51
- return c;
99
+ // Fold runs of horizontal whitespace to a single space
100
+ c = c.replace(HORIZONTAL_WS, ' ');
101
+ // …then collapse each run of line breaks (with the whitespace around it) to a single `\n`,
102
+ // which SURVIVES normalization as the command separator it is (EXT-55).
103
+ c = c.replace(LINE_BREAK_RUN, '\n');
104
+ // Trim the ends — including a leading/trailing break, which separates nothing.
105
+ return c.trim();
52
106
  }
53
107
  //# sourceMappingURL=normalize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/core/shell/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uEAAuE;AACvE,yCAAyC;AACzC,MAAM,QAAQ,GAAG,0BAA0B,CAAC;AAC5C,kDAAkD;AAClD,MAAM,QAAQ,GAAG,gCAAgC,CAAC;AAClD,8DAA8D;AAC9D,MAAM,SAAS,GAAG,iBAAiB,CAAC;AACpC,cAAc;AACd,MAAM,UAAU,GAAG,OAAO,CAAC;AAE3B;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,IAAI,CAAC,GAAG,OAAO,CAAC;IAChB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC9B,wDAAwD;IACxD,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACxB,wEAAwE;IACxE,uEAAuE;IACvE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAClC,4EAA4E;IAC5E,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5B,6EAA6E;IAC7E,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,OAAO,CAAC,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/core/shell/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uEAAuE;AACvE,yCAAyC;AACzC,MAAM,QAAQ,GAAG,0BAA0B,CAAC;AAC5C,kDAAkD;AAClD,MAAM,QAAQ,GAAG,gCAAgC,CAAC;AAClD,8DAA8D;AAC9D,MAAM,SAAS,GAAG,iBAAiB,CAAC;AACpC,cAAc;AACd,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3B,8FAA8F;AAC9F,MAAM,YAAY,GAAG,QAAQ,CAAC;AAC9B,+FAA+F;AAC/F,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,+DAA+D;AAC/D,MAAM,cAAc,GAAG,WAAW,CAAC;AAEnC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,WAAW,CAAC;AAEnD,gFAAgF;AAChF,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,MAAM,CAAC,IAAI,uBAAuB,GAAG,CAAC,CAAC;AAE/E;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC;AAEtC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,IAAI,CAAC,GAAG,OAAO,CAAC;IAChB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC7B,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC9B,wDAAwD;IACxD,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACxB,gGAAgG;IAChG,8DAA8D;IAC9D,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAClC,wEAAwE;IACxE,uEAAuE;IACvE,yEAAyE;IACzE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAClC,4EAA4E;IAC5E,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC5B,wDAAwD;IACxD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAClC,2FAA2F;IAC3F,wEAAwE;IACxE,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IACpC,+EAA+E;IAC/E,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAClB,CAAC"}