@sema-agent/core 7.24.2 → 7.24.3
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 +28 -0
- package/dist/core/mcp-failure.d.ts +73 -0
- package/dist/core/mcp-failure.js +21 -0
- package/dist/core/mcp.d.ts +18 -1
- package/dist/core/mcp.js +33 -12
- package/dist/core/runner/prepare-wiring-manifest.d.ts +3 -2
- package/dist/core/runner/prepare-wiring-manifest.js +3 -1
- package/dist/core/wiring-manifest.d.ts +20 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/prompt-assembly/packs/sema-default.js +15 -0
- package/dist/prompts/default.d.ts +1 -1
- package/dist/prompts/default.js +4 -4
- package/dist/tools/fs/bash-program-position.d.ts +5 -0
- package/dist/tools/fs/bash-program-position.js +1 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +476 -1
- package/dist/tools/fs/bash-readonly-classifier.js +98 -34
- package/dist/tools/fs/fs-bash.js +2 -325
- package/dist/tools/fs/shell-read-establish.d.ts +127 -0
- package/dist/tools/fs/shell-read-establish.js +326 -0
- package/dist/tools/fs/shell-read-plan.d.ts +113 -0
- package/dist/tools/fs/shell-read-plan.js +700 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +7 -1
- package/wire-changes/core@7.24.3.json +244 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ShellSegmentConnector } from "./bash-lexer.js";
|
|
1
|
+
import { type SegmentTokens, type ShellSegmentConnector } from "./bash-lexer.js";
|
|
2
2
|
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
3
3
|
/**
|
|
4
4
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
@@ -480,6 +480,30 @@ export declare function formatOutOfRootReadApprovalOption(directory: string): st
|
|
|
480
480
|
* the compound grammar refuses first, so a command-level pin cannot tell a live guard from an absent one.
|
|
481
481
|
*/
|
|
482
482
|
export declare function deepestCommonDir(a: string, b: string): string | undefined;
|
|
483
|
+
/** Programs whose operands are NEVER filesystem paths, so no operand of theirs is checked:
|
|
484
|
+
* `echo`'s words are literal text, `which`'s are PATH lookups, `tr`'s are character sets, and
|
|
485
|
+
* `basename`/`dirname` do pure string surgery on a path without ever opening it. Everything else —
|
|
486
|
+
* including a deployment-widened name this file does not know — goes through the path-shaped
|
|
487
|
+
* operand scan below, which is safe for an unknown command because a bare word can only ever name
|
|
488
|
+
* something under the working directory. */
|
|
489
|
+
export declare const NO_PATH_OPERAND_COMMANDS: ReadonlySet<string>;
|
|
490
|
+
/** True for a token that can denote a filesystem location OUTSIDE the working directory. A bare word
|
|
491
|
+
* (`foo`, `a.txt`, a grep pattern, an awk program) cannot: with no separator and no `..` it always
|
|
492
|
+
* resolves under the cwd, which the caller's cwd governance already covers.
|
|
493
|
+
*
|
|
494
|
+
* The one spelling that would break that argument is a win-form absolute written with
|
|
495
|
+
* backslashes only (`\\host\share\secret`, `C:\other\secret`): no `/`, so not path-shaped, yet it names
|
|
496
|
+
* something the cwd does not contain. Two separate facts, once written as one:
|
|
497
|
+
* · on the COMPOUND faces it is indeed unreachable — {@link SHELL_SEGMENT_QUOTE_BLIND_REJECT} refuses the
|
|
498
|
+
* whole command as soon as it contains a backslash, wherever it stands, so no such token reaches this scan;
|
|
499
|
+
* · on {@link classifySimpleCommandReadBoundary} — one command, no compound grammar, and therefore no
|
|
500
|
+
* such gate — it DOES reach it. Measured on a win32-rooted boundary, before the lane split below: the operand
|
|
501
|
+
* resolved to nothing, was dropped, and the verdict was empty, i.e. a read outside every root
|
|
502
|
+
* AUTO-ALLOWED on the one face whose doctrine is hard refusal in exactly that direction.
|
|
503
|
+
* The second fact is why the lane split in
|
|
504
|
+
* {@link collectSegmentBoundaryFindings} tests {@link isAbsolutePathForm} BESIDE this predicate,
|
|
505
|
+
* which is what the option-payload arm always did; the two now read one rule rather than two. */
|
|
506
|
+
export declare function isPathShapedToken(tok: string): boolean;
|
|
483
507
|
/** Resolve one operand against the boundary's base, WITHOUT touching the filesystem. Returns undefined
|
|
484
508
|
* when the token cannot be resolved statically — the fail-closed direction, reported as its own
|
|
485
509
|
* demotion rather than silently treated as in-root.
|
|
@@ -519,6 +543,13 @@ export interface OperandSpellings {
|
|
|
519
543
|
/** Resolve one operand into {@link OperandSpellings}, or `undefined` on the same unresolvable operands
|
|
520
544
|
* {@link resolveOperandLexically} reports (it is this function's folded half). */
|
|
521
545
|
export declare function resolveOperandSpellings(base: string | undefined, operand: string, homeDir: string | undefined): OperandSpellings | undefined;
|
|
546
|
+
/** Programs that run their argument AS SHELL TEXT (the sh family, `eval`, `source`, `.`): what they read is decided
|
|
547
|
+
* by text nobody here parsed. Spelled as a pattern, not a name list — these are program names, never tool names.
|
|
548
|
+
* The prefix alternation is the SPELLINGS of the shell, not a curated subset of them: the Almquist shell and
|
|
549
|
+
* its descendants (`ash`, `mksh`, `yash`, `pdksh`) and the C shell family (`csh`, `tcsh`) all run `-c` text
|
|
550
|
+
* exactly as `sh` does, and a deployment that meets one of them meets the whole member. GNU `parallel` is a member
|
|
551
|
+
* for the reason it is not a launcher: it hands a BUILT command line to a shell. */
|
|
552
|
+
export declare const SHELL_REENTRY_PROGRAM_RE: RegExp;
|
|
522
553
|
/** Whether a command the grammar gate REFUSED WHOLE may READ a file the boundary never judged. The answer is the
|
|
523
554
|
* complement of a WHITELIST: the command is exempt only when every part of it is a shape the scan fully understands
|
|
524
555
|
* and none of those shapes reads a file outside the walk's reach — everything else is read evidence, so a token the
|
|
@@ -540,6 +571,446 @@ export interface RefusedCommandScan {
|
|
|
540
571
|
readonly mayRead: boolean;
|
|
541
572
|
readonly declined: boolean;
|
|
542
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* Does this RAW token carry a metacharacter the shell EXPANDS, unquoted?
|
|
576
|
+
*
|
|
577
|
+
* The boundary scan below judges the token text as if it were the argv word the program receives. Quote
|
|
578
|
+
* removal (folded above) is the only rewriting step it models — but brace expansion runs BEFORE tilde
|
|
579
|
+
* expansion and before the word ever reaches the program, so `cat {<abs>,}` hands the program an absolute
|
|
580
|
+
* path this scan never saw, and `ls {~,}` hands it a home directory the `~` arm of
|
|
581
|
+
* {@link resolveOperandLexically} never got the chance to refuse. Both were real out-of-root reads.
|
|
582
|
+
*
|
|
583
|
+
* Rather than emulate expansion (the classifier resolves paths lexically and has no filesystem, so it
|
|
584
|
+
* cannot know what a brace list or a glob expands TO), an operand carrying one of these unquoted is
|
|
585
|
+
* declared unresolvable — the same verdict `~user` already gets, and the same direction the whole file
|
|
586
|
+
* errs in. Quoting is honoured exactly as bash does it: nothing expands inside single quotes, while `$`
|
|
587
|
+
* and a backtick still expand inside double quotes (braces do not).
|
|
588
|
+
*
|
|
589
|
+
* Deliberately NOT in the set: the GLOB characters `* ? [ ]`. A glob expands only to names that already
|
|
590
|
+
* exist in the directory the pattern itself names, and a path component can never match a `/` — so a glob
|
|
591
|
+
* cannot manufacture a traversal that is not already textually present (`cat ../*` still carries `../`
|
|
592
|
+
* and is judged on it), while listing them would demote the everyday `ls *.txt`. `$`, backtick and the
|
|
593
|
+
* parentheses are redundant against a compliant caller (both faces reject them upstream, whole-string;
|
|
594
|
+
* a home-directory variable word never arrives as `$` — {@link foldHomeVariableSpelling} substituted the
|
|
595
|
+
* declared value before segmentation) and are kept here only so this scan is self-standing.
|
|
596
|
+
*/
|
|
597
|
+
export declare function hasUnquotedExpansionMetachar(rawToken: string): boolean;
|
|
598
|
+
/**
|
|
599
|
+
* The same quote walk for PATHNAME expansion (`*`, `?`, `[`). Split from
|
|
600
|
+
* {@link hasUnquotedExpansionMetachar} because the two have different consequences, not different
|
|
601
|
+
* detection: a brace/variable/command expansion can produce ANY word (including a fresh absolute path or
|
|
602
|
+
* an extra operand), so it is unresolvable; a glob only ever names entries of a directory that is already
|
|
603
|
+
* on the command line, so a caller holding a filesystem can decide it — see
|
|
604
|
+
* {@link CompoundReadonlyVerdict.undecidedPaths}. Both quoting forms suppress globbing in bash, so unlike
|
|
605
|
+
* the expansion walk there is no `"`-open arm here.
|
|
606
|
+
*/
|
|
607
|
+
export declare function hasUnquotedGlobMetachar(rawToken: string): boolean;
|
|
608
|
+
/**
|
|
609
|
+
* Does this token supply the verb's FIRST-OPERAND slot, so that the first operand is a FILE rather than the
|
|
610
|
+
* slot ({@link VerbOptionSlots.firstOperand})? One reader, every verb with such a row.
|
|
611
|
+
*
|
|
612
|
+
* Read over the whole leading letter RUN, not a prefix: GNU clusters short options (`-if` is `-i -f`) and a
|
|
613
|
+
* prefix reading left the slot to swallow — and therefore skip — a real file operand. Digits belong to the run
|
|
614
|
+
* (grep's obsolete numeric context spec: `-2ebeta` is `-C2 -e beta`), and a long option is matched as an
|
|
615
|
+
* ABBREVIATION of the declared name (`grep --reg=X <outside>` is `--regexp`). Over-triggering is harmless in
|
|
616
|
+
* the direction that matters: a token wrongly counted here makes the first operand a CHECKED path candidate.
|
|
617
|
+
*/
|
|
618
|
+
export declare function suppliesFirstOperandSlot(slot: NonNullable<VerbOptionSlots["firstOperand"]>, tok: string): boolean;
|
|
619
|
+
/**
|
|
620
|
+
* WHAT ONE OPTION'S VALUE IS, per verb — the one declaration every reader of an option token shares.
|
|
621
|
+
*
|
|
622
|
+
* The question asked of a value is always "can this name a file?", and the answer is a property of the
|
|
623
|
+
* OPTION and its own grammar, never of the value's shape: `sed -e 's/a/b/'` and `sed -f s.sed` differ in
|
|
624
|
+
* nothing a shape test can see. The kinds spell that answer out per slot so a row is auditable letter by
|
|
625
|
+
* letter, and {@link slotValueCandidates} is where every consumer gets it — a kind that cannot name a
|
|
626
|
+
* file yields nothing there, which is the same answer as "this value named none".
|
|
627
|
+
*/
|
|
628
|
+
export type OptionValueKind = "path" | "indirect" | "pattern" | "script" | "count" | "delimiter" | "name-pattern" | "form" | "header" | "url" | "label";
|
|
629
|
+
/**
|
|
630
|
+
* One verb's option grammar: which short letters and long names take a VALUE, and what that value is.
|
|
631
|
+
*
|
|
632
|
+
* FAIL-SAFE BY CONSTRUCTION, which is what makes a PARTIAL row sound: the reader must account for every
|
|
633
|
+
* option character it walks, so the first character a row does not declare ends the reading as
|
|
634
|
+
* `unexplained` and hands the token back to the verb-blind payload scan ({@link attachedOptionPayloads})
|
|
635
|
+
* — today's over-approximation, unchanged. A row therefore only ever makes an answer MORE precise where
|
|
636
|
+
* it recognizes the option; omitting one costs precision, never a judgement.
|
|
637
|
+
*
|
|
638
|
+
* THREE ARITIES, because `getopt_long` has three. An option that takes no value, one that requires a
|
|
639
|
+
* value (attached or the next token), and one whose value is OPTIONAL — and the third is not the second
|
|
640
|
+
* with a caveat: `getopt_long` gives an optional value NO separated spelling, so `diff --unified a b`
|
|
641
|
+
* hands diff two FILES while `diff --unified=3 a b` hands it a width. A row that files an optional-value
|
|
642
|
+
* option under {@link VerbOptionSlots.longValue} therefore eats `a`, and the file operand it swallowed
|
|
643
|
+
* is judged by nobody — the one direction this table may never move. They go in
|
|
644
|
+
* {@link VerbOptionSlots.longOptionalValue}, which reads the attached spelling and consumes nothing in
|
|
645
|
+
* the separated one. A short letter whose value is optional (`sed -i[SUFFIX]`) is left out of BOTH short
|
|
646
|
+
* maps for the same reason: an optional value can only ever be attached, and the blind scan is the safe
|
|
647
|
+
* reading of a token this row will not explain.
|
|
648
|
+
*
|
|
649
|
+
* WHAT A ROW MAY NOT GET WRONG, stated as the rule for adding one: declaring a FILE-valued option
|
|
650
|
+
* as something else hides a read, and so does declaring an OPTIONAL-value option as a required one —
|
|
651
|
+
* the value it eats in the separated spelling is a file operand. Declaring a value-taking option as
|
|
652
|
+
* plain is safe (the reading ends unexplained at its value), and declaring the non-file slots is what
|
|
653
|
+
* removes the phantom candidates a blind split invents — admitting one more option character moves the
|
|
654
|
+
* split right, and a split that lands on a `/` manufactures an absolute path the command never names.
|
|
655
|
+
*
|
|
656
|
+
* AND A ROW IS ABOUT A NAME, WHICH TWO IMPLEMENTATIONS MAY NOT SPELL ALIKE. `sed -l` takes a required
|
|
657
|
+
* line length in one widely deployed sed and NO value in another; `grep --context` is optional in one
|
|
658
|
+
* and required in another; and the engine runs whichever the host ships. A disputed arity is resolved
|
|
659
|
+
* towards the reading that CONSUMES NOTHING, because only consuming can swallow a file operand: a
|
|
660
|
+
* disputed LONG name is filed as optional-value (right under one reading, merely imprecise under the
|
|
661
|
+
* other), and a disputed SHORT letter is left out of both short maps, since the short maps have no way
|
|
662
|
+
* to say "attached only". Where implementations disagree about a value's KIND rather than its arity,
|
|
663
|
+
* the file-naming reading is the one to declare — over-approximating a value costs an ask and
|
|
664
|
+
* under-approximating one costs the read. Every row here is checked against the binaries this
|
|
665
|
+
* repository's own tools run on; a row added from a manual page alone is a guess.
|
|
666
|
+
*
|
|
667
|
+
* AND A ROW APPLIES WHILE THE COMMAND IS STILL IN ITS OPTION SECTION. POSIX ends option parsing at the
|
|
668
|
+
* first operand, GNU permutes, and a transfer program parses its own options anywhere — so once the
|
|
669
|
+
* operands have begun, whether a dash-leading token is an option at all is implementation-dependent and
|
|
670
|
+
* BOTH readings are taken: the token is not consumed (it is judged as the operand it may be) and the
|
|
671
|
+
* slot's candidates are judged too (it may name a file). Adding a candidate is always safe; consuming
|
|
672
|
+
* is what hides one.
|
|
673
|
+
*
|
|
674
|
+
* @contract shell.read_boundary.option_value_slots — where this table declares a verb's option, the
|
|
675
|
+
* read boundary reads that option's value from the DECLARED slot and nowhere else: a slot that can name
|
|
676
|
+
* a file yields exactly its value as a path candidate (an `indirect` slot also yields what the value
|
|
677
|
+
* names past its `@`, a `header` slot only for a whole-value `@<file>`, and a `url` slot the local path
|
|
678
|
+
* a `file:` URL names), and a slot that cannot yields none — the attached and separated spellings of one
|
|
679
|
+
* option read the same row and can never disagree, except where the row declares the value OPTIONAL,
|
|
680
|
+
* which has no separated spelling at all. Past the command's first operand the separated spelling
|
|
681
|
+
* consumes nothing and its value is judged BOTH as that value and as an operand, because which of the
|
|
682
|
+
* two it is, is implementation-dependent. Where it declares nothing — no row for the verb, an
|
|
683
|
+
* option character the row does not account for, a long-option prefix whose reachable names leave this
|
|
684
|
+
* spelling's value unsettled —
|
|
685
|
+
* the verb-blind payload scan judges the token exactly as it did before, so an absent or partial row
|
|
686
|
+
* costs precision and never a judgement. A row is DATA about a PROGRAM and not about the read-only
|
|
687
|
+
* allowlist: it says what that program does with its own argument, never that the program may be read
|
|
688
|
+
* from, and it grants no per-verb arm to a program a deployment has not listed.
|
|
689
|
+
*/
|
|
690
|
+
export interface VerbOptionSlots {
|
|
691
|
+
/** Short letters whose value is the rest of the token, or the NEXT token when the letter ends it. */
|
|
692
|
+
readonly shortValue: Readonly<Record<string, OptionValueKind>>;
|
|
693
|
+
/** Short letters taking no value. */
|
|
694
|
+
readonly shortPlain: string;
|
|
695
|
+
/** Long option FULL names whose value is `=<value>` or the next token. */
|
|
696
|
+
readonly longValue: Readonly<Record<string, OptionValueKind>>;
|
|
697
|
+
/** Long option FULL names whose value exists ONLY as `=<value>`: `getopt_long`'s `optional_argument`,
|
|
698
|
+
* which never reaches for the next token. Reading one of these as {@link longValue} would consume the
|
|
699
|
+
* operand that follows the bare spelling, so the operand goes unjudged — see the THREE ARITIES note. */
|
|
700
|
+
readonly longOptionalValue: Readonly<Record<string, OptionValueKind>>;
|
|
701
|
+
/** Long option FULL names taking no value — declared so that an abbreviation reaching one is seen to be
|
|
702
|
+
* ambiguous rather than read as its value-taking neighbour. */
|
|
703
|
+
readonly longPlain: readonly string[];
|
|
704
|
+
/** SINGLE-dash WORD options — a grammar getopt does not have and a cluster walk cannot read: `-name` is not
|
|
705
|
+
* `-n -a -m -e`, and splitting it that way manufactures a path candidate per suffix. Asked before the cluster
|
|
706
|
+
* walk, only for a dash plus MORE THAN ONE character; a name in neither map falls to the verb-blind scan. */
|
|
707
|
+
readonly dashWordValue?: Readonly<Record<string, OptionValueKind>>;
|
|
708
|
+
/** The value-less half of {@link dashWordValue}. */
|
|
709
|
+
readonly dashWordPlain?: readonly string[];
|
|
710
|
+
/** The FIRST positional operand, where this verb's grammar gives it a meaning that is NOT a file: grep's
|
|
711
|
+
* PATTERN, sed's SCRIPT. Read with the same {@link OptionValueKind} vocabulary the option slots use, so
|
|
712
|
+
* {@link slotValueCandidates} answers "can this word name a file" once for both spellings — `sed -e '/x/p' f`
|
|
713
|
+
* and `sed '/x/p' f` are one invocation and were two readings. `suppliedBy*` name the options that deliver
|
|
714
|
+
* the slot instead, leaving the first operand a file after all. No row ⇒ an ordinary first operand. */
|
|
715
|
+
readonly firstOperand?: {
|
|
716
|
+
readonly kind: OptionValueKind;
|
|
717
|
+
/** Short letters, matched inside a cluster and with digits allowed in front (grep's obsolete numeric
|
|
718
|
+
* context spec: `-2ebeta` is `-C2 -e beta`). */
|
|
719
|
+
readonly suppliedByLetters: string;
|
|
720
|
+
/** Long option FULL names; an abbreviation reaching one counts ({@link isLongOptionAbbrevOf}). */
|
|
721
|
+
readonly suppliedByLongNames: readonly string[];
|
|
722
|
+
};
|
|
723
|
+
/** This program parses its options WHEREVER they appear, so the permute ambiguity the both-readings rule
|
|
724
|
+
* below answers (POSIX/GNU/BSD disagree whether a dash-token past the first operand is still an option) does
|
|
725
|
+
* not exist for it. Declared only where the program's manual states the grammar, and never to rescue a
|
|
726
|
+
* spelling into auto-allow: a token the row does not explain still goes to the verb-blind scan. */
|
|
727
|
+
readonly optionsArePositionIndependent?: true;
|
|
728
|
+
}
|
|
729
|
+
/** What one row makes of ONE option token. `unexplained` = the row cannot account for the token, and the
|
|
730
|
+
* caller reads it the verb-blind way it always did. */
|
|
731
|
+
export type VerbOptionSlotReading = {
|
|
732
|
+
readonly form: "unexplained";
|
|
733
|
+
} | {
|
|
734
|
+
readonly form: "no-value";
|
|
735
|
+
} | {
|
|
736
|
+
readonly form: "attached";
|
|
737
|
+
readonly value: string;
|
|
738
|
+
readonly kind: OptionValueKind;
|
|
739
|
+
} | {
|
|
740
|
+
readonly form: "separated";
|
|
741
|
+
readonly kind: OptionValueKind;
|
|
742
|
+
};
|
|
743
|
+
/**
|
|
744
|
+
* Read one option token of `name` through its row. `undefined` ⇒ this verb has no row.
|
|
745
|
+
*
|
|
746
|
+
* Short form is getopt: scanning the cluster left to right, the FIRST value-taking letter owns the
|
|
747
|
+
* remainder of the token, or the next token when it ends there. Long form is `getopt_long`'s
|
|
748
|
+
* unambiguous-prefix rule with an EXACT name resolved first; a prefix reaching several names is settled
|
|
749
|
+
* only where they agree about what THIS spelling does with its value, which is one question for an
|
|
750
|
+
* attached `=value` (can it name a file?) and two for a separated one (does the option take the next
|
|
751
|
+
* token at all?) — see the branch below.
|
|
752
|
+
*
|
|
753
|
+
* An unsettled prefix falls back rather than guessing, because skipping a value that is really a FILE
|
|
754
|
+
* hides a read: the verb-blind scan that judges every suffix is the safe answer to "this row does not
|
|
755
|
+
* know". That direction is why this reading is NOT the stdin floor's ({@link SEPARATED_VALUE_OPTIONS}),
|
|
756
|
+
* which asks a different question — does this value fill one of the SLOTS the floor counts — and answers
|
|
757
|
+
* it in the other direction. Measured rather than argued: reading the floor off this table moved twelve
|
|
758
|
+
* of its verdicts, eight of them wrongly (`grep -e pat file` blocks on nothing, and its pattern value is
|
|
759
|
+
* exactly what makes grep's floor of two `<pattern> <file>`).
|
|
760
|
+
*/
|
|
761
|
+
export declare function readVerbOptionSlot(name: string, tok: string): VerbOptionSlotReading | undefined;
|
|
762
|
+
/** Does this verb's row declare that its options are parsed WHEREVER they appear — i.e. that the permute
|
|
763
|
+
* ambiguity the both-readings rule answers does not exist for it? See
|
|
764
|
+
* {@link VerbOptionSlots.optionsArePositionIndependent}. */
|
|
765
|
+
export declare function verbOptionsArePositionIndependent(name: string): boolean;
|
|
766
|
+
/** The verb's first-operand slot row, for a reader outside this module that walks operands with the same tables. */
|
|
767
|
+
export declare function verbFirstOperandSlot(name: string): VerbOptionSlots["firstOperand"];
|
|
768
|
+
/**
|
|
769
|
+
* The path candidates one DECLARED value offers, which is also the answer to "did this value name a
|
|
770
|
+
* file?" — an empty list IS the no.
|
|
771
|
+
*
|
|
772
|
+
* Three readings, one per grammar the declared slots have: a value that always names a file offers
|
|
773
|
+
* itself; an `indirect` one may name a file either way, so it offers itself AND what it names past an
|
|
774
|
+
* `@`; and a `header` names a file ONLY as a whole-value `@<file>` — `curl -H 'X: /etc/x'` sends that
|
|
775
|
+
* text and opens nothing, while `curl -H @<file>` adds a header per line of the file (measured against
|
|
776
|
+
* the binary, both directions). Reading `header` the `indirect` way would refuse a header whose text
|
|
777
|
+
* merely looks like a path; reading it as a non-file kind hides the `@` spelling, which is the direction
|
|
778
|
+
* that may not be taken.
|
|
779
|
+
*/
|
|
780
|
+
export declare function slotValueCandidates(value: string, kind: OptionValueKind): string[];
|
|
781
|
+
/**
|
|
782
|
+
* The per-command model of RECURSIVE/EXPANDING read forms: invocations that read the
|
|
783
|
+
* SUBTREE of a path operand, not the operand itself. Consumed by {@link collectSegmentBoundaryFindings}
|
|
784
|
+
* only when a {@link BashReadonlyRootBoundary.denyMatch} seat is wired and has entries to apply (see
|
|
785
|
+
* {@link CompoundReadonlyVerdict.recursiveReadPaths} for the contract and why the deny seat is the
|
|
786
|
+
* trigger), and then only for the rows whose {@link RecursiveReadFormModel.discloses} column reads
|
|
787
|
+
* "contents". The reference implementation draws the same line as a verb set: its recursive-reach breaker
|
|
788
|
+
* fires for `grep`/`egrep`/`fgrep`/`rg`/`diff` (plus two copy verbs a WRITE face answers for here) and for no
|
|
789
|
+
* name that merely lists a tree; `tar` is a contents row here and absent there, because that implementation
|
|
790
|
+
* ships no built-in deny table and this one does.
|
|
791
|
+
*
|
|
792
|
+
* Membership covers every {@link BASH_READONLY_DEFAULT_ALLOW} verb with a recursive form, PLUS the
|
|
793
|
+
* common tree-reading verbs a deployment plausibly widens its allowlist with (`find`, `rg`, …): the
|
|
794
|
+
* allow set is a caller parameter, and a table pinned to the default list would silently go blind the
|
|
795
|
+
* day a deployment adds `rg` — whose recursion needs no flag at all. A widened verb this table does
|
|
796
|
+
* not know stays name-only classified (recorded residual: the deployment owns that widening, same
|
|
797
|
+
* posture as the allowlist's own header note).
|
|
798
|
+
*
|
|
799
|
+
* DIRECTION. Detection over-approximates deliberately: a flag mis-read as recursive costs one ask on
|
|
800
|
+
* a command that is usually a real-getopt error anyway (`grep -dr …` — ambiguous enum value), while an
|
|
801
|
+
* under-read auto-allows a traversal the deny judge never saw — the leaking direction. The cluster
|
|
802
|
+
* walk therefore honours getopt value-owner semantics ONLY where skipping prevents a false fire on a
|
|
803
|
+
* value AND the ownership holds on every supported implementation (`grep -eRfoo` is a pattern under
|
|
804
|
+
* GNU and BSD alike), never to rescue a spelling into auto-allow — a letter whose ownership DIVERGES
|
|
805
|
+
* across implementations is not modeled at all (see the `ls` row), because a scan stopped on the
|
|
806
|
+
* wrong platform's table is an auto-allowed real traversal.
|
|
807
|
+
*/
|
|
808
|
+
export interface RecursiveReadFormModel {
|
|
809
|
+
/** What the traversal puts in front of the CALLER: the file CONTENTS it walked over, or only their
|
|
810
|
+
* NAMES and sizes. The demotion arm reads "contents" rows only — the deny judge cannot pre-check a
|
|
811
|
+
* subtree, and handing back the bytes it guards is the hazard; handing back names is a different
|
|
812
|
+
* disclosure with a different answer. WHERE THE BYTES GO is part of the question and not always a
|
|
813
|
+
* property of the verb (`tar -cf -` writes the archive to stdout), and reading that per invocation is a
|
|
814
|
+
* per-program option model this zero-IO pass does not carry — so an ambiguous row resolves demote-ward,
|
|
815
|
+
* as every ambiguity here does. A "names" row's other fields have no behavioural consumer today; the
|
|
816
|
+
* row stays because it is a DECISION, and a verb deleted from this table reads as one nobody made. */
|
|
817
|
+
readonly discloses: "contents" | "names";
|
|
818
|
+
/** The verb's ordinary operation IS a tree walk — every invocation with a path operand recurses,
|
|
819
|
+
* no flag required (`du`, `find`, `rg`, `tree`, `ag`, `ack`). */
|
|
820
|
+
readonly always?: true;
|
|
821
|
+
/** Short option letters selecting recursion, judged over the getopt option-letter run. */
|
|
822
|
+
readonly shortLetters?: string;
|
|
823
|
+
/** Value-taking short letters: the FIRST one hit owns the token remainder (or, when it ends the
|
|
824
|
+
* token, the next token) — letters inside that value are not options and must not fire. List a
|
|
825
|
+
* letter ONLY when it takes a value on EVERY supported implementation (GNU and BSD): stopping the
|
|
826
|
+
* scan on one platform's table is an under-read on the other, the leaking direction. */
|
|
827
|
+
readonly valueOwners?: string;
|
|
828
|
+
/** Long option names selecting recursion, abbreviation-aware ({@link isLongOptionAbbrevOf} — an
|
|
829
|
+
* abbreviation real getopt_long would call ambiguous over-fires into an ask, the safe side). */
|
|
830
|
+
readonly longNames?: readonly string[];
|
|
831
|
+
/** Enum-valued options where one VALUE selects recursion (grep's `-d recurse` /
|
|
832
|
+
* `--directories=recurse`). The value is matched as a CASE-FOLDED unambiguous-prefix
|
|
833
|
+
* over-approximation: GNU argmatch accepts prefixes, and BSD grep accepts the value
|
|
834
|
+
* case-insensitively (`-d RECURSE` recurses — verified on the real binary), so
|
|
835
|
+
* both laxities fold in. A spelling only one implementation accepts over-fires on the other
|
|
836
|
+
* into an ask on an erroring command — the required side. */
|
|
837
|
+
readonly enumOptions?: readonly {
|
|
838
|
+
shortLetter?: string;
|
|
839
|
+
longName?: string;
|
|
840
|
+
recursiveValue: string;
|
|
841
|
+
}[];
|
|
842
|
+
/** tar's traditional dash-less FIRST-operand key word (`tar cf out.tar dir`, the "old option
|
|
843
|
+
* style" in tar's own manual): mode letters found there select recursion exactly as their dashed
|
|
844
|
+
* spellings do. */
|
|
845
|
+
readonly bundledModeLetters?: string;
|
|
846
|
+
/** `true` when a bare `-` operand denotes STDIN for this command (content readers: grep's file
|
|
847
|
+
* slot, diff, tar's `-f -`). Absent ⇒ `-` is an ordinary filename (ls/du/find/tree have no stdin
|
|
848
|
+
* sense) and stays a positional operand for the recursion demotion. */
|
|
849
|
+
readonly dashIsStdin?: boolean;
|
|
850
|
+
}
|
|
851
|
+
export declare const RECURSIVE_READ_FORMS: Readonly<Record<string, RecursiveReadFormModel>>;
|
|
852
|
+
/**
|
|
853
|
+
* Read ONE sed script and answer whether it can name a file or run a command.
|
|
854
|
+
*
|
|
855
|
+
* sed's command letters are a CLOSED set, which is what makes this a small recognizer over a known
|
|
856
|
+
* grammar rather than a second hand-written parser over an open one: address → optional negation →
|
|
857
|
+
* command letter → the argument that letter takes. Anything the walk cannot finish reading — an
|
|
858
|
+
* unbalanced `s` delimiter, an unclosed `{`, a letter outside the set — answers `unparsed`, which
|
|
859
|
+
* its caller treats exactly as it treats `names`: sed itself rejects those scripts, so the cost of the
|
|
860
|
+
* conservative answer is one approval on a script that was not going to run.
|
|
861
|
+
*/
|
|
862
|
+
/** What one sed script NAMES: the files its `r`/`R` commands read, the files its `w`/`W` commands (and `s///w`)
|
|
863
|
+
* write, the command lines its `e` commands run, and whether an `s///e` flag executes the pattern space. */
|
|
864
|
+
export interface SedScriptNames {
|
|
865
|
+
readonly reads: readonly string[];
|
|
866
|
+
readonly writes: readonly string[];
|
|
867
|
+
readonly commands: readonly string[];
|
|
868
|
+
readonly executesPatternSpace: boolean;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Does this verb's SCRIPT SLOT put a file name or a command line somewhere the walk's operand reading
|
|
872
|
+
* cannot see? Table-driven by verb so a second such verb is a row rather than a second branch.
|
|
873
|
+
*
|
|
874
|
+
* EVERY implementation reading is asked, and the answers combined: an affirmative `names` under any one
|
|
875
|
+
* refuses; a script no reading could SEE refuses, because file-supplied program text may carry anything;
|
|
876
|
+
* and a script no reading could finish refuses. A reading that lands on a clean script is what clears the
|
|
877
|
+
* invocation — which is what keeps the everyday spellings, whose other reading lands on a plain file name,
|
|
878
|
+
* at their old verdict.
|
|
879
|
+
*/
|
|
880
|
+
/** Every file and command line a sed invocation's script slot names, under BOTH option dialects (the union,
|
|
881
|
+
* deduplicated): what a candidate extraction hands the read judge (`reads`), the write face (`writes`) and a
|
|
882
|
+
* nested plan (`commands`). A reading that saw no text names nothing here — that fact rides the verdict face. */
|
|
883
|
+
export declare function sedScriptSlotFiles(args: readonly string[]): SedScriptNames;
|
|
884
|
+
/**
|
|
885
|
+
* How an INTERPRETER receives the program it runs, declared per interpreter rather than guessed.
|
|
886
|
+
*
|
|
887
|
+
* The member that completes the ARGUMENT-AS-PROGRAM family. An interpreter's program can arrive three
|
|
888
|
+
* ways: as an OPTION's value (`perl -e '...'`, `python3 -c '...'`, `node --eval '...'`), as the first
|
|
889
|
+
* OPERAND (the awk family, whose program is mandatory and is not a file unless `-f` named one), or on
|
|
890
|
+
* STANDARD INPUT when the command line names none at all. Only the third and the first are this arm's
|
|
891
|
+
* business together with the second; a program named as a FILE operand (`perl x.pl`, `python3 app.py`)
|
|
892
|
+
* is a path the ordinary operand walk already judges, and stays exactly where it was.
|
|
893
|
+
*
|
|
894
|
+
* WHAT IS JUDGED IS THE PRESENCE OF PROGRAM TEXT, NEVER ITS CONTENT. A quoted program is ONE word to
|
|
895
|
+
* this file's lexer and its contents are never parsed — so the answer cannot be "does this program read
|
|
896
|
+
* a file", only "is there a program this walk did not read". That is why the everyday harmless
|
|
897
|
+
* spellings (`node -e 'console.log(1)'`) refuse along with the rest: telling them apart needs a parser
|
|
898
|
+
* per language, which is exactly the thing a lexical floor must not grow.
|
|
899
|
+
*
|
|
900
|
+
* A row is DATA, so a further interpreter is a row rather than a branch, and a versioned spelling
|
|
901
|
+
* (`python3.11`, `perl5.36`, `tclsh8.6`) is the same row via {@link interpreterKey}.
|
|
902
|
+
*/
|
|
903
|
+
export interface InlineProgramModel {
|
|
904
|
+
/** Options whose value is program TEXT, separated (`--eval prog`) or fused (`--eval=prog`). Matched on
|
|
905
|
+
* the whole token, so a single-dash long option (`-cmd`) is one of these rather than a letter bundle. */
|
|
906
|
+
readonly longText: ReadonlySet<string>;
|
|
907
|
+
/** Options whose value NAMES the program (a script file, or a module the interpreter resolves). */
|
|
908
|
+
readonly longFile: ReadonlySet<string>;
|
|
909
|
+
/** Options that NAME the program while taking NO value of their own — a mode that selects what runs
|
|
910
|
+
* (`node --test` runs the test files it discovers, and reads no program from its input). */
|
|
911
|
+
readonly longNamesProgram: ReadonlySet<string>;
|
|
912
|
+
/** Options whose value is a file the interpreter reads BESIDE the program it is given — an
|
|
913
|
+
* initialization script, a library to preload. Their value is consumed so it is not counted as an
|
|
914
|
+
* operand, and they do NOT name the program: what follows still has to. */
|
|
915
|
+
readonly longSupplemental: ReadonlySet<string>;
|
|
916
|
+
/** Options whose value is the NEXT token and means none of the above. */
|
|
917
|
+
readonly longValue: ReadonlySet<string>;
|
|
918
|
+
/** Long options this row declares to take NO value. Declaring them is what keeps an invocation
|
|
919
|
+
* EXPLAINED past them, so a later script operand still ends the interpreter's own option scanning. */
|
|
920
|
+
readonly longPlain: ReadonlySet<string>;
|
|
921
|
+
/** Whether an option that NAMES the program also ends this interpreter's option processing, because
|
|
922
|
+
* what follows is the named program's own argument list (`python3 -m <module> -c` passes `-c` to the
|
|
923
|
+
* module). False where a named source is PRELIMINARY and further sources still follow (`dc -f <file>
|
|
924
|
+
* -e '<program>'` runs both). */
|
|
925
|
+
readonly fileOptionEndsOptions: boolean;
|
|
926
|
+
/** Short-option letters that introduce program text — alone (`-e prog`), fused (`-eprog`) or inside a
|
|
927
|
+
* bundle (`-pe`, `-lne`, `-uc`, `-0777e`). */
|
|
928
|
+
readonly textLetters: string;
|
|
929
|
+
/** Short-option letters whose value names the program (`-f prog.awk`, `-fprog.awk`, `-m <module>`). */
|
|
930
|
+
readonly fileLetters: string;
|
|
931
|
+
/** Short-option letters whose value is the rest of the token, or the NEXT token when the token ends on
|
|
932
|
+
* them (`perl -I lib`). The bundle scan stops there — what follows in the token is that value. */
|
|
933
|
+
readonly separatedValueLetters: string;
|
|
934
|
+
/** Short-option letters whose value can only be ATTACHED (`perl -i.bak`, `-C<flags>`): the bundle scan
|
|
935
|
+
* stops there too, but a bare spelling takes NOTHING from the next token — reading `perl -C -e '…'`
|
|
936
|
+
* as "-C whose value is -e" swallowed the option that carries the program. */
|
|
937
|
+
readonly attachedValueLetters: string;
|
|
938
|
+
/** Short-option letters that take no value at all. Declaring them is what makes an invocation EXPLAINED
|
|
939
|
+
* (see `explained` in the recognizer); they change nothing else. */
|
|
940
|
+
readonly plainLetters: string;
|
|
941
|
+
/** Short-option letters that make this interpreter PRINT AND EXIT. Per interpreter, because the same
|
|
942
|
+
* letter is a banner in one and something else entirely in another (`-v` is the version in ruby and
|
|
943
|
+
* VERBOSE EXECUTION in python; `-h` is the version banner in lua and the HOST in psql). */
|
|
944
|
+
readonly bannerLetters: string;
|
|
945
|
+
/** Short-option letters that make the interpreter read commands INTERACTIVELY after whatever else it
|
|
946
|
+
* was given — a banner or a named script does not end the session, so the program still arrives on
|
|
947
|
+
* standard input (`lua -v -i`, `lua -i script.lua`). */
|
|
948
|
+
readonly interactiveLetters: string;
|
|
949
|
+
/** Which OPERAND names the program, when any does, and whether it is program TEXT (the awk family, a
|
|
950
|
+
* jq filter, the SQL after a database) or a program FILE (every script interpreter). Absent when no
|
|
951
|
+
* operand ever names the program — psql's are connection arguments, and an interactive compiler's are
|
|
952
|
+
* modules it loads before reading commands from its input. */
|
|
953
|
+
readonly programOperand?: {
|
|
954
|
+
readonly at: number;
|
|
955
|
+
readonly isText: boolean;
|
|
956
|
+
};
|
|
957
|
+
/** Whether an invocation that names no program anywhere reads one from its standard input. */
|
|
958
|
+
readonly stdinWhenUnnamed: boolean;
|
|
959
|
+
}
|
|
960
|
+
/** The interpreters this file has a row for, keyed by the basename bash resolves, with the trailing
|
|
961
|
+
* version dropped ({@link interpreterKey} — which is also why the key for `sqlite3` is `sqlite`). */
|
|
962
|
+
export declare const INTERPRETER_INLINE_PROGRAMS: ReadonlyMap<string, InlineProgramModel>;
|
|
963
|
+
/** The row a versioned spelling belongs to: `python3`, `python3.11` and `python` are one program with
|
|
964
|
+
* one program-delivery grammar, so the trailing version is dropped before the lookup. */
|
|
965
|
+
export declare function interpreterKey(name: string): string;
|
|
966
|
+
/**
|
|
967
|
+
* How this invocation receives its program, or `undefined` when it NAMES one (a script file, a module,
|
|
968
|
+
* a program file option) or names none at all on an interpreter that then reads none.
|
|
969
|
+
*
|
|
970
|
+
* Read over the folded arguments with the program word excluded. Three sources in one pass — an option
|
|
971
|
+
* whose value is program text, the operand a row declares as the program, and standard input — and one
|
|
972
|
+
* rule for every doubt: an option this file has no row for never ENDS the search for program text. The
|
|
973
|
+
* word that names the program stops the scan (past a script, `-p` belongs to the SCRIPT), but only while
|
|
974
|
+
* every option so far was one the row EXPLAINS: after an option of unknown arity the following word may
|
|
975
|
+
* be that option's value rather than the script, so the search has to run to the end.
|
|
976
|
+
*/
|
|
977
|
+
export declare function inlineProgramClause(model: InlineProgramModel, args: readonly string[]): string | undefined;
|
|
978
|
+
/** Does this already-vetted segment (FOLDED tokens — quoting does not change what getopt sees) invoke
|
|
979
|
+
* a {@link RECURSIVE_READ_FORMS} verb in a recursive form that DISCLOSES CONTENTS? `args` excludes the
|
|
980
|
+
* command name. Names-only traversals (`find`, `ls -R`, `du`, `tree`, an archive write) are in the table
|
|
981
|
+
* and answer `false` here — see {@link RecursiveReadFormModel.discloses}. */
|
|
982
|
+
export declare function segmentSelectsRecursiveRead(name: string, args: readonly string[]): boolean;
|
|
983
|
+
export declare function assignmentWordPayload(word: string): {
|
|
984
|
+
kind: "value";
|
|
985
|
+
value: string;
|
|
986
|
+
} | {
|
|
987
|
+
kind: "unresolved";
|
|
988
|
+
reason: string;
|
|
989
|
+
} | undefined;
|
|
990
|
+
/** WHICH programs are DECLARED not to open the value of a `NAME=value` ARGUMENT — an allow-list, because
|
|
991
|
+
* the other polarity was measured false. `make PREFIX=/usr/local`, `cmake -D…`, `docker run -e HOME=/root`
|
|
992
|
+
* name a build prefix and an environment value their program never opens, and judging those was the one
|
|
993
|
+
* shape of the verb-blind rule that MANDATED the everyday build line. The exemption is theirs alone.
|
|
994
|
+
*
|
|
995
|
+
* The polarity is the whole point. Listing the programs that DO open such a value means anything the walk
|
|
996
|
+
* fails to recognise is exempt — and what it fails to recognise is WHICH PROGRAM RUNS: an undeclared
|
|
997
|
+
* wrapper is read as the program (`caffeinate dd if=<path>`), and a launcher can carry the program inside
|
|
998
|
+
* an option payload with no word equal to it (`env -Sdd if=<path>`). Both were verified to execute the
|
|
999
|
+
* read. Inverted, none of those is on this list, so none is exempt — nor is a fifth spelling of the same
|
|
1000
|
+
* trick. A further program is a member, not a branch, and adding one is a claim about it alone.
|
|
1001
|
+
*
|
|
1002
|
+
* NOT its business: the LEADING environment assignments and an OPTION word's payload, each with its own arm. */
|
|
1003
|
+
export declare const ASSIGNMENT_VALUE_NOT_PATHS: ReadonlySet<string>;
|
|
1004
|
+
/** The program the shell will RUN here and where its OWN argv begins — the two seats
|
|
1005
|
+
* {@link HeadedSegment.argvProgram} / {@link HeadedSegment.argvFrom} carry, from one reader so they cannot
|
|
1006
|
+
* disagree. Both assignment spellings are stripped BEFORE the program-position reader runs: it does not
|
|
1007
|
+
* read `NAME+=value` as an assignment (quoting makes `'A+=x'` a command NAME on folded text), so a leading
|
|
1008
|
+
* append word arrives AS the program and hides any launcher — measured on `A+=x env B=<outside> <prog>`. */
|
|
1009
|
+
export declare function argvProgramOf(tokens: SegmentTokens): {
|
|
1010
|
+
program: string;
|
|
1011
|
+
from: number;
|
|
1012
|
+
} | undefined;
|
|
1013
|
+
export declare function attachedOptionPayloads(tok: string): string[];
|
|
543
1014
|
/**
|
|
544
1015
|
* The home directory's VARIABLE spellings, SUBSTITUTED with the declared value before a read face segments the
|
|
545
1016
|
* command: `$HOME` / `${HOME}` at the START of a word (an empty quote pair before it included), unquoted or
|
|
@@ -643,6 +1114,10 @@ export interface OutOfRootReadGateVerdict {
|
|
|
643
1114
|
readonly checked: readonly string[];
|
|
644
1115
|
readonly undecided: readonly string[];
|
|
645
1116
|
}
|
|
1117
|
+
/** Working-directory moves {@link evaluateReadBoundary} does NOT model. `cd` is deliberately absent —
|
|
1118
|
+
* it IS modelled (the walk threads the base through it), which is the whole difference: a move the
|
|
1119
|
+
* walk follows keeps the verdict decidable, and one it cannot follow does not. */
|
|
1120
|
+
export declare const UNMODELLED_BASE_MOVERS: ReadonlySet<string>;
|
|
646
1121
|
/** Test seam for the lockstep pin that the launcher table (the ONE launcher truth, in the program-position
|
|
647
1122
|
* reader's module) stays a SUBSET of the rule lane's curated `BARE_INTERPRETER_NAMES`. Exported for that one
|
|
648
1123
|
* assertion — production code reads the table directly. The containment face ({@link classifyOutOfRootReadGate})
|