import type { GateEvaluator } from './gate-types.js'; /** * transport-shield (mmnto-ai/totem#2799): a PreToolUse gate over the Bash and * PowerShell tools that refuses the KNOWN payload-mangling command shapes before * the shell sees them, naming the cure in every refusal. * * The pattern table lives HERE, once, beside the evaluator (the charter's * errata: provenance is the table in core; the CLI only installs). Every row is * a pure function of the payload — no filesystem, no environment, no clock * beyond the verdict's `checkedAt` — so the same command yields the same * verdict on every machine given the same `platform` and `tool` fields, and a * test can pin win32 on any runner. * * Evaluation order is the table order: the first DENY row that fires wins; a * WARN row is consulted only when no deny fired. A shape the table lacks is * allowed — the gate enumerates known shapes (the charter's positive corpus), * and a miss is a corpus gap counted by the charter's metric, never a fail-open * of an applicable gate. * * False-positive budget (ADR-109: a non-exact-match gate ships a stated budget * and the fixture that measures it): ZERO denies over the benign corpus in * `transport-shield.fold.test.ts` (everyday commands that share a token with a * row — `diff -b`, `curl -b`, `grep gh -b`, a Windows path as a sed operand, * `<<` inside a quoted argument, a here-string). A deny on a benign command in * the field is a corpus row plus a fix, never a hand-carved exemption; the * `--pilot` tier exists for a measurement week. * * Out of scope by design, disclosed: a bare backslash outside the four named * contexts (a heredoc body, a sed -i expression, an inline node/python body, a * gh --body) is not a shape this gate reads; a heredoc inside a `bash -c "…"` * or `sh -c '…'` operand is quoted text to this scanner and is not recursed * into (a corpus gap, not a fail-open of an applicable row); a wrapper that * takes operands of its own before the program (`sudo -u me gh …`, `timeout * 30 gh …`, `npx …`) hides the program from the position anchor — the same * class. The scanners read the shell's own grammar where a mis-read would * desynchronize them: a `#` that begins a word — after an unquoted blank, * newline, `;`, `|`, `&`, an opening `(` or an OPERATOR `)` (a subshell's, a * case pattern's) — is a comment to the end of the line, discarded WITHOUT * quote processing (POSIX 2.3 rule 9; bash §3.1.3), while a `#` that continues * a word is not one: `a#b`, and `$(x)#1` / `<(x)#1`, where the `)` closes a * substitution that is part of the word (rules 5 and 8 for `$( … )`; process * substitution is a bash extension, bash §3.5.6, that behaves the same way) — * the scanners track which `(` each `)` closes. `$(( … ))` / `(( … ))` arithmetic is skipped. So * neither an apostrophe in a comment nor a `<<` shift can hide a later heredoc * or expose comment text as arguments. For the PowerShell tool the SAME walk * reads PowerShell's grammar where it differs from bash's, so there is one * model of which text is code and no pre-pass to disagree with it: a `<# … #>` * block comment outside quotes is skipped whole (a `<#` inside a string is * text; a quote inside a block opens nothing), the backtick is the escape * inside a double-quoted string, and a `#` is read by the same word-boundary * rule as bash's. Not read, disclosed: PowerShell also begins a comment after a * token-ending string, an assignment operator or a `)` (`'a'#b`, `$x=#c`, * `$(1)#c`), which the scanners read as word text — the over-scan direction, * except when such a comment carries text the scanners read as shell syntax * (an odd `'` or `"`, a `<#`, a trailing backslash, an unterminated `$(`) on * the line before a later positive: those five carriers, each confirmed by * execution, are the miss direction. PowerShell's token boundaries are not * derivable from a character walk (`$x=#c` is an assignment and a comment at * statement position but one argument after a command; `'a'#b` and `x='a'#c` * differ only by where the token began), and five successive folds of the * PowerShell reading — a pre-pass, its string tracking, its comment rule, then * two token-boundary models inside the scanners — each opened a sibling shape * (the bot-round record on mmnto-ai/totem#2804), so they stay disclosed rather * than modelled. PowerShell * here-strings (`@" … "@`, `@' … '@`) are not parsed either — a quote inside * one can desynchronize the quote scan for that tool, the miss direction. The * MSYS opt-out is honoured through the shell forms that export * the variable to the judged program (MSYS reads its presence, any value): the * segment's own `VAR=… prog` prefix, an earlier `export` / `declare -x` / * `typeset -x`, a bare assignment under `set -a` or followed by `export NAME`, * until an `unset` — never as a substring: a comment, a heredoc body or a * quoted string that names the cure opts nothing out. Disclosed over-allow: an * export inside a subshell `( … )` or a pipeline element is taken as reaching * later segments though the shell would not apply it. */ export declare const TRANSPORT_SHIELD_EVENT = "transport-shield"; /** The module label every verdict cites as its `provenance.source` (never a path). */ export declare const TRANSPORT_SHIELD_SOURCE = "transport-shield pattern table"; /** A heredoc body at or above this many bytes WARNs (the banked ~4 KB harness trap). */ export declare const HEREDOC_OVERSIZE_BYTES = 4096; /** `provenance.matched` is bounded to this many characters. */ export declare const MATCHED_FRAGMENT_MAX = 80; export type TransportTool = 'Bash' | 'PowerShell'; export interface TransportShieldPayload { tool: TransportTool; command: string; /** The host's `process.platform` — REQUIRED so the verdict never reads it in core. */ platform: string; } export type TransportPatternId = 'heredoc-escape' | 'heredoc-oversize' | 'msys-body-slash' | 'sed-i-escape' | 'inline-body-escape' | 'msys-rev-path-subshell'; export interface TransportMatch { /** The offending fragment, raw and as written; the evaluator bounds and sanitizes it for provenance. */ fragment: string; /** One clause naming what matched, for the reason text. */ detail: string; } export interface TransportPattern { id: TransportPatternId; disposition: 'deny' | 'warn'; /** The safe alternative, in the imperative; joined to the detail in the reason. */ cure: string; find(payload: TransportShieldPayload): TransportMatch | null; } /** * Parse an unknown payload into the gate's shape. Throws `GATE_INVALID` on a * missing or non-string `command`, a `tool` outside the pair, or a missing * `platform` — never default-allows (ADR-109: an unparseable payload is a * broken source). The distributed wrapper never sends such a payload: it exits * 0 as not-applicable first, so this branch is reachable only by hand. */ export declare function parseTransportShieldPayload(payload: unknown): TransportShieldPayload; export interface HeredocSpan { /** The delimiter word as written (quotes stripped). */ delimiter: string; /** Whether the delimiter was quoted (`<<'EOF'` / `<<"EOF"`). */ quoted: boolean; /** Whether the operator was `<<-` (bash strips leading TABS from body and terminator lines). */ stripTabs: boolean; /** The body text between the operator line and the terminator line (or the end). */ body: string; /** True when no terminator line was found — the body runs to the end of the command. */ unterminated: boolean; /** Offsets of the body within the command, for blanking. */ bodyStart: number; bodyEnd: number; } /** * What the scanners read beyond bash's grammar. `powershell` (the PowerShell * tool): a `<# … #>` block comment outside quotes is skipped whole (inside a * `$( … )` too); the backtick is the escape inside a double-quoted string. * Everything else — where a `#` begins a comment, what a quote or an `=` does * to a token — is read with bash's rules; the shapes where PowerShell's * tokenizer differs are disclosed in the module header, not modelled. */ export interface ScanOptions { powershell?: boolean; } /** * Locate every heredoc in a command in ONE pass that tracks shell quoting and * SKIPS heredoc bodies: an operator inside a quoted argument is text, `<<<` is * a here-string, and an apostrophe inside a body never opens a quote (the * fold's own regression, mmnto-ai/totem#2799 pass 2). A body starts after the * newline that ends the operator's line and runs to the first line that IS the * delimiter — an exact line match, as bash reads it (a `EOF ` with trailing * space or a CRLF `EOF\r` does not terminate); for `<<-` leading tabs are * stripped first — or to the end of the command when no such line exists. An * operator with no newline after it yields a body of the remaining text: the * conservative reading, so a truncated command still refuses on its escapes. * A `#` that begins a word discards the rest of its line without quote * processing, as bash does, and `$(( … ))` / `(( … ))` arithmetic is skipped. * `parens` records what each open `(` is — a substitution (`$(`, `<(`, `>(`), * which is part of a word, or a grouping operator — so the `)` that closes it * can say whether the next character begins a word. With `powershell` set the * same walk reads PowerShell's grammar where it differs (see `ScanOptions`). */ export declare function findHeredocs(command: string, opts?: ScanOptions): HeredocSpan[]; export interface ShellSegment { /** * Tokens with quotes resolved: single-quoted text literal; inside double * quotes a backslash escapes only `$`, backtick, `"`, `\` and newline (POSIX) * and is otherwise kept; outside quotes a backslash escapes the next character * and backslash-newline is a line continuation (dropped). */ tokens: string[]; /** The raw text of the segment, for newline and substring checks. */ raw: string; } /** * A minimal POSIX-shell tokenizer: splits a command into segments at control * operators (`&&`, `||`, `|`, `;`, a background `&`, newline) and each segment * into words with quotes resolved. Good enough to find an option's value and a * program name; it does not expand anything. Command substitutions `$( … )` * and process substitutions `<( … )` / `>( … )` stay inside the word that * carries them; grouping `(` and `)` are operators that delimit words. A `#` * that begins a word discards the rest of its line, as bash does — comment * text is never an argument. With `powershell` set the same walk reads * PowerShell's grammar where it differs (see `ScanOptions`). */ export declare function tokenizeShell(command: string, opts?: ScanOptions): ShellSegment[]; /** * The pattern table, in evaluation order. Deny rows first, warn rows after. * Each `find` reads only the payload. */ export declare const TRANSPORT_PATTERNS: ReadonlyArray; /** * The gate: parse the payload (throws on an invalid one), walk the table, and * return the first deny, else the first warn, else allow. Pure over the payload. */ export declare const transportShieldEvaluator: GateEvaluator; //# sourceMappingURL=transport-shield.d.ts.map