@sema-agent/core 7.46.0 → 7.48.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 +106 -0
- package/dist/core/allow-entry.d.ts +1 -1
- package/dist/core/allow-entry.js +4 -5
- package/dist/core/ask-origin.d.ts +35 -12
- package/dist/core/ask-origin.js +23 -6
- package/dist/core/checkpoint-store.d.ts +1 -1
- package/dist/core/engine-notice.d.ts +7 -4
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +2 -1
- package/dist/core/gate-outcome.d.ts +4 -2
- package/dist/core/gate-outcome.js +2 -2
- package/dist/core/mcp-server-spec.d.ts +1 -1
- package/dist/core/permission-rule-model.d.ts +12 -6
- package/dist/core/permission-rule-model.js +10 -2
- package/dist/core/permission-rules.d.ts +4 -5
- package/dist/core/permission-rules.js +28 -15
- package/dist/core/runner/gate-exit.d.ts +4 -1
- package/dist/core/runner/gate-exit.js +5 -1
- package/dist/core/runner/park-commit.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +5 -5
- package/dist/core/runner/prepare-policy-chain.js +2 -1
- package/dist/core/safety-axis-vocab.d.ts +1 -1
- package/dist/core/safety-merge-corpus.d.ts +1 -1
- package/dist/core/session-policy-store.d.ts +6 -3
- package/dist/core/session-policy-store.js +28 -10
- package/dist/core/shell-wrapper-projection.js +1 -1
- package/dist/core/shell-wrapper-table.d.ts +8 -8
- package/dist/core/shell-wrapper-table.js +3 -3
- package/dist/core/tool-catalog-entries.js +3 -2
- package/dist/core/tool-face.d.ts +10 -1
- package/dist/core/tool-name-aliases.d.ts +9 -8
- package/dist/core/tool-name-aliases.js +2 -0
- package/dist/core/tool-name-readings.d.ts +8 -0
- package/dist/core/tool-name-readings.js +2 -0
- package/dist/core/tool-policy.d.ts +10 -9
- package/dist/core/tool-policy.js +6 -7
- package/dist/core/tool-registry.d.ts +9 -0
- package/dist/core/tool-registry.js +10 -0
- package/dist/core/write-protect.d.ts +10 -3
- package/dist/core/write-protect.js +11 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow.js +2 -2
- package/dist/prompt-assembly/types.d.ts +1 -1
- package/dist/shell/lex.d.ts +15 -0
- package/dist/shell/lex.js +86 -15
- package/dist/tools/fs/bash-program-position.d.ts +16 -6
- package/dist/tools/fs/bash-program-position.js +18 -12
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/index.js +2 -2
- package/dist/tools/fs/program-read-model.d.ts +84 -12
- package/dist/tools/fs/program-read-model.js +105 -34
- package/dist/tools/fs/program-read-model.rows.js +26 -7
- package/dist/tools/fs/read-deny-home.d.ts +20 -1
- package/dist/tools/fs/read-deny-home.js +28 -7
- package/dist/tools/fs/read-deny.d.ts +13 -6
- package/dist/tools/fs/read-deny.js +12 -3
- package/dist/tools/fs/removal-targets.js +24 -5
- package/dist/tools/fs/shell-read-argv.d.ts +6 -8
- package/dist/tools/fs/shell-read-argv.js +5 -5
- package/dist/tools/fs/shell-read-bindings.d.ts +71 -19
- package/dist/tools/fs/shell-read-bindings.js +190 -75
- package/dist/tools/fs/shell-read-establish.js +6 -7
- package/dist/tools/fs/shell-read-plan.d.ts +3 -15
- package/dist/tools/fs/shell-read-plan.js +126 -129
- package/dist/tools/fs/shell-read-start.d.ts +57 -0
- package/dist/tools/fs/shell-read-start.js +92 -0
- package/dist/tools/fs/shell-read-word.d.ts +68 -8
- package/dist/tools/fs/shell-read-word.js +106 -12
- package/dist/tools/fs/shell-read-write-targets.js +4 -3
- package/dist/tools/fs/shell-reentry-text.d.ts +103 -3
- package/dist/tools/fs/shell-reentry-text.js +122 -4
- package/dist/tools/monitor.d.ts +3 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +9 -3
- package/wire-changes/core@7.47.0.json +412 -0
- package/wire-changes/core@7.48.0.json +252 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SHELL_CONTROL_PREFIXES } from "../../shell/lex.js";
|
|
2
|
-
import { BLOCK_WORD_NAMES, LAUNCHER_NAMES, feedsArgvFromStdin, launcherOptionStep, programRow, selectedOptions, streamDefaultProgram } from "./program-read-model.js";
|
|
2
|
+
import { BLOCK_WORD_NAMES, LAUNCHER_NAMES, feedsArgvFromStdin, launcherOptionStep, launcherRunsShell, programRow, selectedOptions, streamDefaultProgram } from "./program-read-model.js";
|
|
3
3
|
export const NOT_AUTO_ALLOWED = "— not auto-allowed";
|
|
4
4
|
export function programBasename(word) {
|
|
5
5
|
return word.includes("/") ? (word.split("/").pop() ?? word) : word;
|
|
@@ -16,9 +16,12 @@ export function writtenAssignmentPrefix(token) {
|
|
|
16
16
|
}
|
|
17
17
|
export { SHELL_CONTROL_PREFIXES };
|
|
18
18
|
export function isBareControlWord(words, i, set) {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
return isShellSyntaxWord(words[i], set);
|
|
20
|
+
}
|
|
21
|
+
export function isShellSyntaxWord(w, set) {
|
|
22
|
+
return w !== undefined && w.atCommandPosition && w.raw === w.folded && set.has(w.folded);
|
|
21
23
|
}
|
|
24
|
+
export const LOOP_LIST_HEADS = new Set(["for", "select"]);
|
|
22
25
|
export function isLeadingAssignmentWord(words, i) {
|
|
23
26
|
const w = words[i];
|
|
24
27
|
return w !== undefined && w.assignment !== undefined && w.assignment.bare && w.atCommandPosition;
|
|
@@ -79,12 +82,12 @@ export function readProgramPosition(words) {
|
|
|
79
82
|
}
|
|
80
83
|
const launchers = [{ index: i, name: basename }];
|
|
81
84
|
const owner = head;
|
|
82
|
-
const shellRefusal = (
|
|
85
|
+
const shellRefusal = (launcher, from) => ({ kind: "control_syntax_at_program_position", owner, word: "sh", runsShell: { launcher, from } });
|
|
83
86
|
let lastRow = launcherRowOf(basename)?.launcher;
|
|
84
87
|
let owedOperands = lastRow?.requiredOperands ?? 0;
|
|
85
88
|
const chain0 = (program, refusal) => ({ controlWords, written, assignments, launchers, argvFromStdin: false, runsDefaultProgram: false, program, refusal });
|
|
86
|
-
if (
|
|
87
|
-
return chain0(undefined, shellRefusal(
|
|
89
|
+
if (launcherRunsShell(launcherRowOf(basename), folded.slice(i + 1)))
|
|
90
|
+
return chain0(undefined, shellRefusal(i, i + 1));
|
|
88
91
|
let argvFromStdin = feedsArgvFromStdin(basename);
|
|
89
92
|
let lastReentry = argvFromStdin ? i : -1;
|
|
90
93
|
let reentryDefault = argvFromStdin ? streamDefaultProgram(basename) : undefined;
|
|
@@ -92,6 +95,7 @@ export function readProgramPosition(words) {
|
|
|
92
95
|
const runsDefaultProgram = lastReentry === folded.length - 1;
|
|
93
96
|
return { controlWords, written, assignments, launchers, argvFromStdin, runsDefaultProgram, ...(runsDefaultProgram && reentryDefault !== undefined ? { defaultProgram: reentryDefault } : {}), program, refusal };
|
|
94
97
|
};
|
|
98
|
+
const selectsShell = (l, end) => selectedOptions(launcherRowOf(l.name), folded.slice(l.index + 1, end)).some((o) => o.mode === "interactive");
|
|
95
99
|
for (let j = pastLauncherOptions(basename, folded, i + 1); j < folded.length; j++) {
|
|
96
100
|
const t = folded[j];
|
|
97
101
|
if (owedOperands > 0 && !t.startsWith("-")) {
|
|
@@ -100,6 +104,8 @@ export function readProgramPosition(words) {
|
|
|
100
104
|
}
|
|
101
105
|
if (lastRow?.assignmentOperands === true && !t.startsWith("-") && t.includes("="))
|
|
102
106
|
continue;
|
|
107
|
+
if (lastRow?.shellCommand !== undefined && !t.startsWith("-") && selectsShell(launchers[launchers.length - 1], j))
|
|
108
|
+
return chain(undefined, shellRefusal(launchers[launchers.length - 1].index, j));
|
|
103
109
|
const wordBasename = programBasename(t);
|
|
104
110
|
if (feedsArgvFromStdin(wordBasename)) {
|
|
105
111
|
argvFromStdin = true;
|
|
@@ -112,8 +118,8 @@ export function readProgramPosition(words) {
|
|
|
112
118
|
if (LAUNCHER_NAMES.has(wordBasename)) {
|
|
113
119
|
launchers.push({ index: j, name: wordBasename });
|
|
114
120
|
lastRow = launcherRowOf(wordBasename)?.launcher;
|
|
115
|
-
if (
|
|
116
|
-
return chain(undefined, shellRefusal(
|
|
121
|
+
if (launcherRunsShell(launcherRowOf(wordBasename), folded.slice(j + 1)))
|
|
122
|
+
return chain(undefined, shellRefusal(j, j + 1));
|
|
117
123
|
owedOperands = lastRow?.requiredOperands ?? 0;
|
|
118
124
|
j = pastLauncherOptions(wordBasename, folded, j + 1) - 1;
|
|
119
125
|
continue;
|
|
@@ -126,8 +132,8 @@ export function readProgramPosition(words) {
|
|
|
126
132
|
return chain({ index: j, name: t, basename: wordBasename });
|
|
127
133
|
}
|
|
128
134
|
const last = launchers[launchers.length - 1];
|
|
129
|
-
if (lastRow?.whenNoProgram === "shell" ||
|
|
130
|
-
return chain(undefined, shellRefusal());
|
|
135
|
+
if (lastRow?.whenNoProgram === "shell" || selectsShell(last, folded.length))
|
|
136
|
+
return chain(undefined, shellRefusal(last.index, folded.length));
|
|
131
137
|
return chain(undefined);
|
|
132
138
|
}
|
|
133
139
|
export function argvReentryUnjudged(pos, acceptsTarget) {
|
|
@@ -139,12 +145,12 @@ export function argvReentryUnjudged(pos, acceptsTarget) {
|
|
|
139
145
|
export const SHELL_SCAN_DECLINES = Object.freeze(["unreadable_program_position", "stream_fed_operands"]);
|
|
140
146
|
export function programPositionRunsShell(pos, runsShell) {
|
|
141
147
|
if (pos.refusal !== undefined)
|
|
142
|
-
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell
|
|
148
|
+
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell !== undefined;
|
|
143
149
|
return pos.program !== undefined && runsShell(pos.program.basename);
|
|
144
150
|
}
|
|
145
151
|
export function shellScanDeclineOf(pos, acceptsStreamTarget) {
|
|
146
152
|
if (pos.refusal !== undefined) {
|
|
147
|
-
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell
|
|
153
|
+
return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell !== undefined ? undefined : "unreadable_program_position";
|
|
148
154
|
}
|
|
149
155
|
return argvReentryUnjudged(pos, acceptsStreamTarget) ? "stream_fed_operands" : undefined;
|
|
150
156
|
}
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -187,8 +187,9 @@ export interface HandsToolkitOptions {
|
|
|
187
187
|
* under a short bound of its own, and a probe of it that fails costs that entry alone. The public shell judgment takes the
|
|
188
188
|
* same list as {@link import("./shell-read-check.js").ShellReadJudgeInput.readDenyHomes}. */
|
|
189
189
|
readDenyHomes?: readonly string[];
|
|
190
|
-
/** Where each home-anchored row's literal head leads beyond the home's spellings
|
|
191
|
-
* ({@link import("./read-deny-home.js").readDenyHomeLinksOf}
|
|
190
|
+
/** Where each home-anchored row's literal head leads beyond the home's spellings, and each rooted row's (`/…`) beyond its own
|
|
191
|
+
* spelling ({@link import("./read-deny-home.js").readDenyHomeLinksOf} / {@link import("./read-deny-home.js").readDenyRootLinksOf},
|
|
192
|
+
* keyed by the row's pattern) — the Runner passes the
|
|
192
193
|
* locations its own judge was compiled with, so the band and the gate judge one placement.
|
|
193
194
|
* Unless a mount carries both, the band resolves the placement itself the way the Runner does (the homes it carries
|
|
194
195
|
* are its home set — the account is not asked and no home is added;
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -14,7 +14,7 @@ export * from "./read-deny.js";
|
|
|
14
14
|
export { readDenyHomesOf } from "./read-deny-home.js";
|
|
15
15
|
import { BASH_READONLY_DEFAULT_ALLOW } from "./bash-readonly-classifier.js";
|
|
16
16
|
export { BASH_CLASSIFY_EXTRA_ALLOW, bashClassifyDefaultAllow } from "./bash-readonly-classifier.js";
|
|
17
|
-
import { checkedReadDenyHomes, compileReadDeny, compileWriteDeny, readDenyBuiltinConfigOf, readDenyPatternRun } from "./read-deny.js";
|
|
17
|
+
import { checkedReadDenyHomes, compileReadDeny, compileWriteDeny, isRootedReadDenyPattern, readDenyBuiltinConfigOf, readDenyPatternRun } from "./read-deny.js";
|
|
18
18
|
import { withGateTimeout } from "../../core/gate-bounded-await.js";
|
|
19
19
|
import { carryHomeViews, homeViewsAt, placeReadDenyAtHome, placementViewsOf, readDenyPlacedLater, sayPlacementLost } from "./read-deny-home.js";
|
|
20
20
|
import { deploymentFaceClampNotice, resolveContainmentFace } from "./containment-face.js";
|
|
@@ -51,7 +51,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
|
|
|
51
51
|
const carriedViews = carried ? placementViewsOf(opts.readDenyHomes) : undefined;
|
|
52
52
|
if (carried && carriedViews === undefined)
|
|
53
53
|
sayPlacementLost();
|
|
54
|
-
const placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored);
|
|
54
|
+
const placesNothing = !unplaced.entries.some((e) => readDenyPatternRun(e.pattern).homeAnchored || isRootedReadDenyPattern(e.pattern));
|
|
55
55
|
const late = carried || placesNothing ? undefined : readDenyPlacedLater(carryHomeViews(unplaced, homeViewsAt(env, [])), () => placeReadDenyAtHome(env, compileAt, readDenyHomes));
|
|
56
56
|
const readDeny = late?.matcher ??
|
|
57
57
|
(carried
|
|
@@ -75,7 +75,8 @@ export type ArgRole =
|
|
|
75
75
|
| {
|
|
76
76
|
readonly kind: "shell-text";
|
|
77
77
|
}
|
|
78
|
-
/** The program
|
|
78
|
+
/** The program a launcher or a builtin runs by name: a function (`compgen -F`), the program a launcher runs in place of
|
|
79
|
+
* a shell (`su -s PROGRAM`). */
|
|
79
80
|
| {
|
|
80
81
|
readonly kind: "command";
|
|
81
82
|
}
|
|
@@ -163,10 +164,10 @@ export interface OptionSpec {
|
|
|
163
164
|
readonly endsOptions?: true;
|
|
164
165
|
/** `banner`: prints and exits; `interactive`: reads commands from its standard input (after its program, `python -i`;
|
|
165
166
|
* instead of a script file, `sh -s`; a launcher's shell, run where no program follows, `sudo -s`); `names-program`: selects
|
|
166
|
-
* the program to run without a value (`node --test
|
|
167
|
-
* (`tail -f`); `repeats`: repeats on an interval until killed (`free -s`);
|
|
168
|
-
* text tells it to (`sed -n`); `forced`: removes without asking, and passes
|
|
169
|
-
* (`rm -f`). */
|
|
167
|
+
* the program to run without a value (`node --test`; a launcher's, from the caller's environment: `su -m`); `follows`:
|
|
168
|
+
* follows its files as they grow, until killed (`tail -f`); `repeats`: repeats on an interval until killed (`free -s`);
|
|
169
|
+
* `quiet`: prints only what the program's own text tells it to (`sed -n`); `forced`: removes without asking, and passes
|
|
170
|
+
* over an operand that is not there (`rm -f`). */
|
|
170
171
|
readonly mode?: OptionMode;
|
|
171
172
|
/** This switch turns the program's own value glob off (`curl -g`): every {@link ArgRole} `programGlob` value of the
|
|
172
173
|
* invocation is read literally. */
|
|
@@ -250,15 +251,44 @@ export interface LauncherSpec {
|
|
|
250
251
|
readonly assignmentOperands?: true;
|
|
251
252
|
/** A bare negative integer in its option section is its own argument (`nice -5 <cmd>`). */
|
|
252
253
|
readonly negativeIntegerIsOwnArgument?: true;
|
|
253
|
-
/** It
|
|
254
|
-
|
|
254
|
+
/** It runs a SHELL in place of a program word, and how the words after it become that shell's argv: `joined` — its
|
|
255
|
+
* non-option words, joined by a space, are the shell's `-c` text (`watch "cat /x"`); `user` — its first operand (past
|
|
256
|
+
* a lone `-`) names the user it runs as, and the words after the user are the shell's own argv, behind a `-c TEXT` its
|
|
257
|
+
* own shell-text option gives (`su [-] [USER [ARG…]]`: an implementation that reads its options anywhere takes them
|
|
258
|
+
* past the user too, one that stops at the user hands them on); the program is its option of role `command` where
|
|
259
|
+
* one is given (`su -s PROGRAM`), or a `names-program` switch's (`su -m`: the caller's `$SHELL`). */
|
|
260
|
+
readonly shellText?: "joined" | "user";
|
|
261
|
+
/** Option spellings that make a {@link shellText} launcher run the program its operands name instead
|
|
262
|
+
* (`runuser -u USER CMD…`). */
|
|
263
|
+
readonly programWith?: readonly string[];
|
|
255
264
|
/** With no program word it runs a shell. */
|
|
256
265
|
readonly whenNoProgram?: "shell";
|
|
266
|
+
/** Where a switch selecting its shell (mode `interactive`: `sudo -s`) has words after it, they are that shell's `-c`
|
|
267
|
+
* text: `escaped` — joined by a space, every character of each but a letter, a digit, `_`, `-` and `$` escaped with a
|
|
268
|
+
* backslash. Absent, the launcher takes no such words (`doas -s CMD` is refused) and they are read as the program. */
|
|
269
|
+
readonly shellCommand?: "escaped";
|
|
257
270
|
/** The spelling a required operand must have (a word spelled otherwise is not one the launcher accepts). Absent: any
|
|
258
271
|
* word that is not option-shaped. */
|
|
259
272
|
readonly requiredOperandSpelling?: RegExp;
|
|
260
273
|
/** A lone `-` in its option section is its own switch (`env -`, the obsolete spelling of `env -i`). */
|
|
261
274
|
readonly bareDash?: true;
|
|
275
|
+
/** It runs its shell as ANOTHER user ({@link TargetUser}): the user an option of `userWith` names (`sudo -u USER`), or —
|
|
276
|
+
* a {@link shellText} `user` launcher — its user operand; root where none is named. The shell's `HOME` is that user's
|
|
277
|
+
* home (`target`), or the caller's or that user's as the launcher's policy decides (`policy`: `sudo -s`); a switch of
|
|
278
|
+
* `loginWith` (`sudo -i`, `su -l`, su's lone `-` operand) starts it as a LOGIN shell — its `HOME` that user's, and it
|
|
279
|
+
* stands in that home. A `names-program` switch (`su -m`) keeps the caller's environment, `HOME` with it. */
|
|
280
|
+
readonly asUser?: {
|
|
281
|
+
readonly userWith?: readonly string[];
|
|
282
|
+
readonly home: "target" | "policy";
|
|
283
|
+
readonly loginWith: readonly string[];
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** The user a launcher runs its shell as ({@link LauncherSpec.asUser}): the name, where a word spells it literally; the
|
|
287
|
+
* shell's `HOME` (`caller` — kept; `target` — that user's; `either` — one of the two); whether it is a login shell. */
|
|
288
|
+
export interface TargetUser {
|
|
289
|
+
readonly name?: string;
|
|
290
|
+
readonly home: "caller" | "target" | "either";
|
|
291
|
+
readonly login: boolean;
|
|
262
292
|
}
|
|
263
293
|
/** How a program receives the PROGRAM it runs (an interpreter): an option value, an operand (`operands.positional`),
|
|
264
294
|
* or standard input when it names none. */
|
|
@@ -550,6 +580,20 @@ export declare function attachedStdinFile(row: ProgramRow | undefined, tok: stri
|
|
|
550
580
|
/** The row read under each implementation's grammar alone (one view per grammar), or the row itself when it declares
|
|
551
581
|
* at most one. A reader that needs to know what ONE implementation does with an argv reads these views. */
|
|
552
582
|
export declare function implementationsOf(row: ProgramRow): readonly ProgramRow[];
|
|
583
|
+
/** What a scan over an argv's option words found: the specs selected, the values they took (argv index; the value
|
|
584
|
+
* inside the option word when it is attached), and the operands (argv indices). */
|
|
585
|
+
interface OptionScan {
|
|
586
|
+
readonly specs: readonly OptionSpec[];
|
|
587
|
+
/** Each selected option's spelling as its row declares it (`-x`, `--exec`), in step with `specs`. */
|
|
588
|
+
readonly names: readonly string[];
|
|
589
|
+
readonly values: readonly {
|
|
590
|
+
readonly at: number;
|
|
591
|
+
readonly value?: string;
|
|
592
|
+
readonly spec: OptionSpec;
|
|
593
|
+
readonly name: string;
|
|
594
|
+
}[];
|
|
595
|
+
readonly operands: readonly number[];
|
|
596
|
+
}
|
|
553
597
|
/**
|
|
554
598
|
* Every option spec an argv selects. A short cluster is read as getopt reads it, scanning left to right: a declared
|
|
555
599
|
* letter is selected; a value-taking letter owns the rest of the token (and the next word, when it ends the token); a
|
|
@@ -561,6 +605,14 @@ export declare function implementationsOf(row: ProgramRow): readonly ProgramRow[
|
|
|
561
605
|
* option, and a word past `--`, included. The reading a check that only ever refuses takes: it errs to more options.
|
|
562
606
|
*/
|
|
563
607
|
export declare function selectedOptions(row: ProgramRow | undefined, args: readonly string[], under?: "one-implementation"): OptionSpec[];
|
|
608
|
+
/** The options ONE implementation's reading of an argv selects ({@link selectedOptions}), named by the spelling its row
|
|
609
|
+
* declares them under (`-x`, `--exec`), and the values they take. An option is told apart by its spelling, never by its
|
|
610
|
+
* spec: spellings share specs (every switch of a row is one). */
|
|
611
|
+
export declare function selectedSpellings(row: ProgramRow, args: readonly string[]): Pick<OptionScan, "names" | "values">;
|
|
612
|
+
/** Does ONE implementation of a row (`view`, {@link implementationsOf}) reject these option words — a spelling the row
|
|
613
|
+
* declares (another implementation's) that this one does not? getopt calls it an invalid option: the program runs
|
|
614
|
+
* nothing. */
|
|
615
|
+
export declare function implementationRejects(row: ProgramRow, view: ProgramRow, words: readonly string[]): boolean;
|
|
564
616
|
/** A path role whose effect writes. */
|
|
565
617
|
export type WritingRole = Extract<ArgRole, {
|
|
566
618
|
kind: "path";
|
|
@@ -643,18 +695,38 @@ export declare function moduleTailOf(row: ProgramRow | undefined, args: readonly
|
|
|
643
695
|
* value exists only attached (spelled bare, it takes nothing), 1 for one that takes the next word; `undefined` for any
|
|
644
696
|
* other spelling and for an option the walk does not step over. */
|
|
645
697
|
export declare function launcherOptionArity(row: ProgramRow, tok: string): 0 | 1 | undefined;
|
|
646
|
-
/** The PATH role of the value a launcher option
|
|
647
|
-
* when its value names no path. Such a value is a word the launcher opens or
|
|
698
|
+
/** The PATH role of the value a launcher option word takes (`xargs -a FILE`, `strace -fo FILE`, `strace -oFILE`), or
|
|
699
|
+
* `undefined` when its value names no path ({@link readLauncherOption}). Such a value is a word the launcher opens or
|
|
700
|
+
* writes, not an operand of its own. */
|
|
648
701
|
export declare function launcherOptionPathRole(row: ProgramRow, tok: string): Extract<ArgRole, {
|
|
649
702
|
kind: "path";
|
|
650
703
|
}> | undefined;
|
|
651
|
-
/** How many
|
|
652
|
-
*
|
|
653
|
-
* switches (1); a lone `-` where the launcher reads it as its own switch (1). */
|
|
704
|
+
/** How many words one launcher option occupies ({@link readLauncherOption}), or `undefined` where the row does not
|
|
705
|
+
* explain it. */
|
|
654
706
|
export declare function launcherOptionStep(row: ProgramRow, tok: string): 1 | 2 | undefined;
|
|
707
|
+
/** Does a launcher that runs a shell ({@link LauncherSpec.shellText}) run one over these words — the words after it —
|
|
708
|
+
* rather than the program they name? Not where an implementation selects one of its {@link LauncherSpec.programWith}
|
|
709
|
+
* options (as that implementation reads them: past `--` a word is an operand). */
|
|
710
|
+
export declare function launcherRunsShell(row: ProgramRow, args: readonly string[]): boolean;
|
|
711
|
+
/** The user a launcher runs its shell as ({@link LauncherSpec.asUser}), read off its own words (`words`, as the program
|
|
712
|
+
* receives them) by its row's walk: the value of its last `userWith` option, else its user operand (`operand`; a lone `-`
|
|
713
|
+
* in front of it starts a login shell, `dash`), else root — a name only where it is spelled as one (`root`, `#0`, a login
|
|
714
|
+
* name); `undefined` for a launcher that runs no other user. */
|
|
715
|
+
export declare function targetUserOf(row: ProgramRow, words: readonly string[], operand?: {
|
|
716
|
+
readonly name?: string;
|
|
717
|
+
readonly dash: boolean;
|
|
718
|
+
}): TargetUser | undefined;
|
|
655
719
|
/** The spellings of a launcher's command-line options (`env -S` / `--split-string`): the value is a command line the
|
|
656
720
|
* launcher splits and runs. */
|
|
657
721
|
export declare function commandLineOptionSpellings(row: ProgramRow | undefined): readonly string[];
|
|
722
|
+
/** The command line ONE launcher option word carries, read as getopt reads the word ({@link readLauncherOption}): a
|
|
723
|
+
* command-line spelling ({@link commandLineOptionSpellings}) exactly — `"next"`, the next word is the line — or with its
|
|
724
|
+
* value after `=` (long) or after the letter (short, `attached`); in a short cluster every letter before the
|
|
725
|
+
* command-line letter is a switch the walk steps (`env -vS 'S'`, `env -iS'S'`). `undefined` where the word carries
|
|
726
|
+
* none. */
|
|
727
|
+
export declare function commandLineOf(row: ProgramRow, tok: string): {
|
|
728
|
+
readonly attached: string;
|
|
729
|
+
} | "next" | undefined;
|
|
658
730
|
/** One implementation's reading of where the program text is: the text (every text option's value joined by newlines,
|
|
659
731
|
* or the first operand), `from-file` (an option names a FILE holding it), `unparsed` (an option the grammar does not
|
|
660
732
|
* declare, or a text option with nothing left to take), or `none` (no program text at all). */
|
|
@@ -682,6 +682,16 @@ export function implementationsOf(row) {
|
|
|
682
682
|
export function selectedOptions(row, args, under) {
|
|
683
683
|
return row === undefined ? [] : [...scanOptions(row, args, under === "one-implementation").specs];
|
|
684
684
|
}
|
|
685
|
+
export function selectedSpellings(row, args) {
|
|
686
|
+
const scan = scanOptions(row, args, true);
|
|
687
|
+
return { names: scan.names, values: scan.values };
|
|
688
|
+
}
|
|
689
|
+
export function implementationRejects(row, view, words) {
|
|
690
|
+
if (view === row)
|
|
691
|
+
return false;
|
|
692
|
+
const declares = (n) => (n.startsWith("--") ? mergedTable(view, "long").has(n.slice(2)) : mergedTable(view, n.length === 2 ? "short" : "dashWords").has(n.slice(1)));
|
|
693
|
+
return selectedSpellings(row, words).names.some((n) => !declares(n));
|
|
694
|
+
}
|
|
685
695
|
function scanOptions(row, args, one) {
|
|
686
696
|
const stopsAtOperand = one && row.grammars[0]?.afterFirstOperand === "operands";
|
|
687
697
|
const shorts = mergedTable(row, "short");
|
|
@@ -689,8 +699,10 @@ function scanOptions(row, args, one) {
|
|
|
689
699
|
const dashWords = mergedTable(row, "dashWords");
|
|
690
700
|
const obsolete = row.grammars.flatMap((g) => (g.obsoleteFirst === undefined ? [] : [g.obsoleteFirst]));
|
|
691
701
|
const specs = [];
|
|
702
|
+
const names = [];
|
|
692
703
|
const values = [];
|
|
693
704
|
const operands = [];
|
|
705
|
+
const select = (spec, name) => { specs.push(spec); names.push(name); };
|
|
694
706
|
let endOfOptions = false;
|
|
695
707
|
for (let k = 0; k < args.length; k++) {
|
|
696
708
|
const t = args[k];
|
|
@@ -704,7 +716,7 @@ function scanOptions(row, args, one) {
|
|
|
704
716
|
for (const ch of t.replace(/^.\d+/, "")) {
|
|
705
717
|
const spec = o.letters[ch];
|
|
706
718
|
if (spec !== undefined)
|
|
707
|
-
|
|
719
|
+
select(spec, `-${ch}`);
|
|
708
720
|
}
|
|
709
721
|
continue;
|
|
710
722
|
}
|
|
@@ -719,13 +731,13 @@ function scanOptions(row, args, one) {
|
|
|
719
731
|
endOfOptions = true;
|
|
720
732
|
continue;
|
|
721
733
|
}
|
|
722
|
-
const take = (spec, attached) => {
|
|
734
|
+
const take = (spec, name, attached) => {
|
|
723
735
|
if (attached !== undefined) {
|
|
724
|
-
values.push({ at: k, value: attached, spec });
|
|
736
|
+
values.push({ at: k, value: attached, spec, name });
|
|
725
737
|
return;
|
|
726
738
|
}
|
|
727
739
|
if (one && k + 1 < args.length) {
|
|
728
|
-
values.push({ at: k + 1, spec });
|
|
740
|
+
values.push({ at: k + 1, spec, name });
|
|
729
741
|
k++;
|
|
730
742
|
}
|
|
731
743
|
};
|
|
@@ -735,22 +747,23 @@ function scanOptions(row, args, one) {
|
|
|
735
747
|
continue;
|
|
736
748
|
const eq = t.indexOf("=");
|
|
737
749
|
const exact = longs.get(name);
|
|
738
|
-
const hits = exact !== undefined ? [exact] : [...longs].filter(([full]) => isLongOptionAbbrevOf(name, full))
|
|
739
|
-
for (const m of hits)
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
750
|
+
const hits = exact !== undefined ? [[name, exact]] : [...longs].filter(([full]) => isLongOptionAbbrevOf(name, full));
|
|
751
|
+
for (const [full, m] of hits)
|
|
752
|
+
select(m.spec, `--${full}`);
|
|
753
|
+
const [full, only] = hits[0] ?? [];
|
|
754
|
+
if (hits.length === 1 && (only.arity === 1 || (eq >= 0 && only.arity === "attached")))
|
|
755
|
+
take(only.spec, `--${full}`, eq >= 0 ? t.slice(eq + 1) : undefined);
|
|
756
|
+
else if (one && eq < 0 && hits.length > 1 && hits.every(([, m]) => m.arity === 1))
|
|
744
757
|
k++;
|
|
745
758
|
continue;
|
|
746
759
|
}
|
|
747
760
|
const word = /^-([A-Za-z_][\w-]+)$/.exec(t)?.[1];
|
|
748
761
|
const dash = word === undefined ? undefined : dashWords.get(word);
|
|
749
762
|
if (dash !== undefined) {
|
|
750
|
-
|
|
763
|
+
select(dash.spec, t);
|
|
751
764
|
if (one) {
|
|
752
765
|
if (dash.arity === 1)
|
|
753
|
-
take(dash.spec, undefined);
|
|
766
|
+
take(dash.spec, t, undefined);
|
|
754
767
|
continue;
|
|
755
768
|
}
|
|
756
769
|
}
|
|
@@ -761,18 +774,18 @@ function scanOptions(row, args, one) {
|
|
|
761
774
|
continue;
|
|
762
775
|
break;
|
|
763
776
|
}
|
|
764
|
-
|
|
777
|
+
select(m.spec, `-${t[i]}`);
|
|
765
778
|
if (m.arity === 0)
|
|
766
779
|
continue;
|
|
767
780
|
const rest = t.slice(i + 1);
|
|
768
781
|
if (rest !== "")
|
|
769
|
-
values.push({ at: k, value: rest, spec: m.spec });
|
|
782
|
+
values.push({ at: k, value: rest, spec: m.spec, name: `-${t[i]}` });
|
|
770
783
|
else if (m.arity === 1 || m.arity === "suffix")
|
|
771
|
-
take(m.spec, undefined);
|
|
784
|
+
take(m.spec, `-${t[i]}`, undefined);
|
|
772
785
|
break;
|
|
773
786
|
}
|
|
774
787
|
}
|
|
775
|
-
return { specs, values, operands };
|
|
788
|
+
return { specs, names, values, operands };
|
|
776
789
|
}
|
|
777
790
|
export function writesPath(role) {
|
|
778
791
|
return role?.kind === "path" && WRITING_EFFECTS.has(role.effect);
|
|
@@ -1023,7 +1036,14 @@ function steppableLauncherOption(row, tok) {
|
|
|
1023
1036
|
const m = tok.startsWith("--") ? (tok.includes("=") ? undefined : mergedTable(row, "long").get(tok.slice(2))) : tok.length === 2 && tok.startsWith("-") ? mergedTable(row, "short").get(tok[1]) : undefined;
|
|
1024
1037
|
if (m === undefined || m.spec.value?.kind === "shell-text" || (m.spec.value?.kind === "path" && m.spec.value.base !== undefined))
|
|
1025
1038
|
return undefined;
|
|
1026
|
-
return m.arity
|
|
1039
|
+
return m.arity !== "disputed" || takesNoWordBare(row, tok) ? m : undefined;
|
|
1040
|
+
}
|
|
1041
|
+
function takesNoWordBare(row, spelling) {
|
|
1042
|
+
const key = spelling.startsWith("--") ? spelling.slice(2) : spelling.slice(1);
|
|
1043
|
+
return row.grammars.every((g) => {
|
|
1044
|
+
const s = (spelling.startsWith("--") ? g.long : g.short)[key];
|
|
1045
|
+
return s === undefined || s.arity === 0 || s.arity === "attached";
|
|
1046
|
+
});
|
|
1027
1047
|
}
|
|
1028
1048
|
export function launcherOptionArity(row, tok) {
|
|
1029
1049
|
if (row.grammars.length === 0)
|
|
@@ -1033,27 +1053,59 @@ export function launcherOptionArity(row, tok) {
|
|
|
1033
1053
|
const m = steppableLauncherOption(row, tok);
|
|
1034
1054
|
return m === undefined ? undefined : m.arity === 1 ? 1 : 0;
|
|
1035
1055
|
}
|
|
1056
|
+
function readLauncherOption(row, tok) {
|
|
1057
|
+
if (tok === "-")
|
|
1058
|
+
return row.launcher?.bareDash === true ? { step: 1 } : undefined;
|
|
1059
|
+
if (tok === "--")
|
|
1060
|
+
return launcherOptionArity(row, tok) === 0 ? { step: 1 } : undefined;
|
|
1061
|
+
if (tok.startsWith("--")) {
|
|
1062
|
+
const eq = tok.indexOf("=");
|
|
1063
|
+
const m = steppableLauncherOption(row, eq > 2 ? tok.slice(0, eq) : tok);
|
|
1064
|
+
if (m === undefined)
|
|
1065
|
+
return undefined;
|
|
1066
|
+
if (eq > 2)
|
|
1067
|
+
return m.arity === 0 || m.arity === "disputed" ? undefined : { step: 1, valued: m };
|
|
1068
|
+
return m.arity === 1 ? { step: 2, valued: m } : { step: 1 };
|
|
1069
|
+
}
|
|
1070
|
+
if (!/^-[^-]/.test(tok))
|
|
1071
|
+
return undefined;
|
|
1072
|
+
for (let i = 1; i < tok.length; i++) {
|
|
1073
|
+
const m = steppableLauncherOption(row, `-${tok[i]}`);
|
|
1074
|
+
if (m === undefined)
|
|
1075
|
+
return undefined;
|
|
1076
|
+
if (m.arity === 0)
|
|
1077
|
+
continue;
|
|
1078
|
+
if (i + 1 < tok.length)
|
|
1079
|
+
return m.arity === "disputed" ? undefined : { step: 1, valued: m };
|
|
1080
|
+
return m.arity === 1 ? { step: 2, valued: m } : { step: 1 };
|
|
1081
|
+
}
|
|
1082
|
+
return { step: 1 };
|
|
1083
|
+
}
|
|
1036
1084
|
export function launcherOptionPathRole(row, tok) {
|
|
1037
|
-
const role =
|
|
1085
|
+
const role = readLauncherOption(row, tok)?.valued?.spec.value;
|
|
1038
1086
|
return role?.kind === "path" ? role : undefined;
|
|
1039
1087
|
}
|
|
1040
1088
|
export function launcherOptionStep(row, tok) {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1089
|
+
return readLauncherOption(row, tok)?.step;
|
|
1090
|
+
}
|
|
1091
|
+
export function launcherRunsShell(row, args) {
|
|
1092
|
+
const l = row.launcher;
|
|
1093
|
+
if (l?.shellText === undefined)
|
|
1094
|
+
return false;
|
|
1095
|
+
const programWith = l.programWith ?? [];
|
|
1096
|
+
return !implementationsOf(row).some((view) => selectedSpellings(view, args).names.some((n) => programWith.includes(n)));
|
|
1097
|
+
}
|
|
1098
|
+
export function targetUserOf(row, words, operand) {
|
|
1099
|
+
const as = row.launcher?.asUser;
|
|
1100
|
+
if (as === undefined)
|
|
1101
|
+
return undefined;
|
|
1102
|
+
const own = selectedSpellings(row, words);
|
|
1103
|
+
const user = own.values.filter((v) => as.userWith?.includes(v.name) === true).at(-1);
|
|
1104
|
+
const named = user !== undefined ? user.value ?? words[user.at] : operand === undefined ? "root" : operand.name;
|
|
1105
|
+
const login = operand?.dash === true || own.names.some((n) => as.loginWith.includes(n));
|
|
1106
|
+
const keepsCaller = selectedOptions(row, words).some((o) => o.mode === "names-program");
|
|
1107
|
+
const name = named === "#0" ? "root" : named !== undefined && /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(named) ? named : undefined;
|
|
1108
|
+
return { ...(name !== undefined ? { name } : {}), home: login ? "target" : as.home === "policy" ? "either" : keepsCaller ? "caller" : "target", login };
|
|
1057
1109
|
}
|
|
1058
1110
|
export function commandLineOptionSpellings(row) {
|
|
1059
1111
|
if (row === undefined)
|
|
@@ -1063,6 +1115,25 @@ export function commandLineOptionSpellings(row) {
|
|
|
1063
1115
|
...[...mergedTable(row, "long")].filter(([, m]) => m.spec.value?.kind === "shell-text").map(([n]) => `--${n}`),
|
|
1064
1116
|
];
|
|
1065
1117
|
}
|
|
1118
|
+
export function commandLineOf(row, tok) {
|
|
1119
|
+
const lines = new Set(commandLineOptionSpellings(row));
|
|
1120
|
+
if (tok.startsWith("--")) {
|
|
1121
|
+
const eq = tok.indexOf("=");
|
|
1122
|
+
if (!lines.has(eq > 2 ? tok.slice(0, eq) : tok))
|
|
1123
|
+
return undefined;
|
|
1124
|
+
return eq > 2 ? { attached: tok.slice(eq + 1) } : "next";
|
|
1125
|
+
}
|
|
1126
|
+
if (!/^-[^-]/.test(tok))
|
|
1127
|
+
return undefined;
|
|
1128
|
+
for (let i = 1; i < tok.length; i++) {
|
|
1129
|
+
const letter = `-${tok[i]}`;
|
|
1130
|
+
if (lines.has(letter))
|
|
1131
|
+
return i + 1 < tok.length ? { attached: tok.slice(i + 1) } : "next";
|
|
1132
|
+
if (steppableLauncherOption(row, letter)?.arity !== 0)
|
|
1133
|
+
return undefined;
|
|
1134
|
+
}
|
|
1135
|
+
return undefined;
|
|
1136
|
+
}
|
|
1066
1137
|
export function readProgramTextLanguage(row) {
|
|
1067
1138
|
const first = row?.operands?.positional?.[0];
|
|
1068
1139
|
return first?.kind === "program-text" && READ_LANGUAGES.has(first.language) ? first.language : undefined;
|
|
@@ -360,8 +360,19 @@ const launcherGrammar = (g, extra = {}) => {
|
|
|
360
360
|
put(spelling, val(SHELL_TEXT));
|
|
361
361
|
for (const spelling of g.shells ?? [])
|
|
362
362
|
put(spelling, { arity: 0, mode: "interactive" });
|
|
363
|
+
for (const spelling of g.programs ?? [])
|
|
364
|
+
put(spelling, { arity: 0, mode: "names-program" });
|
|
363
365
|
return { dialect: "any", short, long, afterFirstOperand: "either", ...extra };
|
|
364
366
|
};
|
|
367
|
+
const SU_OPTIONS = {
|
|
368
|
+
switches: ["-f", "-l", "-P", "-T", "-h", "-V", "--fast", "--login", "--pty", "--no-pty", "--help", "--version"],
|
|
369
|
+
banners: ["-h", "-V"],
|
|
370
|
+
values: { "-g": LABEL, "-G": LABEL, "-w": LABEL, "-s": COMMAND, "--group": LABEL, "--supp-group": LABEL, "--whitelist-environment": LABEL, "--shell": COMMAND },
|
|
371
|
+
shellText: ["-c", "--command", "--session-command"],
|
|
372
|
+
programs: ["-m", "-p", "--preserve-environment"],
|
|
373
|
+
};
|
|
374
|
+
const SU = launcherGrammar(SU_OPTIONS, { stepsDashDash: true });
|
|
375
|
+
const SU_LINUX_ONLY = [...SU_OPTIONS.switches, ...Object.keys(SU_OPTIONS.values), ...SU_OPTIONS.shellText, ...SU_OPTIONS.programs].filter((s) => !["-f", "-l", "-m"].includes(s));
|
|
365
376
|
const launchers = [
|
|
366
377
|
{ key: "env", grammars: implementations(launcherGrammar({
|
|
367
378
|
switches: ["-i", "-v", "--ignore-environment", "--debug", "--list-signal-handling", "--help", "--version"],
|
|
@@ -380,18 +391,18 @@ const launchers = [
|
|
|
380
391
|
optional: { "-i": LABEL, "-l": COUNT, "--replace": LABEL, "--eof": LABEL, "--max-lines": COUNT },
|
|
381
392
|
}, {}), { reads: { "-a": val(READ, { delivers: "operands" }), "--arg-file": val(READ, { delivers: "operands" }) } }, { lacks: ["-d", "-i", "-l", "-a", "--arg-file", "--null", "--no-run-if-empty", "--verbose", "--exit", "--interactive", "--open-tty", "--show-limits", "--help", "--version", "--max-args", "--max-procs", "--delimiter", "--max-chars", "--process-slot-var", "--replace", "--eof", "--max-lines"] }), launcher: {}, reentry: { kind: "argv", defaultProgram: "echo" }, witness: BINARY },
|
|
382
393
|
launcher("busybox"),
|
|
383
|
-
launcher("sudo", { assignmentOperands: true }, launcherGrammar({
|
|
394
|
+
launcher("sudo", { assignmentOperands: true, shellCommand: "escaped", asUser: { userWith: ["-u", "--user"], home: "policy", loginWith: ["-i", "--login"] } }, launcherGrammar({
|
|
384
395
|
switches: ["-A", "-b", "-E", "-H", "-K", "-k", "-l", "-n", "-P", "-S", "-V", "-v", "--askpass", "--background", "--preserve-env", "--set-home", "--remove-timestamp", "--reset-timestamp", "--list", "--non-interactive", "--preserve-groups", "--stdin", "--version", "--validate", "--bell", "--help"],
|
|
385
396
|
shells: ["-i", "-s", "--login", "--shell"],
|
|
386
397
|
values: {
|
|
387
398
|
"-u": LABEL, "-g": LABEL, "-U": LABEL, "-C": COUNT, "-D": WORKDIR, "-h": LABEL, "-p": LABEL, "-r": LABEL, "-R": ROOTDIR, "-t": LABEL, "-T": COUNT, "-a": LABEL,
|
|
388
399
|
"--user": LABEL, "--group": LABEL, "--other-user": LABEL, "--close-from": COUNT, "--chdir": WORKDIR, "--host": LABEL, "--prompt": LABEL, "--role": LABEL, "--chroot": ROOTDIR, "--type": LABEL, "--command-timeout": COUNT, "--auth-type": LABEL,
|
|
389
400
|
},
|
|
390
|
-
}), { witness: PRIVILEGED }),
|
|
391
|
-
launcher("doas", {}, launcherGrammar({ switches: ["-L", "-n"], shells: ["-s"], values: { "-a": LABEL, "-u": LABEL, "-C": READ } }), { witness: PRIVILEGED }),
|
|
401
|
+
}, { stepsDashDash: true }), { witness: PRIVILEGED }),
|
|
402
|
+
launcher("doas", { asUser: { userWith: ["-u"], home: "target", loginWith: [] } }, launcherGrammar({ switches: ["-L", "-n"], shells: ["-s"], values: { "-a": LABEL, "-u": LABEL, "-C": READ } }, { stepsDashDash: true }), { witness: PRIVILEGED }),
|
|
392
403
|
launcher("pkexec", { whenNoProgram: "shell" }, launcherGrammar({ switches: ["--disable-internal-agent", "--keep-cwd", "--version", "--help"], values: { "--user": LABEL } }), { witness: PRIVILEGED }),
|
|
393
|
-
|
|
394
|
-
|
|
404
|
+
{ key: "su", grammars: implementations(SU, { afterFirstOperand: "options" }, { lacks: SU_LINUX_ONLY, afterFirstOperand: "operands" }), launcher: { shellText: "user", asUser: { home: "target", loginWith: ["-l", "--login"] } }, witness: PRIVILEGED },
|
|
405
|
+
{ key: "runuser", grammars: [launcherGrammar({ ...SU_OPTIONS, values: { ...SU_OPTIONS.values, "-u": LABEL, "--user": LABEL } }, { afterFirstOperand: "options", stepsDashDash: true })], launcher: { shellText: "user", programWith: ["-u", "--user"], asUser: { home: "target", loginWith: ["-l", "--login"] } }, witness: PRIVILEGED },
|
|
395
406
|
launcher("chroot", { requiredOperands: 1, whenNoProgram: "shell" }, undefined, { witness: PRIVILEGED }),
|
|
396
407
|
launcher("setpriv", {}, undefined, { witness: PRIVILEGED }),
|
|
397
408
|
launcher("nohup", {}, grammar({}, BANNERS)),
|
|
@@ -409,7 +420,15 @@ const launchers = [
|
|
|
409
420
|
{ key: "stdbuf", grammars: implementations(grammar({ i: val(LABEL), o: val(LABEL), e: val(LABEL) }, { input: val(LABEL), output: val(LABEL), error: val(LABEL), ...BANNERS }), {}, { lacks: ["--input", "--output", "--error", "--help", "--version"] }), launcher: {}, witness: BINARY },
|
|
410
421
|
launcher("timeout", { requiredOperands: 1, requiredOperandSpelling: DURATION_SPELLING }, grammar({ v: SWITCH, k: val(COUNT), s: val(LABEL) }, { foreground: SWITCH, "preserve-status": SWITCH, verbose: SWITCH, "kill-after": val(COUNT), signal: val(LABEL), ...BANNERS })),
|
|
411
422
|
{ key: "time", grammars: implementations(grammar({ p: SWITCH, a: SWITCH, v: SWITCH, q: SWITCH, f: val(LABEL), o: val(WRITE) }, { portability: SWITCH, append: SWITCH, verbose: SWITCH, quiet: SWITCH, help: SWITCH, version: SWITCH, format: val(LABEL), output: val(WRITE) }, { stepsDashDash: true }), {}, { lacks: ["-v", "-q", "-f", "--portability", "--append", "--verbose", "--quiet", "--help", "--version", "--format", "--output"] }), launcher: {}, witness: BINARY },
|
|
412
|
-
|
|
423
|
+
{ key: "watch", grammars: [
|
|
424
|
+
launcherGrammar({
|
|
425
|
+
switches: ["-b", "-C", "-c", "-e", "-f", "-g", "-h", "-p", "-r", "-t", "-w", "-v", "-x", "--beep", "--color", "--no-color", "--errexit", "--follow", "--chgexit", "--exec", "--precise", "--no-rerun", "--no-title", "--no-wrap", "--help", "--version"],
|
|
426
|
+
banners: ["-h", "-v"],
|
|
427
|
+
values: { "-n": COUNT, "--interval": COUNT, "-q": COUNT, "--equexit": COUNT, "-s": WRITE, "--shotsdir": WRITE },
|
|
428
|
+
optional: { "-d": LABEL, "--differences": LABEL },
|
|
429
|
+
}, { afterFirstOperand: "operands", stepsDashDash: true }),
|
|
430
|
+
launcherGrammar({ switches: ["-d", "-t"], values: { "-n": COUNT } }, { afterFirstOperand: "operands", stepsDashDash: true }),
|
|
431
|
+
], launcher: { shellText: "joined", programWith: ["-x", "--exec"] }, witness: SHELL_TEXT_LAUNCHER },
|
|
413
432
|
launcher("setsid", {}, launcherGrammar({ switches: ["-c", "-f", "-w", "--ctty", "--fork", "--wait", "--help", "--version"] })),
|
|
414
433
|
launcher("flock", { requiredOperands: 1 }, launcherGrammar({
|
|
415
434
|
switches: ["-s", "-x", "-u", "-n", "-o", "-F", "--shared", "--exclusive", "--unlock", "--nonblock", "--nb", "--close", "--no-fork", "--verbose", "--help", "--version"],
|
|
@@ -425,7 +444,7 @@ const launchers = [
|
|
|
425
444
|
values: { "-t": COUNT, "-S": LABEL, "-G": LABEL, "--target": COUNT, "--setuid": LABEL, "--setgid": LABEL },
|
|
426
445
|
optional: { "-r": ROOTDIR, "-w": WORKDIR, "--root": ROOTDIR, "--wd": WORKDIR },
|
|
427
446
|
}), { witness: LINUX_NAMESPACE }),
|
|
428
|
-
launcher("script", { shellText:
|
|
447
|
+
launcher("script", { shellText: "joined" }, launcherGrammar({ shellText: ["-c", "--command"] }), { witness: SHELL_TEXT_LAUNCHER }),
|
|
429
448
|
launcher("numactl", {}, undefined, { witness: LINUX_NAMESPACE }),
|
|
430
449
|
launcher("prlimit", {}, undefined, { witness: LINUX_NAMESPACE }),
|
|
431
450
|
launcher("systemd-run", {}, undefined, { witness: { declared: "hands the program to a service manager" } }),
|
|
@@ -38,6 +38,23 @@ export declare function readDenyHomeLinksOf(env: ExecutionEnv, entries: readonly
|
|
|
38
38
|
readonly linked: Map<string, string[]>;
|
|
39
39
|
readonly whole: boolean;
|
|
40
40
|
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Where the ROOTED rows of a table (`/app/config-lkg.json`, read-deny.ts) lead — the same `linked` answer
|
|
43
|
+
* {@link readDenyHomeLinksOf} gives for home-anchored rows, by the same machine ({@link linkedLocationsOf}): the row's
|
|
44
|
+
* LITERAL HEAD is spelled under the root and resolved the way the read faces mint their keys, and a location that differs
|
|
45
|
+
* from the spelling is where one of the head's segments is a link (`/app` a link to `/data/app`) — the row is placed
|
|
46
|
+
* there too, so a read of the link's target meets it. The laws are `readDenyHomeLinksOf`'s: a head that resolves to its own
|
|
47
|
+
* spelling adds nothing, a head with nothing below it adds nothing, a head the environment does not answer adds
|
|
48
|
+
* nothing and the answer is not WHOLE, a row whose head is empty (`/*.json`) has no location to resolve. A root is no
|
|
49
|
+
* home, so the answer does not depend on the homes: it is read whether or not a home answered.
|
|
50
|
+
*/
|
|
51
|
+
export declare function readDenyRootLinksOf(env: ExecutionEnv, entries: readonly {
|
|
52
|
+
readonly pattern: string;
|
|
53
|
+
readonly caseSensitive?: boolean;
|
|
54
|
+
}[], signal?: AbortSignal, deadline?: number): Promise<{
|
|
55
|
+
readonly linked: Map<string, string[]>;
|
|
56
|
+
readonly whole: boolean;
|
|
57
|
+
}>;
|
|
41
58
|
/**
|
|
42
59
|
* Where the rows that open with the configuration directory's name (`~/.sema/…`, {@link agentConfigDirTail}) lead when
|
|
43
60
|
* the host declares the directory's root (`RunnerDeps.agentConfigDir`) — the same `linked` answer
|
|
@@ -138,7 +155,9 @@ export declare function carryHomeViews<T extends object>(judge: T, source: HomeV
|
|
|
138
155
|
* with no answer, or a declared home that did not answer, places nothing: the rows are held anywhere (`compile([])`)
|
|
139
156
|
* rather than at a spelling that may miss the home's canonical one;
|
|
140
157
|
* · each AUXILIARY home (see the module head) read once under {@link AUXILIARY_HOME_PROBE_MS} — its spellings, its
|
|
141
|
-
* chain, its rows' heads — at the same time; whatever of it did not answer costs that home alone and never `whole
|
|
158
|
+
* chain, its rows' heads — at the same time; whatever of it did not answer costs that home alone and never `whole`;
|
|
159
|
+
* · the ROOTED rows' heads ({@link readDenyRootLinksOf}), read whether or not a home answered: a head that did not answer
|
|
160
|
+
* leaves the placement not WHOLE, and a placement that held the rows anywhere still places them.
|
|
142
161
|
* `compile` is the mount's own compile; the rows' patterns are read off its compile at no home. */
|
|
143
162
|
export declare function placeReadDenyAtHome(env: ExecutionEnv, compile: (homes: readonly string[], linked?: ReadonlyMap<string, readonly string[]>) => ReadDenyMatcher, homes?: readonly string[], signal?: AbortSignal, configRoot?: string): Promise<{
|
|
144
163
|
readonly matcher: PlacedReadDenyMatcher;
|