@sema-agent/core 7.18.0 → 7.20.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 +276 -2
- package/dist/agents/subagent.js +12 -20
- package/dist/agents/teacher.js +2 -5
- package/dist/agents/verify.js +21 -10
- package/dist/brain/anthropic.js +8 -3
- package/dist/brain/open-responses.js +12 -4
- package/dist/brain/openai.js +7 -2
- package/dist/brain/reasoning.d.ts +13 -13
- package/dist/brain/reasoning.js +2 -2
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +6 -1
- package/dist/brain/stream-shared.d.ts +3 -0
- package/dist/brain/stream-shared.js +1 -0
- 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 +74 -26
- package/dist/core/auto-mode.js +80 -11
- package/dist/core/checkpoint-store.d.ts +7 -0
- package/dist/core/checkpoint-store.js +2 -2
- package/dist/core/child-clamp-seats.d.ts +79 -0
- package/dist/core/child-clamp-seats.js +29 -0
- package/dist/core/engine-notice.d.ts +89 -41
- package/dist/core/fs-write-gate-policy.d.ts +17 -9
- package/dist/core/fs-write-gate-policy.js +51 -4
- package/dist/core/gate-bounded-await.d.ts +38 -0
- package/dist/core/gate-bounded-await.js +53 -0
- package/dist/core/gate-fold.js +20 -48
- package/dist/core/gate-lanes.js +17 -9
- package/dist/core/gate-outcome.d.ts +34 -6
- package/dist/core/gate-outcome.js +7 -0
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +18 -0
- package/dist/core/hooks.d.ts +8 -8
- package/dist/core/memory-engine/engine.d.ts +14 -9
- package/dist/core/memory-engine/engine.js +21 -8
- package/dist/core/memory-engine/layout.d.ts +31 -0
- package/dist/core/memory-engine/layout.js +68 -3
- package/dist/core/memory-engine/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/read-root-candidate.d.ts +12 -0
- package/dist/core/read-root-candidate.js +24 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/roles.d.ts +9 -1
- package/dist/core/roles.js +4 -0
- package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
- package/dist/core/runner/advertised-writable-dirs.js +51 -3
- package/dist/core/runner/announce-once-ledger.d.ts +15 -0
- package/dist/core/runner/announce-once-ledger.js +8 -0
- package/dist/core/runner/classifier-ladder.d.ts +154 -11
- package/dist/core/runner/classifier-ladder.js +170 -10
- package/dist/core/runner/contracts.d.ts +46 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +7 -6
- package/dist/core/runner/env-capability-census.d.ts +30 -0
- package/dist/core/runner/env-capability-census.js +38 -0
- package/dist/core/runner/forward-sink.d.ts +32 -0
- package/dist/core/runner/forward-sink.js +41 -0
- package/dist/core/runner/gate-exit.d.ts +4 -0
- package/dist/core/runner/gate-exit.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +0 -9
- package/dist/core/runner/listing-fold.d.ts +57 -0
- package/dist/core/runner/listing-fold.js +75 -0
- package/dist/core/runner/prepare-ask-lane.js +1 -0
- package/dist/core/runner/prepare-caps-and-workflow.js +20 -31
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +8 -10
- package/dist/core/runner/prepare-execution-env.d.ts +1 -1
- package/dist/core/runner/prepare-execution-env.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +8 -0
- package/dist/core/runner/prepare-gate-stations.js +6 -2
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +3 -5
- package/dist/core/runner/prepare-listings.d.ts +3 -0
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory.js +7 -1
- package/dist/core/runner/prepare-policy-chain.js +5 -5
- package/dist/core/runner/prepare-project-context.d.ts +5 -1
- package/dist/core/runner/prepare-project-context.js +3 -2
- package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
- package/dist/core/runner/prepare-run-refs.d.ts +4 -2
- package/dist/core/runner/prepare-run-refs.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -6
- package/dist/core/runner/resume-apply.js +3 -3
- package/dist/core/runner/run-clock-and-content.js +3 -7
- package/dist/core/runner/run-harness-handlers.js +10 -10
- package/dist/core/runner/run-leg.js +31 -16
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -25
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +41 -29
- package/dist/core/runner/synthetic-tools.js +25 -40
- package/dist/core/runner/task-root-canonical.d.ts +45 -0
- package/dist/core/runner/task-root-canonical.js +18 -0
- package/dist/core/runner/turn-attachments.d.ts +160 -53
- package/dist/core/runner/turn-attachments.js +153 -51
- package/dist/core/safe-notify.d.ts +5 -0
- package/dist/core/safe-notify.js +24 -2
- package/dist/core/sensitive-path-policy.d.ts +84 -17
- package/dist/core/sensitive-path-policy.js +37 -19
- package/dist/core/skill-listing-budget.d.ts +106 -0
- package/dist/core/skill-listing-budget.js +57 -0
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +29 -2
- package/dist/core/tool-policy.d.ts +31 -5
- package/dist/core/tool-policy.js +45 -32
- package/dist/core/tool-spec.d.ts +37 -4
- package/dist/core/types.d.ts +1 -1
- package/dist/core/write-protect.d.ts +105 -20
- package/dist/core/write-protect.js +89 -32
- package/dist/index.d.ts +8 -7
- package/dist/index.js +6 -5
- package/dist/orchestration/workflow-primitives.js +10 -20
- package/dist/stores/file/memory-store.d.ts +5 -2
- package/dist/stores/file/memory-store.js +8 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +40 -2
- package/dist/tools/fs/bash-readonly-classifier.js +376 -41
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +19 -28
- package/dist/tools/fs/fs-shared.d.ts +22 -0
- package/dist/tools/fs/fs-shared.js +30 -0
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/fs/read-deny.d.ts +4 -3
- package/dist/tools/fs/safety.d.ts +15 -0
- package/dist/tools/fs/safety.js +7 -0
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +31 -5
- package/wire-changes/core@7.18.0.json +1273 -0
- package/wire-changes/core@7.19.0.json +457 -0
- package/wire-changes/core@7.20.0.json +568 -0
|
@@ -35,7 +35,7 @@ export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
|
|
|
35
35
|
export type { ShellSegmentConnector } from "./bash-lexer.js";
|
|
36
36
|
export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
|
|
37
37
|
/**
|
|
38
|
-
* The CLASSIFY face
|
|
38
|
+
* The verbs the CLASSIFY face admits ON TOP of the shared list ({@link bashClassifyDefaultAllow}) —
|
|
39
39
|
* admissible only where the compound classifier's per-verb judgment runs, and NOT on the shared list's
|
|
40
40
|
* other consumer faces:
|
|
41
41
|
* · `find` — read-only only when no CC `aCy` dangerous predicate token is present (guard arm);
|
|
@@ -54,7 +54,18 @@ export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
|
|
|
54
54
|
* poll-loop face inherits the guard arms for free: its body vetting delegates to the same compound
|
|
55
55
|
* classifier.
|
|
56
56
|
*/
|
|
57
|
-
export declare const
|
|
57
|
+
export declare const BASH_CLASSIFY_EXTRA_ALLOW: readonly string[];
|
|
58
|
+
/** A verb only the classify face admits. */
|
|
59
|
+
export type BashClassifyExtraVerb = (typeof BASH_CLASSIFY_EXTRA_ALLOW)[number];
|
|
60
|
+
/**
|
|
61
|
+
* @contract shell.classify_allow.one_union_point — the classify face's default allow set is the union of
|
|
62
|
+
* two SPELLED-OUT tables ({@link BASH_READONLY_DEFAULT_ALLOW} ∪ {@link BASH_CLASSIFY_EXTRA_ALLOW}) and
|
|
63
|
+
* this function is its only assembly point; no call site composes its own. Both halves stay arrays of
|
|
64
|
+
* string literals so a static reader can enumerate them: a vocabulary derived in place
|
|
65
|
+
* (`[...TABLE, "extra"]`) is invisible to the release's wire-change manifest, so a verb added to or
|
|
66
|
+
* dropped from this security-relevant set would move nothing a downstream reader could see.
|
|
67
|
+
*/
|
|
68
|
+
export declare function bashClassifyDefaultAllow(): readonly string[];
|
|
58
69
|
/**
|
|
59
70
|
* G2 — options for {@link parseLeadingCommandName}. Omitting them keeps the parser byte-identical to
|
|
60
71
|
* what every existing caller (the `bash_readonly` declaration face, the coarse command-name policy, the
|
|
@@ -413,6 +424,21 @@ export interface CompoundReadonlyVerdict {
|
|
|
413
424
|
* path is unchanged.
|
|
414
425
|
*/
|
|
415
426
|
recursiveReadPaths?: readonly string[];
|
|
427
|
+
/**
|
|
428
|
+
* The ONE directory whose addition to the declared roots would make every objection this walk raised go
|
|
429
|
+
* away. It rides the SAME arm as the containment sentence and is dropped wherever that sentence is (a
|
|
430
|
+
* SHAPE refusal takes both — `tail -f <out-of-root>` stays refused after any grant), and on that arm the
|
|
431
|
+
* claim is MEASURED, not argued: the walk runs again with the proposed root in place and the directory is
|
|
432
|
+
* offered only if that walk has no objection left. `..`-traversing spellings are excluded on their own
|
|
433
|
+
* account ({@link outOfRootSpellings}): that is a disagreement between the folded text and the kernel,
|
|
434
|
+
* which no replay of the text can settle. VALUE: the deepest directory PROPERLY
|
|
435
|
+
* containing every {@link outOfRootPaths} entry, folded lexically — zero I/O, so a path that is itself a
|
|
436
|
+
* directory contributes its PARENT (`ls /etc/ssl` ⇒ `/etc`) where the reference implementation narrows
|
|
437
|
+
* with a stat; refused rather than widened when the result would be a family root or would contain the
|
|
438
|
+
* declared home or a declared {@link BashReadonlyRootBoundary.roots} entry. Meaning and spelling
|
|
439
|
+
* contract: {@link import("../../core/tool-spec.js").ReadRootGrantCandidate}.
|
|
440
|
+
*/
|
|
441
|
+
outOfRootGrantCandidate?: string;
|
|
416
442
|
}
|
|
417
443
|
/**
|
|
418
444
|
* The single minting point for the out-of-root-read approval option text, so a gate rendering
|
|
@@ -425,6 +451,18 @@ export interface CompoundReadonlyVerdict {
|
|
|
425
451
|
* uses the host's native separator.
|
|
426
452
|
*/
|
|
427
453
|
export declare function formatOutOfRootReadApprovalOption(directory: string): string;
|
|
454
|
+
/**
|
|
455
|
+
* The deepest directory containing both, or `undefined` when they share none (two path families).
|
|
456
|
+
*
|
|
457
|
+
* LINEAR, and that is the point: both paths are already folded, so the answer is their common prefix cut
|
|
458
|
+
* at a separator. Climbing one directory at a time re-folds the whole remaining path each step — the same
|
|
459
|
+
* answer at quadratic cost, measured at 4.9 s of synchronous work for one 32 KB command naming a
|
|
460
|
+
* 16 000-segment operand, on a walk that runs BEFORE the gate arms its probe deadline. The family-aware
|
|
461
|
+
* comparator keeps the last word: a prefix it cannot confirm contains both paths yields no candidate.
|
|
462
|
+
* Exported for its unit tests — the family confirmation is reachable from a command only through shapes
|
|
463
|
+
* the compound grammar refuses first, so a command-level pin cannot tell a live guard from an absent one.
|
|
464
|
+
*/
|
|
465
|
+
export declare function deepestCommonDir(a: string, b: string): string | undefined;
|
|
428
466
|
/** Resolve one operand against the boundary's base, WITHOUT touching the filesystem. Returns undefined
|
|
429
467
|
* when the token cannot be resolved statically — the fail-closed direction, reported as its own
|
|
430
468
|
* demotion rather than silently treated as in-root.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isAbsoluteForFamily, isAbsolutePathForm, isBlockedDevicePath, isShellRootedSpellingUnmapped, joinForFamily, nativeUncSpellingOf, normalizeAbsPathLexically, win32NamespaceScreen, pathFamilyOf, withinAnyRoot } from "./safety.js";
|
|
1
|
+
import { hasWin32AliasedComponent, isAbsoluteForFamily, isAbsolutePathForm, isBlockedDevicePath, isShellRootedSpellingUnmapped, joinForFamily, nativeUncSpellingOf, normalizeAbsPathLexically, win32NamespaceScreen, pathFamilyOf, withinAnyRoot } from "./safety.js";
|
|
2
2
|
import { lexShellText, literalTildeSpelling, quoteMask, substitutionsInHeredocBody, SUBSTITUTION_PLACEHOLDER, wordsOf, } from "./bash-lexer.js";
|
|
3
3
|
import { ASSIGNMENT_WORD, COMMAND_LAUNCHERS, NOT_AUTO_ALLOWED, SHELL_BLOCK_WORDS, argvReentryUnjudged, isBareControlWord, programBasename, programPositionRefusalSentence, readProgramPosition, } from "./bash-program-position.js";
|
|
4
4
|
export { NOT_AUTO_ALLOWED } from "./bash-program-position.js";
|
|
@@ -8,7 +8,10 @@ export const BASH_READONLY_DEFAULT_ALLOW = [
|
|
|
8
8
|
"cal", "uptime", "id", "groups", "nproc", "locale", "free", "realpath", "readlink",
|
|
9
9
|
"true", "false", "seq", "expr", "type", "sleep", "diff", "cmp", "comm",
|
|
10
10
|
];
|
|
11
|
-
export const
|
|
11
|
+
export const BASH_CLASSIFY_EXTRA_ALLOW = ["find", "sed", "cd"];
|
|
12
|
+
export function bashClassifyDefaultAllow() {
|
|
13
|
+
return [...BASH_READONLY_DEFAULT_ALLOW, ...BASH_CLASSIFY_EXTRA_ALLOW];
|
|
14
|
+
}
|
|
12
15
|
const SHELL_OPERATORS = /[;&|<>$()`\n\r\\]/;
|
|
13
16
|
function hasBareShellOperator(s, quotedOperatorsAreText) {
|
|
14
17
|
if (!quotedOperatorsAreText)
|
|
@@ -135,6 +138,49 @@ export function formatOutOfRootReadApprovalOption(directory) {
|
|
|
135
138
|
const leaf = cut >= 0 ? trimmed.slice(cut + 1) : trimmed;
|
|
136
139
|
return `Yes, allow reading from ${leaf || directory}/ from this project`;
|
|
137
140
|
}
|
|
141
|
+
function parentDirLexically(p) {
|
|
142
|
+
return normalizeAbsPathLexically(`${p}/..`);
|
|
143
|
+
}
|
|
144
|
+
export function deepestCommonDir(a, b) {
|
|
145
|
+
if (a === b)
|
|
146
|
+
return a;
|
|
147
|
+
const shorter = Math.min(a.length, b.length);
|
|
148
|
+
let end = 0;
|
|
149
|
+
let i = 0;
|
|
150
|
+
while (i < shorter && a[i] === b[i]) {
|
|
151
|
+
if (a[i] === "/" || a[i] === "\\")
|
|
152
|
+
end = i;
|
|
153
|
+
i++;
|
|
154
|
+
}
|
|
155
|
+
if (i === shorter && (a[shorter] === "/" || a[shorter] === "\\" || b[shorter] === "/" || b[shorter] === "\\"))
|
|
156
|
+
end = shorter;
|
|
157
|
+
const cut = a.slice(0, end);
|
|
158
|
+
const candidate = cut.includes("/") || cut.includes("\\") ? cut : a.slice(0, end + 1);
|
|
159
|
+
if (candidate.length === 0 || !withinAnyRoot([candidate], a) || !withinAnyRoot([candidate], b))
|
|
160
|
+
return undefined;
|
|
161
|
+
return candidate;
|
|
162
|
+
}
|
|
163
|
+
function outOfRootGrantCandidateOf(paths, boundary) {
|
|
164
|
+
if (paths.length === 0)
|
|
165
|
+
return undefined;
|
|
166
|
+
if (paths.some(hasWin32AliasedComponent))
|
|
167
|
+
return undefined;
|
|
168
|
+
let candidate = parentDirLexically(paths[0]);
|
|
169
|
+
for (const path of paths.slice(1)) {
|
|
170
|
+
if (candidate === undefined)
|
|
171
|
+
return undefined;
|
|
172
|
+
candidate = deepestCommonDir(candidate, parentDirLexically(path));
|
|
173
|
+
}
|
|
174
|
+
if (candidate === undefined)
|
|
175
|
+
return undefined;
|
|
176
|
+
if (parentDirLexically(candidate) === candidate)
|
|
177
|
+
return undefined;
|
|
178
|
+
if (boundary.homeDir !== undefined && withinAnyRoot([candidate], boundary.homeDir))
|
|
179
|
+
return undefined;
|
|
180
|
+
if (boundary.roots.some((root) => withinAnyRoot([candidate], root)))
|
|
181
|
+
return undefined;
|
|
182
|
+
return candidate;
|
|
183
|
+
}
|
|
138
184
|
const NO_PATH_OPERAND_COMMANDS = new Set([
|
|
139
185
|
"pwd", "echo", "whoami", "uname", "which", "tr", "basename", "dirname",
|
|
140
186
|
"cal", "uptime", "id", "groups", "nproc", "locale", "free", "true", "false", "seq", "expr", "type", "sleep",
|
|
@@ -403,18 +449,6 @@ function isGrepPatternFlagToken(tok) {
|
|
|
403
449
|
}
|
|
404
450
|
return /^-[A-Za-z0-9]*[ef]/.test(tok);
|
|
405
451
|
}
|
|
406
|
-
function isGrepPatternPayloadLongOption(tok) {
|
|
407
|
-
if (!tok.includes("="))
|
|
408
|
-
return false;
|
|
409
|
-
const name = longOptionNameOf(tok);
|
|
410
|
-
return name !== undefined && isLongOptionAbbrevOf(name, "regexp");
|
|
411
|
-
}
|
|
412
|
-
function isCutDelimiterPayloadLongOption(tok) {
|
|
413
|
-
if (!tok.includes("="))
|
|
414
|
-
return false;
|
|
415
|
-
const name = longOptionNameOf(tok);
|
|
416
|
-
return name !== undefined && (isLongOptionAbbrevOf(name, "delimiter") || isLongOptionAbbrevOf(name, "output-delimiter"));
|
|
417
|
-
}
|
|
418
452
|
function isGrepClusterFileStdin(tok) {
|
|
419
453
|
if (tok.startsWith("--") || !/^-[A-Za-z0-9]/.test(tok))
|
|
420
454
|
return false;
|
|
@@ -429,6 +463,283 @@ function isGrepClusterFileStdin(tok) {
|
|
|
429
463
|
}
|
|
430
464
|
return false;
|
|
431
465
|
}
|
|
466
|
+
function valueNamesAFile(kind) {
|
|
467
|
+
return kind === "path" || kind === "indirect" || kind === "form" || kind === "header" || kind === "url";
|
|
468
|
+
}
|
|
469
|
+
function widestValueKind(kinds) {
|
|
470
|
+
if (kinds.has("indirect"))
|
|
471
|
+
return "indirect";
|
|
472
|
+
if (kinds.has("path"))
|
|
473
|
+
return "path";
|
|
474
|
+
if (kinds.has("form"))
|
|
475
|
+
return "form";
|
|
476
|
+
if (kinds.has("header"))
|
|
477
|
+
return "header";
|
|
478
|
+
if (kinds.has("url"))
|
|
479
|
+
return "url";
|
|
480
|
+
return [...kinds][0];
|
|
481
|
+
}
|
|
482
|
+
const VERB_OPTION_SLOTS = {
|
|
483
|
+
head: {
|
|
484
|
+
shortValue: { c: "count", n: "count" },
|
|
485
|
+
shortPlain: "qvz",
|
|
486
|
+
longValue: { bytes: "count", lines: "count" },
|
|
487
|
+
longOptionalValue: {},
|
|
488
|
+
longPlain: ["help", "quiet", "silent", "verbose", "version", "zero-terminated"],
|
|
489
|
+
},
|
|
490
|
+
tail: {
|
|
491
|
+
shortValue: { b: "count", c: "count", n: "count", s: "count" },
|
|
492
|
+
shortPlain: "fFqvz",
|
|
493
|
+
longValue: { bytes: "count", lines: "count", "max-unchanged-stats": "count", pid: "count", "sleep-interval": "count" },
|
|
494
|
+
longOptionalValue: { follow: "label" },
|
|
495
|
+
longPlain: ["help", "quiet", "retry", "silent", "verbose", "version", "zero-terminated"],
|
|
496
|
+
},
|
|
497
|
+
cut: {
|
|
498
|
+
shortValue: { b: "count", c: "count", d: "delimiter", f: "count" },
|
|
499
|
+
shortPlain: "nsz",
|
|
500
|
+
longValue: { bytes: "count", characters: "count", delimiter: "delimiter", fields: "count", "output-delimiter": "delimiter" },
|
|
501
|
+
longOptionalValue: {},
|
|
502
|
+
longPlain: ["complement", "help", "only-delimited", "version", "zero-terminated"],
|
|
503
|
+
},
|
|
504
|
+
grep: {
|
|
505
|
+
shortValue: { A: "count", B: "count", C: "count", D: "label", d: "label", e: "pattern", f: "path", m: "count" },
|
|
506
|
+
shortPlain: "0123456789abcEFGhHiIlLnoPqrRsUvVwxyzZ",
|
|
507
|
+
longValue: {
|
|
508
|
+
"after-context": "count",
|
|
509
|
+
"before-context": "count",
|
|
510
|
+
"binary-files": "label",
|
|
511
|
+
devices: "label",
|
|
512
|
+
directories: "label",
|
|
513
|
+
exclude: "name-pattern",
|
|
514
|
+
"exclude-dir": "name-pattern",
|
|
515
|
+
"exclude-from": "path",
|
|
516
|
+
file: "path",
|
|
517
|
+
"group-separator": "label",
|
|
518
|
+
include: "name-pattern",
|
|
519
|
+
label: "label",
|
|
520
|
+
"max-count": "count",
|
|
521
|
+
regexp: "pattern",
|
|
522
|
+
},
|
|
523
|
+
longOptionalValue: { color: "label", colour: "label", context: "count" },
|
|
524
|
+
longPlain: [
|
|
525
|
+
"basic-regexp", "byte-offset", "count", "dereference-recursive", "extended-regexp",
|
|
526
|
+
"files-with-matches", "files-without-match", "fixed-strings", "help", "ignore-case", "initial-tab",
|
|
527
|
+
"invert-match", "line-buffered", "line-number", "line-regexp", "no-filename", "no-group-separator",
|
|
528
|
+
"no-messages", "null", "null-data", "only-matching", "perl-regexp", "quiet", "recursive", "silent",
|
|
529
|
+
"text", "version", "with-filename", "word-regexp",
|
|
530
|
+
],
|
|
531
|
+
},
|
|
532
|
+
diff: {
|
|
533
|
+
shortValue: { C: "count", D: "label", F: "pattern", I: "pattern", L: "label", S: "path", U: "count", W: "count", X: "path", x: "name-pattern" },
|
|
534
|
+
shortPlain: "abBcdeEHilnNpqrstTuvwyZ",
|
|
535
|
+
longValue: {
|
|
536
|
+
exclude: "name-pattern",
|
|
537
|
+
"exclude-from": "path",
|
|
538
|
+
"from-file": "path",
|
|
539
|
+
"horizon-lines": "count",
|
|
540
|
+
ifdef: "label",
|
|
541
|
+
"ignore-matching-lines": "pattern",
|
|
542
|
+
label: "label",
|
|
543
|
+
"show-function-line": "pattern",
|
|
544
|
+
"starting-file": "path",
|
|
545
|
+
tabsize: "count",
|
|
546
|
+
"to-file": "path",
|
|
547
|
+
width: "count",
|
|
548
|
+
},
|
|
549
|
+
longOptionalValue: { color: "label", context: "count", unified: "count" },
|
|
550
|
+
longPlain: [
|
|
551
|
+
"brief", "ed", "expand-tabs", "help", "ignore-all-space", "ignore-blank-lines", "ignore-case",
|
|
552
|
+
"ignore-space-change", "ignore-tab-expansion", "ignore-trailing-space", "initial-tab", "minimal",
|
|
553
|
+
"new-file", "normal", "paginate", "recursive", "report-identical-files", "rcs", "show-c-function",
|
|
554
|
+
"side-by-side", "speed-large-files", "suppress-blank-empty", "text", "version",
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
cmp: {
|
|
558
|
+
shortValue: { i: "count", n: "count" },
|
|
559
|
+
shortPlain: "blsv",
|
|
560
|
+
longValue: { bytes: "count", "ignore-initial": "count" },
|
|
561
|
+
longOptionalValue: {},
|
|
562
|
+
longPlain: ["help", "print-bytes", "quiet", "silent", "verbose", "version"],
|
|
563
|
+
},
|
|
564
|
+
sed: {
|
|
565
|
+
shortValue: { e: "script", f: "path" },
|
|
566
|
+
shortPlain: "nrEszu",
|
|
567
|
+
longValue: { expression: "script", file: "path", "line-length": "count" },
|
|
568
|
+
longOptionalValue: { "in-place": "label" },
|
|
569
|
+
longPlain: [
|
|
570
|
+
"debug", "follow-symlinks", "help", "null-data", "posix", "quiet", "regexp-extended", "sandbox",
|
|
571
|
+
"separate", "silent", "unbuffered", "version", "zero-terminated",
|
|
572
|
+
],
|
|
573
|
+
},
|
|
574
|
+
date: {
|
|
575
|
+
shortValue: { d: "count", f: "path", r: "path", s: "count" },
|
|
576
|
+
shortPlain: "uR",
|
|
577
|
+
longValue: { date: "count", file: "path", reference: "path", "rfc-3339": "count", set: "count" },
|
|
578
|
+
longOptionalValue: { "iso-8601": "label" },
|
|
579
|
+
longPlain: ["debug", "help", "rfc-822", "rfc-email", "universal", "utc", "version"],
|
|
580
|
+
},
|
|
581
|
+
tar: {
|
|
582
|
+
shortValue: { b: "count", C: "path", f: "path", T: "path", X: "path" },
|
|
583
|
+
shortPlain: "acdhjJkmOpPrStuvwWxzZ",
|
|
584
|
+
longValue: {
|
|
585
|
+
"blocking-factor": "count",
|
|
586
|
+
directory: "path",
|
|
587
|
+
exclude: "name-pattern",
|
|
588
|
+
"exclude-from": "path",
|
|
589
|
+
file: "path",
|
|
590
|
+
"files-from": "path",
|
|
591
|
+
"strip-components": "count",
|
|
592
|
+
},
|
|
593
|
+
longOptionalValue: {},
|
|
594
|
+
longPlain: [
|
|
595
|
+
"absolute-names", "append", "create", "dereference", "extract", "get", "help", "interactive",
|
|
596
|
+
"keep-old-files", "list", "preserve-permissions", "sparse", "to-stdout", "touch", "update",
|
|
597
|
+
"verbose", "verify", "version",
|
|
598
|
+
],
|
|
599
|
+
},
|
|
600
|
+
curl: {
|
|
601
|
+
shortValue: {
|
|
602
|
+
A: "label", b: "indirect", C: "count", c: "path", D: "path", d: "indirect", E: "path", e: "label",
|
|
603
|
+
F: "form", H: "header", K: "path", m: "count", o: "path", P: "label", Q: "label", r: "count",
|
|
604
|
+
T: "path", t: "label", U: "label", u: "label", w: "indirect", X: "label", x: "label", Y: "count",
|
|
605
|
+
y: "count", z: "path",
|
|
606
|
+
},
|
|
607
|
+
shortPlain: "#0123456aBfgGhiIjJklLMnNOpqRsSvVZ",
|
|
608
|
+
longValue: {
|
|
609
|
+
cacert: "path",
|
|
610
|
+
capath: "path",
|
|
611
|
+
cert: "path",
|
|
612
|
+
config: "path",
|
|
613
|
+
"connect-timeout": "count",
|
|
614
|
+
cookie: "indirect",
|
|
615
|
+
"cookie-jar": "path",
|
|
616
|
+
data: "indirect",
|
|
617
|
+
"data-ascii": "indirect",
|
|
618
|
+
"data-binary": "indirect",
|
|
619
|
+
"data-raw": "label",
|
|
620
|
+
"data-urlencode": "form",
|
|
621
|
+
"dump-header": "path",
|
|
622
|
+
form: "form",
|
|
623
|
+
"form-string": "label",
|
|
624
|
+
header: "header",
|
|
625
|
+
key: "path",
|
|
626
|
+
"max-time": "count",
|
|
627
|
+
"netrc-file": "path",
|
|
628
|
+
output: "path",
|
|
629
|
+
proxy: "label",
|
|
630
|
+
range: "count",
|
|
631
|
+
referer: "label",
|
|
632
|
+
request: "label",
|
|
633
|
+
resolve: "label",
|
|
634
|
+
trace: "path",
|
|
635
|
+
"time-cond": "path",
|
|
636
|
+
"trace-ascii": "path",
|
|
637
|
+
"upload-file": "path",
|
|
638
|
+
url: "url",
|
|
639
|
+
user: "label",
|
|
640
|
+
"user-agent": "label",
|
|
641
|
+
"write-out": "indirect",
|
|
642
|
+
},
|
|
643
|
+
longOptionalValue: {},
|
|
644
|
+
longPlain: [
|
|
645
|
+
"append", "compressed", "disable", "fail", "get", "globoff", "head", "help", "include", "insecure",
|
|
646
|
+
"junk-session-cookies", "list-only", "location", "manual", "netrc", "next", "no-buffer", "parallel",
|
|
647
|
+
"proxytunnel", "remote-header-name", "remote-name", "remote-time", "show-error", "silent",
|
|
648
|
+
"use-ascii", "verbose", "version",
|
|
649
|
+
],
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
function readVerbOptionSlot(name, tok) {
|
|
653
|
+
const row = VERB_OPTION_SLOTS[name];
|
|
654
|
+
if (row === undefined)
|
|
655
|
+
return undefined;
|
|
656
|
+
if (!tok.startsWith("-") || tok === "-" || tok === "--")
|
|
657
|
+
return { form: "unexplained" };
|
|
658
|
+
if (tok.startsWith("--")) {
|
|
659
|
+
const long = longOptionNameOf(tok);
|
|
660
|
+
if (long === undefined)
|
|
661
|
+
return { form: "unexplained" };
|
|
662
|
+
const eq = tok.indexOf("=");
|
|
663
|
+
const attached = eq >= 0 ? tok.slice(eq + 1) : undefined;
|
|
664
|
+
const withValue = (kind) => attached !== undefined ? { form: "attached", value: attached, kind } : { form: "separated", kind };
|
|
665
|
+
const exact = row.longValue[long];
|
|
666
|
+
if (exact !== undefined)
|
|
667
|
+
return withValue(exact);
|
|
668
|
+
const exactOptional = row.longOptionalValue[long];
|
|
669
|
+
if (exactOptional !== undefined)
|
|
670
|
+
return attached !== undefined ? { form: "attached", value: attached, kind: exactOptional } : { form: "no-value" };
|
|
671
|
+
if (row.longPlain.includes(long))
|
|
672
|
+
return attached === undefined ? { form: "no-value" } : { form: "unexplained" };
|
|
673
|
+
const valueNames = [
|
|
674
|
+
...Object.keys(row.longValue),
|
|
675
|
+
...(attached !== undefined ? Object.keys(row.longOptionalValue) : []),
|
|
676
|
+
].filter((full) => isLongOptionAbbrevOf(long, full));
|
|
677
|
+
const plainNames = [
|
|
678
|
+
...row.longPlain,
|
|
679
|
+
...(attached === undefined ? Object.keys(row.longOptionalValue) : []),
|
|
680
|
+
].filter((full) => isLongOptionAbbrevOf(long, full));
|
|
681
|
+
if (valueNames.length === 0)
|
|
682
|
+
return plainNames.length > 0 && attached === undefined ? { form: "no-value" } : { form: "unexplained" };
|
|
683
|
+
const kinds = new Set(valueNames.map((full) => (row.longValue[full] ?? row.longOptionalValue[full])));
|
|
684
|
+
if (attached !== undefined)
|
|
685
|
+
return { form: "attached", value: attached, kind: widestValueKind(kinds) };
|
|
686
|
+
if (plainNames.length > 0 || kinds.size !== 1)
|
|
687
|
+
return { form: "unexplained" };
|
|
688
|
+
return withValue([...kinds][0]);
|
|
689
|
+
}
|
|
690
|
+
for (let i = 1; i < tok.length; i++) {
|
|
691
|
+
const ch = tok[i];
|
|
692
|
+
const kind = row.shortValue[ch];
|
|
693
|
+
if (kind !== undefined) {
|
|
694
|
+
const rest = tok.slice(i + 1);
|
|
695
|
+
return rest !== "" ? { form: "attached", value: rest, kind } : { form: "separated", kind };
|
|
696
|
+
}
|
|
697
|
+
if (!row.shortPlain.includes(ch))
|
|
698
|
+
return { form: "unexplained" };
|
|
699
|
+
}
|
|
700
|
+
return { form: "no-value" };
|
|
701
|
+
}
|
|
702
|
+
function localFileUrlPath(value) {
|
|
703
|
+
const m = /^file:(?:\/\/[^/?#]*)?(\/[^?#]*)/i.exec(value.trim());
|
|
704
|
+
if (m === null)
|
|
705
|
+
return undefined;
|
|
706
|
+
const raw = m[1];
|
|
707
|
+
try {
|
|
708
|
+
return decodeURIComponent(raw);
|
|
709
|
+
}
|
|
710
|
+
catch {
|
|
711
|
+
return raw;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function formSpecFileNames(value) {
|
|
715
|
+
const out = [];
|
|
716
|
+
const after = (s) => {
|
|
717
|
+
const at = s.search(/[@<]/);
|
|
718
|
+
if (at !== -1 && at < s.length - 1)
|
|
719
|
+
out.push(s.slice(at + 1));
|
|
720
|
+
};
|
|
721
|
+
after(value);
|
|
722
|
+
for (const part of value.split(/[;,]/))
|
|
723
|
+
after(part);
|
|
724
|
+
return [...new Set(out)];
|
|
725
|
+
}
|
|
726
|
+
function slotValueCandidates(value, kind) {
|
|
727
|
+
if (!valueNamesAFile(kind))
|
|
728
|
+
return [];
|
|
729
|
+
if (kind === "url") {
|
|
730
|
+
const local = localFileUrlPath(value);
|
|
731
|
+
return local !== undefined ? [local] : [];
|
|
732
|
+
}
|
|
733
|
+
if (kind === "form")
|
|
734
|
+
return formSpecFileNames(value).map(literalTildeSpelling);
|
|
735
|
+
const named = value.startsWith("@") && value.length > 1 ? literalTildeSpelling(value.slice(1)) : undefined;
|
|
736
|
+
if (kind === "header")
|
|
737
|
+
return named !== undefined ? [value, named] : [];
|
|
738
|
+
const out = [value];
|
|
739
|
+
if (kind === "indirect" && named !== undefined)
|
|
740
|
+
out.push(named);
|
|
741
|
+
return out;
|
|
742
|
+
}
|
|
432
743
|
const SEPARATED_VALUE_OPTIONS = {
|
|
433
744
|
head: { short: "cn", long: ["bytes", "lines"] },
|
|
434
745
|
tail: { short: "cns", long: ["bytes", "lines", "sleep-interval", "pid", "max-unchanged-stats"] },
|
|
@@ -1205,17 +1516,6 @@ function isGrepFileStdinLongOption(tok) {
|
|
|
1205
1516
|
return name !== undefined && isLongOptionAbbrevOf(name, "file");
|
|
1206
1517
|
}
|
|
1207
1518
|
const CLUSTER_OPTION_CHAR = /[A-Za-z0-9]/;
|
|
1208
|
-
function grepClusterValueOwner(tok) {
|
|
1209
|
-
if (!tok.startsWith("-") || tok.startsWith("--") || !CLUSTER_OPTION_CHAR.test(tok[1] ?? ""))
|
|
1210
|
-
return undefined;
|
|
1211
|
-
for (const ch of tok.slice(1)) {
|
|
1212
|
-
if (ch === "e" || ch === "f")
|
|
1213
|
-
return ch;
|
|
1214
|
-
if (!CLUSTER_OPTION_CHAR.test(ch))
|
|
1215
|
-
return undefined;
|
|
1216
|
-
}
|
|
1217
|
-
return undefined;
|
|
1218
|
-
}
|
|
1219
1519
|
function attachedOptionPayloads(tok) {
|
|
1220
1520
|
const literal = literalTildeSpelling;
|
|
1221
1521
|
const out = [];
|
|
@@ -1239,6 +1539,7 @@ function attachedOptionPayloads(tok) {
|
|
|
1239
1539
|
}
|
|
1240
1540
|
function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
1241
1541
|
const name = tokens.folded[0];
|
|
1542
|
+
const slotVerb = tokens.slotVerb ?? name;
|
|
1242
1543
|
if (NO_PATH_OPERAND_COMMANDS.has(name))
|
|
1243
1544
|
return [];
|
|
1244
1545
|
const args = tokens.folded.slice(1);
|
|
@@ -1285,6 +1586,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1285
1586
|
const eoo = args.indexOf("--");
|
|
1286
1587
|
const patternSuppliedByFlag = name === "grep" && (eoo === -1 ? args : args.slice(0, eoo)).some(isGrepPatternFlagToken);
|
|
1287
1588
|
let sawOperand = false;
|
|
1589
|
+
const argvOperandsStartAt = tokens.slotVerb !== undefined ? 1 : 0;
|
|
1590
|
+
let sawArgvOperand = false;
|
|
1288
1591
|
let endOfOptions = false;
|
|
1289
1592
|
for (let k = 0; k < args.length; k++) {
|
|
1290
1593
|
const t = args[k];
|
|
@@ -1295,15 +1598,35 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1295
1598
|
endOfOptions = true;
|
|
1296
1599
|
continue;
|
|
1297
1600
|
}
|
|
1298
|
-
if (name === "cut" && (t === "-d" || t === "--delimiter" || t === "--output-delimiter")) {
|
|
1299
|
-
k++;
|
|
1300
|
-
continue;
|
|
1301
|
-
}
|
|
1302
|
-
if (name === "cut" && (/^-d./.test(t) || isCutDelimiterPayloadLongOption(t)))
|
|
1303
|
-
continue;
|
|
1304
|
-
if (name === "grep" && (grepClusterValueOwner(t) === "e" || isGrepPatternPayloadLongOption(t)))
|
|
1305
|
-
continue;
|
|
1306
1601
|
if (t.startsWith("-") && t !== "-") {
|
|
1602
|
+
const slot = readVerbOptionSlot(slotVerb, t);
|
|
1603
|
+
if (sawArgvOperand) {
|
|
1604
|
+
judgeOperand(t, argGlobs(k));
|
|
1605
|
+
if (slot !== undefined && slot.form === "separated") {
|
|
1606
|
+
const ahead = args[k + 1];
|
|
1607
|
+
if (ahead !== undefined && !expandingArgs.has(k + 1)) {
|
|
1608
|
+
for (const c of slotValueCandidates(ahead, slot.kind))
|
|
1609
|
+
judgeOperand(c, argGlobs(k + 1));
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (slot !== undefined && slot.form !== "unexplained" && !(slot.form === "separated" && sawArgvOperand)) {
|
|
1614
|
+
if (slot.form === "attached") {
|
|
1615
|
+
for (const c of slotValueCandidates(literalTildeSpelling(slot.value), slot.kind))
|
|
1616
|
+
judgeOperand(c, argGlobs(k));
|
|
1617
|
+
}
|
|
1618
|
+
else if (slot.form === "separated") {
|
|
1619
|
+
const value = args[++k];
|
|
1620
|
+
if (value !== undefined && !expandingArgs.has(k)) {
|
|
1621
|
+
const named = slotValueCandidates(value, slot.kind);
|
|
1622
|
+
for (const c of named)
|
|
1623
|
+
judgeOperand(c, argGlobs(k));
|
|
1624
|
+
if (named.length > 0)
|
|
1625
|
+
positionalOperands.push(value);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1307
1630
|
for (const payload of attachedOptionPayloads(t))
|
|
1308
1631
|
judgeOperand(payload, argGlobs(k));
|
|
1309
1632
|
continue;
|
|
@@ -1311,6 +1634,8 @@ function collectSegmentBoundaryFindings(tokens, boundary) {
|
|
|
1311
1634
|
}
|
|
1312
1635
|
const isGrepPatternSlot = name === "grep" && !patternSuppliedByFlag && !sawOperand;
|
|
1313
1636
|
sawOperand = true;
|
|
1637
|
+
if (k >= argvOperandsStartAt)
|
|
1638
|
+
sawArgvOperand = true;
|
|
1314
1639
|
if (t === "-") {
|
|
1315
1640
|
if (isGrepPatternSlot)
|
|
1316
1641
|
continue;
|
|
@@ -1541,7 +1866,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary, opts)
|
|
|
1541
1866
|
const unlisted = tokenizeSegment(segments[si]);
|
|
1542
1867
|
if (unlisted.folded.length === 0)
|
|
1543
1868
|
continue;
|
|
1544
|
-
foldedSegments.push({ folded: [LAUNCHER_SCAN_HEAD, ...unlisted.folded], raw: [LAUNCHER_SCAN_HEAD, ...unlisted.raw] });
|
|
1869
|
+
foldedSegments.push({ folded: [LAUNCHER_SCAN_HEAD, ...unlisted.folded], raw: [LAUNCHER_SCAN_HEAD, ...unlisted.raw], slotVerb: programBasename(unlisted.folded[0]) });
|
|
1545
1870
|
pushReach();
|
|
1546
1871
|
continue;
|
|
1547
1872
|
}
|
|
@@ -1632,10 +1957,10 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary, opts)
|
|
|
1632
1957
|
shapeRefusal ??= `multiple directory changes in one command require approval for clarity ${NOT_AUTO_ALLOWED}`;
|
|
1633
1958
|
}
|
|
1634
1959
|
if (boundary !== undefined) {
|
|
1635
|
-
const walk = evaluateReadBoundary(foldedSegments, boundary, foldedReach);
|
|
1960
|
+
const walk = evaluateReadBoundary(foldedSegments, boundary, true, foldedReach);
|
|
1636
1961
|
if (shapeRefusal === undefined)
|
|
1637
1962
|
return walk;
|
|
1638
|
-
const { reason: _walkSentence, ...structure } = walk;
|
|
1963
|
+
const { reason: _walkSentence, outOfRootGrantCandidate: _walkGrantCandidate, ...structure } = walk;
|
|
1639
1964
|
return { ...structure, reason: shapeRefusal };
|
|
1640
1965
|
}
|
|
1641
1966
|
if (shapeRefusal !== undefined)
|
|
@@ -1646,7 +1971,7 @@ export function classifyCompoundReadonlyDetailed(command, allow, boundary, opts)
|
|
|
1646
1971
|
return {};
|
|
1647
1972
|
}
|
|
1648
1973
|
const BASE_CANDIDATE_MAX = 256;
|
|
1649
|
-
function evaluateReadBoundary(foldedSegments, boundary, reach) {
|
|
1974
|
+
function evaluateReadBoundary(foldedSegments, boundary, mintGrantCandidate, reach) {
|
|
1650
1975
|
const seen = { outside: new Set(), inside: new Set(), undecided: new Set(), recursive: new Set(), unresolved: new Set(), checkedSpelled: new Set(), undecidedSpelled: new Set(), outsideSpelled: new Set() };
|
|
1651
1976
|
const outside = [];
|
|
1652
1977
|
const inside = [];
|
|
@@ -1691,7 +2016,7 @@ function evaluateReadBoundary(foldedSegments, boundary, reach) {
|
|
|
1691
2016
|
if (si > 0 && reach?.[si]?.andJoined !== true)
|
|
1692
2017
|
andRunStart = reach?.[si]?.orBroken === true ? si + 1 : si;
|
|
1693
2018
|
const reachable = candidates.filter((c) => c.skippedCd < andRunStart);
|
|
1694
|
-
const row = findingsRowFor(JSON.stringify([toks.folded, toks.raw]));
|
|
2019
|
+
const row = findingsRowFor(JSON.stringify([toks.folded, toks.raw, toks.slotVerb]));
|
|
1695
2020
|
for (const { base: currentBase } of reachable) {
|
|
1696
2021
|
for (const finding of findingsFor(row, toks, currentBase)) {
|
|
1697
2022
|
if (finding.kind === "unresolvable") {
|
|
@@ -1804,10 +2129,20 @@ function evaluateReadBoundary(foldedSegments, boundary, reach) {
|
|
|
1804
2129
|
return { ...checkedField, ...undecidedField };
|
|
1805
2130
|
const paths = outside.map((o) => `"${o.path}"`).join(", ");
|
|
1806
2131
|
const allowed = boundary.roots.length > 0 ? boundary.roots.join(", ") : "(none)";
|
|
2132
|
+
const grantCandidate = (() => {
|
|
2133
|
+
if (!mintGrantCandidate || outOfRootSpellings.length > 0)
|
|
2134
|
+
return undefined;
|
|
2135
|
+
const proposed = outOfRootGrantCandidateOf(outside.map((o) => o.path), boundary);
|
|
2136
|
+
if (proposed === undefined)
|
|
2137
|
+
return undefined;
|
|
2138
|
+
const replay = evaluateReadBoundary(foldedSegments, { ...boundary, roots: [...boundary.roots, proposed] }, false, reach);
|
|
2139
|
+
return replay.reason === undefined && (replay.undecidedPaths ?? []).length === 0 ? proposed : undefined;
|
|
2140
|
+
})();
|
|
1807
2141
|
return {
|
|
1808
2142
|
reason: `"${outside[0].command}" reads ${paths}, outside the allowed directories for this session: ${allowed} ${NOT_AUTO_ALLOWED}`,
|
|
1809
2143
|
outOfRootRead: true,
|
|
1810
2144
|
outOfRootPaths: outside.map((o) => o.path),
|
|
2145
|
+
...(grantCandidate !== undefined ? { outOfRootGrantCandidate: grantCandidate } : {}),
|
|
1811
2146
|
...outsideField,
|
|
1812
2147
|
...checkedField,
|
|
1813
2148
|
...undecidedField,
|
|
@@ -1825,7 +2160,7 @@ export function classifySimpleCommandReadBoundary(command, boundary) {
|
|
|
1825
2160
|
if (familyRefusal !== undefined)
|
|
1826
2161
|
return { reason: familyRefusal, refusedWhole: true };
|
|
1827
2162
|
}
|
|
1828
|
-
return evaluateReadBoundary([toks], boundary);
|
|
2163
|
+
return evaluateReadBoundary([toks], boundary, true);
|
|
1829
2164
|
}
|
|
1830
2165
|
const NOT_GATED = { gated: false, paths: [] };
|
|
1831
2166
|
const UNREADABLE_WHITELIST_WORD = /[$`()*?[\]{}~]/;
|
|
@@ -1876,7 +2211,7 @@ export function classifyOutOfRootReadGate(command, allow, boundary) {
|
|
|
1876
2211
|
if (UNMODELLED_BASE_MOVERS.has(name))
|
|
1877
2212
|
baseMoved = true;
|
|
1878
2213
|
if (!allow.has(name) && name !== "cd") {
|
|
1879
|
-
pushReader({ folded: [LAUNCHER_SCAN_HEAD, ...toks.folded.slice(i)], raw: [LAUNCHER_SCAN_HEAD, ...toks.raw.slice(i)] });
|
|
2214
|
+
pushReader({ folded: [LAUNCHER_SCAN_HEAD, ...toks.folded.slice(i)], raw: [LAUNCHER_SCAN_HEAD, ...toks.raw.slice(i)], slotVerb: name });
|
|
1880
2215
|
continue;
|
|
1881
2216
|
}
|
|
1882
2217
|
pushReader(i === 0 && name === written ? toks : { folded: [name, ...toks.folded.slice(i + 1)], raw: [toks.raw[i] ?? name, ...toks.raw.slice(i + 1)] });
|
|
@@ -1885,7 +2220,7 @@ export function classifyOutOfRootReadGate(command, allow, boundary) {
|
|
|
1885
2220
|
return NOT_GATED;
|
|
1886
2221
|
if (baseMoved)
|
|
1887
2222
|
return { gated: true, paths: [] };
|
|
1888
|
-
const verdict = evaluateReadBoundary(readers, containmentOnly, readerReach);
|
|
2223
|
+
const verdict = evaluateReadBoundary(readers, containmentOnly, false, readerReach);
|
|
1889
2224
|
return verdict.outOfRootRead === true ? { gated: true, paths: verdict.outOfRootPaths ?? [] } : NOT_GATED;
|
|
1890
2225
|
};
|
|
1891
2226
|
const direct = judge(command);
|
|
@@ -100,7 +100,7 @@ export declare function bashReversibilityProbe(allow?: readonly string[], bounda
|
|
|
100
100
|
* add a mandate to an ask, never originate one). The poll-loop face's structured verdict
|
|
101
101
|
* ({@link classifyBoundedReadonlyPollLoopDetailed}) is read the same three ways as the compound face's.
|
|
102
102
|
*
|
|
103
|
-
* The reader set is the engine's classify set ({@link
|
|
103
|
+
* The reader set is the engine's classify set ({@link bashClassifyDefaultAllow}) — here it is NOT an
|
|
104
104
|
* auto-allow list but the set of programs whose operands the faces read; a wider set means more reads are
|
|
105
105
|
* judged, never that more runs are vouched.
|
|
106
106
|
*
|