@sema-agent/core 7.17.7 → 7.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +233 -13
- package/dist/agents/send-message-tool.js +1 -0
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +5 -0
- package/dist/agents/teacher.js +1 -0
- package/dist/agents/verify.js +3 -0
- package/dist/brain/anthropic.js +6 -3
- package/dist/brain/open-responses.js +10 -4
- package/dist/brain/openai.js +5 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/route-adjudicator.d.ts +18 -0
- package/dist/brain/route-adjudicator.js +16 -0
- package/dist/brain/stream-engine.js +2 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- package/dist/core/agent-definition.d.ts +10 -0
- package/dist/core/artifact-host.d.ts +41 -4
- package/dist/core/artifact-host.js +18 -2
- package/dist/core/ask-origin.d.ts +16 -0
- package/dist/core/ask-origin.js +8 -0
- package/dist/core/auto-mode-arming.d.ts +6 -0
- package/dist/core/auto-mode-arming.js +5 -5
- package/dist/core/auto-mode-defaults.d.ts +43 -15
- package/dist/core/auto-mode-defaults.js +4 -1
- package/dist/core/auto-mode.d.ts +184 -59
- package/dist/core/auto-mode.js +188 -13
- package/dist/core/checkpoint-store.d.ts +23 -2
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/engine-notice.d.ts +77 -1
- package/dist/core/engine-notice.js +7 -0
- package/dist/core/gate-fold.js +5 -1
- package/dist/core/gate-lanes.js +9 -8
- package/dist/core/gate-outcome.d.ts +42 -2
- package/dist/core/gate-outcome.js +23 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hook-seats.d.ts +66 -0
- package/dist/core/hook-seats.js +37 -0
- package/dist/core/hooks.d.ts +1 -1
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +8 -3
- package/dist/core/runner/assemble-result.d.ts +2 -1
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/classifier-ladder.d.ts +227 -0
- package/dist/core/runner/classifier-ladder.js +214 -0
- package/dist/core/runner/contracts.d.ts +71 -13
- package/dist/core/runner/decide-continuation.d.ts +1 -3
- package/dist/core/runner/decide-continuation.js +1 -7
- package/dist/core/runner/deferral-census.d.ts +52 -0
- package/dist/core/runner/deferral-census.js +44 -0
- package/dist/core/runner/denial-limit-arms.js +2 -2
- package/dist/core/runner/derived-route-fallback.d.ts +8 -4
- package/dist/core/runner/derived-route-fallback.js +8 -3
- package/dist/core/runner/gate-exit.d.ts +12 -0
- package/dist/core/runner/gate-exit.js +6 -3
- package/dist/core/runner/prepare-artifact.d.ts +10 -2
- package/dist/core/runner/prepare-artifact.js +5 -4
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +43 -55
- package/dist/core/runner/prepare-config-doors.d.ts +7 -1
- package/dist/core/runner/prepare-config-doors.js +13 -13
- package/dist/core/runner/prepare-defer-classify.d.ts +13 -19
- package/dist/core/runner/prepare-defer-classify.js +8 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +26 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +24 -7
- package/dist/core/runner/prepare-hands-readface.js +60 -24
- package/dist/core/runner/prepare-inherited-gate.d.ts +17 -4
- package/dist/core/runner/prepare-inherited-gate.js +10 -2
- package/dist/core/runner/prepare-lsp.d.ts +2 -2
- package/dist/core/runner/prepare-lsp.js +1 -1
- package/dist/core/runner/prepare-policy-chain.js +2 -0
- package/dist/core/runner/prepare-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +7 -4
- package/dist/core/runner/prepare-prompt-inputs.d.ts +4 -2
- package/dist/core/runner/prepare-prompt-inputs.js +4 -1
- package/dist/core/runner/prepare-run-refs.d.ts +9 -1
- package/dist/core/runner/prepare-run-refs.js +3 -2
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -0
- package/dist/core/runner/prepare-suspend-saga.js +5 -2
- package/dist/core/runner/prepare-task.js +19 -14
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +10 -2
- package/dist/core/runner/prepare-turn-wiring.d.ts +8 -4
- package/dist/core/runner/prepare-turn-wiring.js +3 -2
- package/dist/core/runner/prepare-wiring-manifest.d.ts +5 -5
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/resume-apply.js +3 -3
- package/dist/core/runner/resume-checkpoint-screen.js +7 -0
- package/dist/core/runner/run-harness-handlers.js +4 -1
- package/dist/core/runner/run-identity-wiring.js +6 -2
- package/dist/core/runner/run-leg.d.ts +2 -0
- package/dist/core/runner/run-leg.js +45 -45
- package/dist/core/runner/run-notification-lane.js +55 -27
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +16 -13
- package/dist/core/runner/run-turn-boundary.js +15 -5
- package/dist/core/runner/runtask.d.ts +6 -5
- package/dist/core/runner/runtask.js +26 -14
- package/dist/core/runner/skill-base-dir.d.ts +54 -0
- package/dist/core/runner/skill-base-dir.js +42 -0
- package/dist/core/runner/synthetic-tools.d.ts +83 -48
- package/dist/core/runner/synthetic-tools.js +69 -32
- package/dist/core/runner/tool-defer-gate.d.ts +22 -9
- package/dist/core/runner/tool-defer-gate.js +15 -5
- package/dist/core/runner/tool-disclosure.d.ts +5 -0
- package/dist/core/runner/tool-disclosure.js +2 -0
- package/dist/core/runner/tool-progress-lane.d.ts +42 -0
- package/dist/core/runner/tool-progress-lane.js +61 -0
- package/dist/core/runner/turn-attachments.d.ts +43 -12
- package/dist/core/runner/turn-attachments.js +58 -14
- package/dist/core/runner-deps.d.ts +48 -7
- package/dist/core/sensitive-path-policy.d.ts +58 -17
- package/dist/core/sensitive-path-policy.js +33 -5
- package/dist/core/skill-listing-budget.d.ts +112 -0
- package/dist/core/skill-listing-budget.js +58 -0
- package/dist/core/swappable-deps.d.ts +9 -7
- package/dist/core/swappable-deps.js +7 -3
- package/dist/core/task-event.d.ts +96 -29
- package/dist/core/task-notification.d.ts +142 -16
- package/dist/core/task-notification.js +106 -0
- package/dist/core/task-result.d.ts +9 -1
- package/dist/core/task-spec.d.ts +46 -3
- package/dist/core/tighten-task-spec.js +7 -5
- package/dist/core/tool-model-gate.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +19 -4
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-search-setting.d.ts +78 -0
- package/dist/core/tool-search-setting.js +90 -0
- package/dist/core/tool-spec.d.ts +45 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/wiring-manifest.d.ts +46 -0
- package/dist/core/wiring-manifest.js +9 -1
- package/dist/core/write-protect.d.ts +8 -3
- package/dist/core/write-protect.js +8 -5
- package/dist/engine/execution-env/node-execution-env.js +10 -4
- package/dist/engine/harness/agent-harness.d.ts +42 -0
- package/dist/engine/harness/agent-harness.js +61 -29
- package/dist/engine/loop/agent-loop.d.ts +68 -3
- package/dist/engine/loop/agent-loop.js +109 -16
- package/dist/index.d.ts +13 -8
- package/dist/index.js +11 -7
- package/dist/internal/harness.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +3 -0
- package/dist/orchestration/run-workflow-tool.js +6 -1
- package/dist/orchestration/workflow-governance.d.ts +7 -1
- package/dist/orchestration/workflow-governance.js +9 -5
- package/dist/orchestration/workflow-primitives.d.ts +3 -0
- package/dist/orchestration/workflow-primitives.js +6 -1
- package/dist/orchestration/workflow-types.d.ts +48 -13
- package/dist/orchestration/workflow-types.js +1 -1
- package/dist/orchestration/workflow.js +45 -17
- package/dist/prompt-assembly/epoch.js +2 -0
- package/dist/prompt-assembly/packs/sema-default.js +2 -2
- package/dist/prompt-assembly/section-usage.d.ts +26 -0
- package/dist/prompt-assembly/section-usage.js +10 -0
- package/dist/prompt-assembly/types.d.ts +8 -6
- package/dist/prompts/default.d.ts +13 -0
- package/dist/prompts/default.js +16 -8
- package/dist/tools/artifact/artifact-text.d.ts +10 -1
- package/dist/tools/artifact/artifact-text.js +6 -3
- package/dist/tools/artifact/artifact-tool.d.ts +12 -2
- package/dist/tools/artifact/artifact-tool.js +203 -70
- package/dist/tools/artifact/conformance.d.ts +56 -0
- package/dist/tools/artifact/conformance.js +349 -0
- package/dist/tools/artifact/local-stub.js +1 -1
- package/dist/tools/artifact/reject-breaker.d.ts +28 -0
- package/dist/tools/artifact/reject-breaker.js +42 -0
- package/dist/tools/fs/bash-lexer.d.ts +132 -0
- package/dist/tools/fs/bash-lexer.js +563 -0
- package/dist/tools/fs/bash-program-position.d.ts +142 -0
- package/dist/tools/fs/bash-program-position.js +125 -0
- package/dist/tools/fs/bash-readonly-classifier.d.ts +81 -16
- package/dist/tools/fs/bash-readonly-classifier.js +180 -558
- package/dist/tools/fs/containment-face.d.ts +89 -0
- package/dist/tools/fs/containment-face.js +81 -0
- package/dist/tools/fs/fs-bash.d.ts +46 -1
- package/dist/tools/fs/fs-bash.js +165 -25
- package/dist/tools/fs/fs-read.d.ts +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +3 -3
- package/dist/tools/fs/fs-write.d.ts +4 -3
- package/dist/tools/fs/fs-write.js +9 -9
- package/dist/tools/fs/index.d.ts +24 -5
- package/dist/tools/fs/index.js +18 -6
- package/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/repo-map.d.ts +2 -2
- package/dist/tools/fs/safety.d.ts +18 -3
- package/dist/tools/fs/safety.js +11 -4
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +99 -13
- package/wire-changes/core@7.17.7.json +49 -0
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/dist/tools/fs/read-face.d.ts +0 -68
- package/dist/tools/fs/read-face.js +0 -47
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE reader of a segment's PROGRAM POSITION: which argv word names the program bash runs, and what stands in
|
|
3
|
+
* front of it — bare control words with their declared options, environment assignments, a launcher chain.
|
|
4
|
+
*
|
|
5
|
+
* Every face that judges a command by its program (the compound walk, the single-command boundary face, the
|
|
6
|
+
* refused-command whitelist, the poll loop's base-move check) reads this ONE function. A face that needs the
|
|
7
|
+
* answer it cannot give reads {@link ProgramPosition.refusal}: a declarative record of why the program is not
|
|
8
|
+
* readable, minted into one sentence by {@link programPositionRefusalSentence}. Reading less than this reader
|
|
9
|
+
* does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
|
|
10
|
+
*/
|
|
11
|
+
import type { SegmentTokens } from "./bash-lexer.js";
|
|
12
|
+
/** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
|
|
13
|
+
* they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
|
|
14
|
+
export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
|
|
15
|
+
/** argv[0] as the program bash will resolve it: `/usr/bin/cat` and `cat` are one name. */
|
|
16
|
+
export declare function programBasename(word: string): string;
|
|
17
|
+
/** A leading `NAME=value` word: an assignment bash applies to the command's environment rather than a
|
|
18
|
+
* program name. Same shape the exit-code reader in `fs-bash.ts` skips for the same reason. */
|
|
19
|
+
export declare const ASSIGNMENT_WORD: RegExp;
|
|
20
|
+
/** Shell control words that stand BEFORE a command: transparent for the boundary walk (the command behind them is
|
|
21
|
+
* judged). */
|
|
22
|
+
export declare const SHELL_CONTROL_PREFIXES: ReadonlySet<string>;
|
|
23
|
+
/** Shell block words that name no command of their own (a loop head, a block close, a case head). */
|
|
24
|
+
export declare const SHELL_BLOCK_WORDS: ReadonlySet<string>;
|
|
25
|
+
/** A control word is a keyword only when bash sees it BARE: `'if'` / `"do"` / `\\!` are command names (a program
|
|
26
|
+
* looked up on PATH, whose failure changes nothing), so a quoted or escaped spelling never counts as one. */
|
|
27
|
+
export declare function isBareControlWord(toks: SegmentTokens, i: number, words: ReadonlySet<string>): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Programs that turn their STANDARD INPUT into the OPERANDS of another program, and EXEC that program directly.
|
|
30
|
+
*
|
|
31
|
+
* The same class as the shell re-entry programs, one level down: there the unseen text is COMMANDS; here the
|
|
32
|
+
* command's name IS visible and its argv is not — `echo <path> | xargs cat` opens a file no token of this walk
|
|
33
|
+
* ever named, because the operand arrives on a stream. The membership criterion is that behaviour and only that
|
|
34
|
+
* behaviour; a program that consumes stdin as DATA (`sort`, `wc`, a reader the deployment never listed) is NOT a
|
|
35
|
+
* member, because its stdin never becomes a filename. Spelled as a pattern — these are program names, never tool
|
|
36
|
+
* names.
|
|
37
|
+
*
|
|
38
|
+
* "EXECS DIRECTLY" is what separates this class from the shell re-entry class, and it is why GNU `parallel` is
|
|
39
|
+
* NOT a member: parallel BUILDS A COMMAND LINE and hands it to a shell, and with no command word at all it runs
|
|
40
|
+
* each input line as a command — so parallel belongs to the shell re-entry class and is refused whole with no
|
|
41
|
+
* exemption at all. A spelling this file has no name for (an aliased or wrapped `xargs`) is judged as any other
|
|
42
|
+
* unlisted program is — the name-table floor the re-entry class records for itself.
|
|
43
|
+
*/
|
|
44
|
+
export declare const ARGV_REENTRY_PROGRAM_RE: RegExp;
|
|
45
|
+
/** The program an {@link ARGV_REENTRY_PROGRAM_RE} launcher runs when the chain names none: `xargs` defaults to
|
|
46
|
+
* `echo`, which is why a bare one reads nothing. */
|
|
47
|
+
export declare const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
|
|
48
|
+
/**
|
|
49
|
+
* What a launcher declares about its OWN options: each exact spelling with its ARITY — `0` for a switch, `1` for an
|
|
50
|
+
* option whose value is the next token. A fused `--name=value` and every spelling not in the map fall to the refusal
|
|
51
|
+
* (the conservative side, and the verdict those spellings always had); a `1` with no token left to take is not
|
|
52
|
+
* readable either.
|
|
53
|
+
*/
|
|
54
|
+
export interface LauncherRow {
|
|
55
|
+
/** Consulted ONLY for the launcher the command line itself names — a bare keyword in front of the command
|
|
56
|
+
* (`time -p …`) or the written command word (`"time" -p …`, `X=1 time -p …`, `/usr/bin/time -p …`). A launcher
|
|
57
|
+
* nested behind another (`env time -p …`) is that launcher's operand, and its options refuse: resolving past
|
|
58
|
+
* them would turn a whole refusal into an ordinary walked segment — a widening, where every other read of this
|
|
59
|
+
* table only ever finds MORE program to judge. */
|
|
60
|
+
readonly optionArity: ReadonlyMap<string, 0 | 1>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The ONE launcher truth: programs that RUN ANOTHER PROGRAM named in ordinary argv tokens, so a declared reader can
|
|
64
|
+
* sit behind one (`env cat <path>`, `sudo cat <path>`, `timeout 5 cat <path>`), each with the option grammar this
|
|
65
|
+
* file reads for it.
|
|
66
|
+
*
|
|
67
|
+
* Membership is the key set. A row with no declared options means every option-shaped word after that launcher
|
|
68
|
+
* refuses ("which program runs is not readable without the launcher's option table") — the answer every launcher
|
|
69
|
+
* but `time` has always had, now stated per row rather than by omission. Declaring a row's options is a behaviour
|
|
70
|
+
* decision with its own criteria; adding a launcher is a row.
|
|
71
|
+
*
|
|
72
|
+
* `time` carries both spellings a deployment can meet: the shell builtin takes `-p` alone, and the standalone
|
|
73
|
+
* program adds a format, an output file, and three switches.
|
|
74
|
+
*
|
|
75
|
+
* A deliberate SUBSET of the rule lane's own `BARE_INTERPRETER_NAMES`, mirrored rather than imported because that
|
|
76
|
+
* module imports the classifier — the dependency runs that way, and a cycle is a worse problem than a mirror whose
|
|
77
|
+
* subset relation is pinned by test. GNU `parallel` is absent for the reason the shells are absent: it does not run
|
|
78
|
+
* a program named in token-visible argv — it BUILDS a command line and hands it to a shell — so it is a shell
|
|
79
|
+
* re-entry member instead. Language interpreters and shells are excluded as a scope statement: their program
|
|
80
|
+
* argument is a script path or a quoted code string, not a token-visible program with token-visible operands.
|
|
81
|
+
*/
|
|
82
|
+
export declare const LAUNCHER_TABLE: ReadonlyMap<string, LauncherRow>;
|
|
83
|
+
/** The launcher names — derived from the table, never kept beside it. */
|
|
84
|
+
export declare const COMMAND_LAUNCHERS: ReadonlySet<string>;
|
|
85
|
+
export declare const PROGRAM_POSITION_REFUSAL_KINDS: readonly ["option_at_program_position", "control_syntax_at_program_position"];
|
|
86
|
+
export type ProgramPositionRefusalKind = (typeof PROGRAM_POSITION_REFUSAL_KINDS)[number];
|
|
87
|
+
/** Why the program could not be read — a closed set, each member minted into one sentence. `owner` is the word
|
|
88
|
+
* that took the offending token: the control word or the first launcher of the chain. */
|
|
89
|
+
export type ProgramPositionRefusal = {
|
|
90
|
+
readonly kind: "option_at_program_position";
|
|
91
|
+
readonly owner: string;
|
|
92
|
+
readonly option: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly kind: "control_syntax_at_program_position";
|
|
95
|
+
readonly owner: string;
|
|
96
|
+
readonly word: string;
|
|
97
|
+
};
|
|
98
|
+
export interface ProgramPosition {
|
|
99
|
+
/** Indices of the bare control words stripped in front (`!`, `if`, `time`, …), in order; their declared options
|
|
100
|
+
* are skipped with them. */
|
|
101
|
+
readonly controlWords: readonly number[];
|
|
102
|
+
/** The first index past the control words and their options — the WRITTEN command word: an assignment, a
|
|
103
|
+
* launcher, or the program itself. `undefined` when nothing stands there. */
|
|
104
|
+
readonly written: number | undefined;
|
|
105
|
+
/** The leading `NAME=value` words at `written`. */
|
|
106
|
+
readonly assignments: readonly number[];
|
|
107
|
+
/** The launcher chain past the assignments, in order (basenames); empty when the written word is not a launcher. */
|
|
108
|
+
readonly launchers: readonly {
|
|
109
|
+
readonly index: number;
|
|
110
|
+
readonly name: string;
|
|
111
|
+
}[];
|
|
112
|
+
/** Some chain member is an argv re-entry program: the program's operands arrive on a stream. */
|
|
113
|
+
readonly argvFromStdin: boolean;
|
|
114
|
+
/** The chain ends ON the re-entry program (nothing follows it), so it runs its default program. */
|
|
115
|
+
readonly runsDefaultProgram: boolean;
|
|
116
|
+
/** The program bash RUNS: the first word past the chain. `name` is the folded word as written, `basename` what
|
|
117
|
+
* bash resolves (`/usr/bin/cat` is `cat`). `undefined` when the chain names none or the read was refused. */
|
|
118
|
+
readonly program: {
|
|
119
|
+
readonly index: number;
|
|
120
|
+
readonly name: string;
|
|
121
|
+
readonly basename: string;
|
|
122
|
+
} | undefined;
|
|
123
|
+
/** Why no program could be read; `undefined` beside `program: undefined` means the chain simply names none. */
|
|
124
|
+
readonly refusal: ProgramPositionRefusal | undefined;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Read the program position of one segment's argv words.
|
|
128
|
+
*
|
|
129
|
+
* In order: bare control words and the options their launcher rows declare; the written word; its leading
|
|
130
|
+
* assignments; then either the program itself or a launcher chain — whose first member's declared options are read
|
|
131
|
+
* too (it is the launcher the line names), and inside which an assignment, a bare number, a nested launcher and an
|
|
132
|
+
* argv re-entry program are stepped over, an option-shaped word (a lone `-` included — `env - <program>` clears
|
|
133
|
+
* the environment and the table does not say so) refuses, and shell control syntax refuses. An option-shaped
|
|
134
|
+
* WRITTEN word (judged on its basename, as the program it names) refuses the same way: it is where the program
|
|
135
|
+
* would be, and the word in front took an option no row explains.
|
|
136
|
+
*/
|
|
137
|
+
export declare function readProgramPosition(toks: SegmentTokens): ProgramPosition;
|
|
138
|
+
/** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
|
|
139
|
+
* stdin AND the program it runs is not declared no-path. ONE reading, shared by every consumer. */
|
|
140
|
+
export declare function argvReentryUnjudged(pos: ProgramPosition, noPathPrograms: ReadonlySet<string>): boolean;
|
|
141
|
+
/** ONE sentence per refusal kind, minted here and nowhere else. */
|
|
142
|
+
export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export const NOT_AUTO_ALLOWED = "— not auto-allowed";
|
|
2
|
+
export function programBasename(word) {
|
|
3
|
+
return word.includes("/") ? (word.split("/").pop() ?? word) : word;
|
|
4
|
+
}
|
|
5
|
+
export const ASSIGNMENT_WORD = /^[A-Za-z_][A-Za-z0-9_]*=/;
|
|
6
|
+
export const SHELL_CONTROL_PREFIXES = new Set(["!", "if", "then", "elif", "else", "while", "until", "do", "time"]);
|
|
7
|
+
export const SHELL_BLOCK_WORDS = new Set(["for", "select", "case", "esac", "fi", "done", "function", "coproc", "[[", "]]", "in"]);
|
|
8
|
+
export function isBareControlWord(toks, i, words) {
|
|
9
|
+
const word = toks.folded[i];
|
|
10
|
+
return word !== undefined && toks.raw[i] === word && words.has(word);
|
|
11
|
+
}
|
|
12
|
+
export const ARGV_REENTRY_PROGRAM_RE = /^xargs$/;
|
|
13
|
+
export const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
|
|
14
|
+
const NO_DECLARED_OPTIONS = { optionArity: new Map() };
|
|
15
|
+
export const LAUNCHER_TABLE = new Map([
|
|
16
|
+
["env", NO_DECLARED_OPTIONS], ["command", NO_DECLARED_OPTIONS], ["builtin", NO_DECLARED_OPTIONS], ["exec", NO_DECLARED_OPTIONS],
|
|
17
|
+
["noglob", NO_DECLARED_OPTIONS], ["xargs", NO_DECLARED_OPTIONS], ["busybox", NO_DECLARED_OPTIONS],
|
|
18
|
+
["sudo", NO_DECLARED_OPTIONS], ["doas", NO_DECLARED_OPTIONS], ["pkexec", NO_DECLARED_OPTIONS], ["su", NO_DECLARED_OPTIONS],
|
|
19
|
+
["runuser", NO_DECLARED_OPTIONS], ["chroot", NO_DECLARED_OPTIONS], ["setpriv", NO_DECLARED_OPTIONS],
|
|
20
|
+
["nohup", NO_DECLARED_OPTIONS], ["nice", NO_DECLARED_OPTIONS], ["ionice", NO_DECLARED_OPTIONS], ["chrt", NO_DECLARED_OPTIONS],
|
|
21
|
+
["taskset", NO_DECLARED_OPTIONS], ["stdbuf", NO_DECLARED_OPTIONS], ["timeout", NO_DECLARED_OPTIONS],
|
|
22
|
+
["time", {
|
|
23
|
+
optionArity: new Map([
|
|
24
|
+
["-p", 0], ["-a", 0], ["-v", 0], ["-q", 0], ["--portability", 0], ["--append", 0], ["--verbose", 0], ["--quiet", 0], ["--help", 0], ["--version", 0],
|
|
25
|
+
["-f", 1], ["-o", 1], ["--format", 1], ["--output", 1],
|
|
26
|
+
]),
|
|
27
|
+
}],
|
|
28
|
+
["watch", NO_DECLARED_OPTIONS], ["setsid", NO_DECLARED_OPTIONS], ["flock", NO_DECLARED_OPTIONS], ["unshare", NO_DECLARED_OPTIONS],
|
|
29
|
+
["nsenter", NO_DECLARED_OPTIONS], ["script", NO_DECLARED_OPTIONS], ["numactl", NO_DECLARED_OPTIONS], ["prlimit", NO_DECLARED_OPTIONS],
|
|
30
|
+
["systemd-run", NO_DECLARED_OPTIONS],
|
|
31
|
+
["strace", NO_DECLARED_OPTIONS], ["ltrace", NO_DECLARED_OPTIONS], ["valgrind", NO_DECLARED_OPTIONS], ["firejail", NO_DECLARED_OPTIONS],
|
|
32
|
+
["bwrap", NO_DECLARED_OPTIONS],
|
|
33
|
+
]);
|
|
34
|
+
export const COMMAND_LAUNCHERS = new Set(LAUNCHER_TABLE.keys());
|
|
35
|
+
function pastLauncherOptions(launcher, folded, i) {
|
|
36
|
+
const row = LAUNCHER_TABLE.get(launcher);
|
|
37
|
+
if (row === undefined)
|
|
38
|
+
return i;
|
|
39
|
+
for (;;) {
|
|
40
|
+
const tok = folded[i];
|
|
41
|
+
const arity = tok === undefined ? undefined : row.optionArity.get(tok);
|
|
42
|
+
if (arity === undefined)
|
|
43
|
+
return i;
|
|
44
|
+
if (arity === 1 && folded[i + 1] === undefined)
|
|
45
|
+
return i;
|
|
46
|
+
i += arity + 1;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const LAUNCHER_BARE_NUMBER = /^\d+[smhd]?$/;
|
|
50
|
+
export const PROGRAM_POSITION_REFUSAL_KINDS = ["option_at_program_position", "control_syntax_at_program_position"];
|
|
51
|
+
export function readProgramPosition(toks) {
|
|
52
|
+
const { folded } = toks;
|
|
53
|
+
const controlWords = [];
|
|
54
|
+
let i = 0;
|
|
55
|
+
while (i < folded.length && isBareControlWord(toks, i, SHELL_CONTROL_PREFIXES)) {
|
|
56
|
+
const controlWord = folded[i];
|
|
57
|
+
controlWords.push(i);
|
|
58
|
+
i = pastLauncherOptions(controlWord, folded, i + 1);
|
|
59
|
+
}
|
|
60
|
+
const none = (written, assignments, refusal) => ({ controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: undefined, refusal });
|
|
61
|
+
if (i >= folded.length)
|
|
62
|
+
return none(undefined, []);
|
|
63
|
+
const written = i;
|
|
64
|
+
const assignments = [];
|
|
65
|
+
while (i < folded.length && ASSIGNMENT_WORD.test(folded[i])) {
|
|
66
|
+
assignments.push(i);
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
const head = folded[i];
|
|
70
|
+
if (head === undefined)
|
|
71
|
+
return none(written, assignments);
|
|
72
|
+
const basename = programBasename(head);
|
|
73
|
+
if (basename.startsWith("-") && basename !== "-") {
|
|
74
|
+
const owner = controlWords.length > 0 ? folded[controlWords[controlWords.length - 1]] : head;
|
|
75
|
+
return none(written, assignments, { kind: "option_at_program_position", owner, option: head });
|
|
76
|
+
}
|
|
77
|
+
if (!COMMAND_LAUNCHERS.has(basename)) {
|
|
78
|
+
return { controlWords, written, assignments, launchers: [], argvFromStdin: false, runsDefaultProgram: false, program: { index: i, name: head, basename }, refusal: undefined };
|
|
79
|
+
}
|
|
80
|
+
const launchers = [{ index: i, name: basename }];
|
|
81
|
+
const owner = head;
|
|
82
|
+
let argvFromStdin = ARGV_REENTRY_PROGRAM_RE.test(basename);
|
|
83
|
+
let lastReentry = argvFromStdin ? i : -1;
|
|
84
|
+
const chain = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin, runsDefaultProgram: lastReentry === folded.length - 1, program, refusal });
|
|
85
|
+
for (let j = pastLauncherOptions(basename, folded, i + 1); j < folded.length; j++) {
|
|
86
|
+
const t = folded[j];
|
|
87
|
+
if (ASSIGNMENT_WORD.test(t) || LAUNCHER_BARE_NUMBER.test(t))
|
|
88
|
+
continue;
|
|
89
|
+
const wordBasename = programBasename(t);
|
|
90
|
+
if (ARGV_REENTRY_PROGRAM_RE.test(wordBasename)) {
|
|
91
|
+
argvFromStdin = true;
|
|
92
|
+
lastReentry = j;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (COMMAND_LAUNCHERS.has(wordBasename)) {
|
|
96
|
+
launchers.push({ index: j, name: wordBasename });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (t.startsWith("-"))
|
|
100
|
+
return chain(undefined, { kind: "option_at_program_position", owner, option: t });
|
|
101
|
+
if (SHELL_CONTROL_PREFIXES.has(wordBasename) || SHELL_BLOCK_WORDS.has(wordBasename)) {
|
|
102
|
+
return chain(undefined, { kind: "control_syntax_at_program_position", owner, word: wordBasename });
|
|
103
|
+
}
|
|
104
|
+
return chain({ index: j, name: t, basename: wordBasename });
|
|
105
|
+
}
|
|
106
|
+
return chain(undefined);
|
|
107
|
+
}
|
|
108
|
+
export function argvReentryUnjudged(pos, noPathPrograms) {
|
|
109
|
+
if (!pos.argvFromStdin)
|
|
110
|
+
return false;
|
|
111
|
+
const runs = pos.program?.basename ?? (pos.runsDefaultProgram ? ARGV_REENTRY_DEFAULT_PROGRAM : undefined);
|
|
112
|
+
return runs === undefined || !noPathPrograms.has(runs);
|
|
113
|
+
}
|
|
114
|
+
export function programPositionRefusalSentence(refusal) {
|
|
115
|
+
switch (refusal.kind) {
|
|
116
|
+
case "option_at_program_position":
|
|
117
|
+
return `"${refusal.owner}" is given the option "${refusal.option}" where the program it runs would be — which program runs is not readable without that word's option table ${NOT_AUTO_ALLOWED}`;
|
|
118
|
+
case "control_syntax_at_program_position":
|
|
119
|
+
return `"${refusal.owner}" is given shell control syntax ("${refusal.word}") where the program it runs would be ${NOT_AUTO_ALLOWED}`;
|
|
120
|
+
default: {
|
|
121
|
+
const exhaustive = refusal;
|
|
122
|
+
return exhaustive;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ShellSegmentConnector } from "./bash-lexer.js";
|
|
1
2
|
/**
|
|
2
3
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
3
4
|
* The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
|
|
@@ -30,9 +31,8 @@
|
|
|
30
31
|
* value-taking options defeat the stdin-floor operand count without a per-verb option-value model
|
|
31
32
|
* — those are a follow-on slice, not a silent drop.
|
|
32
33
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
|
|
34
|
+
export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
|
|
35
|
+
export type { ShellSegmentConnector } from "./bash-lexer.js";
|
|
36
36
|
export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
|
|
37
37
|
/**
|
|
38
38
|
* The CLASSIFY face's default allow set: the shared list PLUS the verbs that are
|
|
@@ -140,11 +140,6 @@ export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<
|
|
|
140
140
|
export declare function carriesShellRedirection(text: string): boolean;
|
|
141
141
|
/** What {@link splitShellCompoundSegments} produces: the connector-delimited segments, plus which of
|
|
142
142
|
* them a PIPE fed (the read-only classifier's stdin arm needs that distinction; a rule lane does not). */
|
|
143
|
-
/** The connector bash ran a segment through — `"start"` for the first one. A closed set, because the read
|
|
144
|
-
* boundary distinguishes all four: `&&` is the only one under which the previous segment is known to have
|
|
145
|
-
* exited ZERO, `||` is the only one under which the previous segment may have been SKIPPED ENTIRELY, `|`
|
|
146
|
-
* puts the segment in a subshell whose directory changes never reach the parent, and `;` is plain sequence. */
|
|
147
|
-
export type ShellSegmentConnector = "start" | ";" | "&&" | "||" | "|";
|
|
148
143
|
export interface ShellCompoundSegments {
|
|
149
144
|
segments: string[];
|
|
150
145
|
/** Parallel to {@link segments}: was this segment preceded by `|` (rather than `;`/`&&`/`||`/nothing)?
|
|
@@ -184,11 +179,11 @@ export interface ShellCompoundSegments {
|
|
|
184
179
|
* own empty-command rule puts the whole string outside its lane — which is what keeps a statement
|
|
185
180
|
* like "this rule form never matches a command containing a connector" literally true instead of
|
|
186
181
|
* true-except-for-one-spelling.
|
|
187
|
-
* 3. the quote-aware connector scan over `;` `&&` `||`
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* quote-blind scan — strictly MORE segments, each of which the
|
|
191
|
-
* fail-closed direction.
|
|
182
|
+
* 3. the quote-aware connector scan over `;` `&&` `||` `|` — the strict mode of the ONE lexer
|
|
183
|
+
* ({@link lexShellText}). A lone `&` is REFUSED rather than split on: it backgrounds a process that
|
|
184
|
+
* outlives the command, which is not a combinator any face here is willing to reason about.
|
|
185
|
+
* Unbalanced quoting falls back to the quote-blind scan — strictly MORE segments, each of which the
|
|
186
|
+
* caller must still vet, i.e. the fail-closed direction.
|
|
192
187
|
*
|
|
193
188
|
* Empty segments are NOT refused here (`;;`, a leading `;`, a trailing `&&`) — they come back as empty
|
|
194
189
|
* strings, and every caller's per-segment vetting rejects them through its own empty-command rule. The
|
|
@@ -200,6 +195,46 @@ export declare function splitShellCompoundSegments(source: string, options?: {
|
|
|
200
195
|
}): ShellCompoundSegments | {
|
|
201
196
|
reject: string;
|
|
202
197
|
};
|
|
198
|
+
/**
|
|
199
|
+
* Compound read-only classification for the `bash` CLASSIFY face (shellGate:"classify").
|
|
200
|
+
* Splits a command on the connector set `;` `&&` `||` `|` and passes every segment through
|
|
201
|
+
* {@link coarseReadonlyCheck} (the single {@link parseLeadingCommandName} parser + bare-name allowlist).
|
|
202
|
+
* All segments green ⇒ read-only. Returns a reason when rejected, else undefined.
|
|
203
|
+
*
|
|
204
|
+
* G2 — the split CARRIES QUOTE STATE ({@link quoteMask}): a connector character inside a quoted region is
|
|
205
|
+
* argument text, not a boundary, so `grep -E "error:|BUILD FAILED" x.log | tail -10` is two segments
|
|
206
|
+
* (`grep …` and `tail -10`) rather than four, and the phantom head `BUILD` no longer decides the verdict.
|
|
207
|
+
* The whole compound face is what this fixes: a report of an every-command approval prompt on a read-only
|
|
208
|
+
* monitoring pipeline traced to exactly this — a quoted alternation in a `grep -E` pattern, which is the
|
|
209
|
+
* ordinary spelling of that idiom.
|
|
210
|
+
*
|
|
211
|
+
* Soundness — why the quote-aware split is NOT a relaxation. Backslash/`$`/backtick/subshells/redirection/
|
|
212
|
+
* line breaks are whole-string rejected first (the two neutral forms in {@link EXEMPT_REDIRECTION} are
|
|
213
|
+
* removed beforehand, and they move no data into any file), so in the residual space `'`/`"` pairing is
|
|
214
|
+
* the ENTIRETY of quoting and the mask's regions are exactly bash's quoted regions. The unquoted connectors it splits on are therefore exactly bash's command boundaries:
|
|
215
|
+
* every program bash runs still starts at the head of some segment this classifier vetted, and its argv[0]
|
|
216
|
+
* is the head token (a token carrying a quote/brace/glob/tilde character is rejected by the parser's
|
|
217
|
+
* argv[0] metachar rule, and a clean token equals the executed program name). What changes versus the
|
|
218
|
+
* older quote-blind scan is only that PHANTOM segments — heads manufactured out of quoted text, which no
|
|
219
|
+
* program was ever going to run — are gone. Those could only ever ADD a rejection, so removing them
|
|
220
|
+
* removes false demotions and admits no additional program.
|
|
221
|
+
*
|
|
222
|
+
* UNBALANCED QUOTING falls back to the quote-blind scan (fail-closed). A string with an unterminated quote
|
|
223
|
+
* is one bash cannot parse into commands at all, so there is no "true" segmentation to be accurate about;
|
|
224
|
+
* the blind scan yields strictly MORE segments, each of which must still pass the allowlist.
|
|
225
|
+
*
|
|
226
|
+
* A LONE `&` is hard-rejected (not a connector): it backgrounds a process that survives the shell — the node
|
|
227
|
+
* execution env spools stdio and deliberately leaves descendants alive on completion — escaping TaskStop and
|
|
228
|
+
* background-lifecycle governance entirely. Backgrounding is not a read-only combinator, even for a listed
|
|
229
|
+
* reader (`tail -f x &` outlives the call). `|&` falls out of the same scan (its `&` reads as lone → reject).
|
|
230
|
+
* One TRAILING `;` is stripped (a no-op terminator); any other empty segment (`;;`, leading `;`, trailing
|
|
231
|
+
* `&&`/`|`) rejects via the parser's empty-command rule.
|
|
232
|
+
*
|
|
233
|
+
* NOT here by design: no git/subcommand allowlist — "zero-flag" vetting operates
|
|
234
|
+
* on pre-expansion text while the shell expands quotes/braces/globs into argv, and the candidate subcommands
|
|
235
|
+
* are not provably read-only under repo config anyway. Deployments needing those forms must widen via a
|
|
236
|
+
* dedicated widening decision, not this classifier.
|
|
237
|
+
*/
|
|
203
238
|
/**
|
|
204
239
|
* The READ-BOUNDARY face of the read-only classification (opt-in).
|
|
205
240
|
*
|
|
@@ -378,6 +413,21 @@ export interface CompoundReadonlyVerdict {
|
|
|
378
413
|
* path is unchanged.
|
|
379
414
|
*/
|
|
380
415
|
recursiveReadPaths?: readonly string[];
|
|
416
|
+
/**
|
|
417
|
+
* The ONE directory whose addition to the declared roots would make every objection this walk raised go
|
|
418
|
+
* away. It rides the SAME arm as the containment sentence and is dropped wherever that sentence is (a
|
|
419
|
+
* SHAPE refusal takes both — `tail -f <out-of-root>` stays refused after any grant), and on that arm the
|
|
420
|
+
* claim is MEASURED, not argued: the walk runs again with the proposed root in place and the directory is
|
|
421
|
+
* offered only if that walk has no objection left. `..`-traversing spellings are excluded on their own
|
|
422
|
+
* account ({@link outOfRootSpellings}): that is a disagreement between the folded text and the kernel,
|
|
423
|
+
* which no replay of the text can settle. VALUE: the deepest directory PROPERLY
|
|
424
|
+
* containing every {@link outOfRootPaths} entry, folded lexically — zero I/O, so a path that is itself a
|
|
425
|
+
* directory contributes its PARENT (`ls /etc/ssl` ⇒ `/etc`) where the reference implementation narrows
|
|
426
|
+
* with a stat; refused rather than widened when the result would be a family root or would contain the
|
|
427
|
+
* declared home or a declared {@link BashReadonlyRootBoundary.roots} entry. Meaning and spelling
|
|
428
|
+
* contract: {@link import("../../core/tool-spec.js").ReadRootGrantCandidate}.
|
|
429
|
+
*/
|
|
430
|
+
outOfRootGrantCandidate?: string;
|
|
381
431
|
}
|
|
382
432
|
/**
|
|
383
433
|
* The single minting point for the out-of-root-read approval option text, so a gate rendering
|
|
@@ -390,6 +440,18 @@ export interface CompoundReadonlyVerdict {
|
|
|
390
440
|
* uses the host's native separator.
|
|
391
441
|
*/
|
|
392
442
|
export declare function formatOutOfRootReadApprovalOption(directory: string): string;
|
|
443
|
+
/**
|
|
444
|
+
* The deepest directory containing both, or `undefined` when they share none (two path families).
|
|
445
|
+
*
|
|
446
|
+
* LINEAR, and that is the point: both paths are already folded, so the answer is their common prefix cut
|
|
447
|
+
* at a separator. Climbing one directory at a time re-folds the whole remaining path each step — the same
|
|
448
|
+
* answer at quadratic cost, measured at 4.9 s of synchronous work for one 32 KB command naming a
|
|
449
|
+
* 16 000-segment operand, on a walk that runs BEFORE the gate arms its probe deadline. The family-aware
|
|
450
|
+
* comparator keeps the last word: a prefix it cannot confirm contains both paths yields no candidate.
|
|
451
|
+
* Exported for its unit tests — the family confirmation is reachable from a command only through shapes
|
|
452
|
+
* the compound grammar refuses first, so a command-level pin cannot tell a live guard from an absent one.
|
|
453
|
+
*/
|
|
454
|
+
export declare function deepestCommonDir(a: string, b: string): string | undefined;
|
|
393
455
|
/** Resolve one operand against the boundary's base, WITHOUT touching the filesystem. Returns undefined
|
|
394
456
|
* when the token cannot be resolved statically — the fail-closed direction, reported as its own
|
|
395
457
|
* demotion rather than silently treated as in-root.
|
|
@@ -516,8 +578,11 @@ export interface OutOfRootReadGateVerdict {
|
|
|
516
578
|
* as display/triage evidence; emptiness is not "not gated". */
|
|
517
579
|
readonly paths: readonly string[];
|
|
518
580
|
}
|
|
519
|
-
/** Test seam for the lockstep pin that
|
|
520
|
-
*
|
|
581
|
+
/** Test seam for the lockstep pin that the launcher table (the ONE launcher truth, in the program-position
|
|
582
|
+
* reader's module) stays a SUBSET of the rule lane's curated `BARE_INTERPRETER_NAMES`. Exported for that one
|
|
583
|
+
* assertion — production code reads the table directly. The containment face ({@link classifyOutOfRootReadGate})
|
|
584
|
+
* does NOT look through a launcher: there every segment whose program is not a listed reader is judged whole,
|
|
585
|
+
* launcher or not, so looking through one would only narrow what it reads. */
|
|
521
586
|
export declare const COMMAND_LAUNCHERS_FOR_TEST: ReadonlySet<string>;
|
|
522
587
|
/**
|
|
523
588
|
* The OUT-OF-ROOT READ GATE asked as its OWN question: does any listed reader in this command
|
|
@@ -672,7 +737,7 @@ export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
|
|
|
672
737
|
* · STDIN-BLOCKING BODY (an availability residual, not a safety one). CLOSED for the shape this note
|
|
673
738
|
* used to record: a quoted multiword operand like `grep "x y"` read, to the then whitespace-blind
|
|
674
739
|
* tokenizer, as two operands meeting grep's file floor, while bash passes ONE pattern and no file and
|
|
675
|
-
* grep blocks on stdin.
|
|
740
|
+
* grep blocks on stdin. the lexer now counts the operand bash actually passes, so
|
|
676
741
|
* that command demotes on both this arm and the plain face. What REMAINS residual is the general
|
|
677
742
|
* statement: this arm inherits whatever the shared availability arms can see, they are best-effort
|
|
678
743
|
* (an operand that only turns out to be unreadable at run time is not in the text), and a hang is
|