@sema-agent/core 7.24.3 → 7.25.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 +101 -1
- package/dist/agents/peer-session-drain.d.ts +16 -1
- package/dist/agents/peer-session-drain.js +16 -1
- package/dist/agents/send-message-tool.d.ts +10 -2
- package/dist/agents/send-message-tool.js +18 -7
- package/dist/agents/subagent.js +13 -10
- package/dist/core/background-agent-store.d.ts +3 -3
- package/dist/core/checkpoint-store.d.ts +1 -0
- package/dist/core/delegation-frames.d.ts +5 -0
- package/dist/core/gate-fold.js +85 -0
- package/dist/core/gate-lanes.js +23 -1
- package/dist/core/gate-outcome.d.ts +8 -2
- package/dist/core/gate-outcome.js +3 -1
- package/dist/core/hooks.d.ts +29 -0
- package/dist/core/hooks.js +2 -0
- package/dist/core/mailbox-store.d.ts +5 -0
- package/dist/core/mailbox-store.js +1 -1
- package/dist/core/parent-spec-seat.d.ts +7 -1
- package/dist/core/parent-spec-seat.js +2 -0
- package/dist/core/permission-rule-consent.js +29 -47
- package/dist/core/permission-rule-provider.js +4 -3
- package/dist/core/permission-rule-store.d.ts +143 -7
- package/dist/core/permission-rule-store.js +165 -15
- package/dist/core/permission-rules.js +14 -10
- package/dist/core/rule-denial-sentence.d.ts +75 -0
- package/dist/core/rule-denial-sentence.js +16 -0
- package/dist/core/runner/contracts.d.ts +36 -2
- package/dist/core/runner/gate-exit.d.ts +14 -0
- package/dist/core/runner/gate-exit.js +4 -0
- package/dist/core/runner/prepare-delegation-surface.d.ts +4 -4
- package/dist/core/runner/prepare-delegation-surface.js +2 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
- package/dist/core/runner/prepare-gate-stations.js +7 -4
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +25 -10
- package/dist/core/runner/prepare-inherited-gate.d.ts +23 -0
- package/dist/core/runner/prepare-inherited-gate.js +11 -6
- package/dist/core/runner/prepare-policy-chain.d.ts +15 -1
- package/dist/core/runner/prepare-policy-chain.js +42 -22
- package/dist/core/runner/prepare-suspend-saga.d.ts +5 -2
- package/dist/core/runner/prepare-suspend-saga.js +4 -3
- package/dist/core/runner/prepare-task.js +10 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -4
- package/dist/core/runner/prepare-wiring-manifest.js +8 -4
- package/dist/core/runner/resume-apply.js +2 -0
- package/dist/core/runner/run-harness-handlers.js +1 -0
- package/dist/core/runner/run-identity-wiring.d.ts +2 -2
- package/dist/core/runner/run-leg.js +28 -1
- package/dist/core/runner/run-notification-lane.d.ts +2 -2
- package/dist/core/runner/run-notification-lane.js +2 -1
- package/dist/core/runner/run-settle-and-teardown.js +1 -0
- package/dist/core/runner/session-rule-policy.d.ts +73 -2
- package/dist/core/runner/session-rule-policy.js +121 -21
- package/dist/core/session-policy-store.d.ts +27 -3
- package/dist/core/session-policy-store.js +12 -4
- package/dist/core/store-contracts/background-agent-store-contract.js +10 -0
- package/dist/core/store-contracts/peer-directory-contract.d.ts +23 -0
- package/dist/core/store-contracts/peer-directory-contract.js +82 -0
- package/dist/core/task-event.d.ts +6 -0
- package/dist/core/task-notification.d.ts +79 -4
- package/dist/core/task-notification.js +90 -1
- package/dist/core/task-registry-agent.js +1 -2
- package/dist/core/task-registry-monitor.js +2 -1
- package/dist/core/task-registry-shared.d.ts +53 -5
- package/dist/core/task-registry-shared.js +13 -2
- package/dist/core/task-registry.js +8 -5
- package/dist/core/task-spec.d.ts +12 -5
- package/dist/core/tool-catalog-entries.js +1 -0
- package/dist/core/tool-spec.d.ts +1 -1
- package/dist/core/wiring-manifest.d.ts +2 -2
- package/dist/engine/loop/agent-loop.js +1 -1
- package/dist/engine/loop/types.d.ts +4 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -3
- package/dist/orchestration/workflow.js +12 -3
- package/dist/prompts/coordinator.d.ts +2 -1
- package/dist/stores/file/adoption/adopt.js +23 -1
- package/dist/stores/file/permission-rule-store.js +3 -1
- package/dist/stores/file/session-policy-store.d.ts +31 -0
- package/dist/stores/file/session-policy-store.js +87 -7
- package/dist/tools/fs/bash-program-position.d.ts +4 -26
- package/dist/tools/fs/bash-program-position.js +0 -11
- package/dist/tools/fs/bash-readonly-classifier.d.ts +20 -309
- package/dist/tools/fs/bash-readonly-classifier.js +28 -766
- package/dist/tools/fs/fs-bash.d.ts +43 -207
- package/dist/tools/fs/fs-bash.js +20 -341
- package/dist/tools/fs/index.d.ts +12 -13
- package/dist/tools/fs/index.js +3 -4
- package/dist/tools/fs/read-deny.d.ts +25 -0
- package/dist/tools/fs/read-deny.js +19 -0
- package/dist/tools/fs/safety.js +2 -6
- package/dist/tools/fs/shell-read-check.d.ts +99 -0
- package/dist/tools/fs/shell-read-check.js +229 -0
- package/dist/tools/fs/shell-read-establish.d.ts +70 -115
- package/dist/tools/fs/shell-read-establish.js +216 -102
- package/dist/tools/fs/shell-read-judge.d.ts +231 -0
- package/dist/tools/fs/shell-read-judge.js +170 -0
- package/dist/tools/fs/shell-read-plan.d.ts +3 -0
- package/dist/tools/fs/shell-read-plan.js +82 -19
- package/dist/tools/monitor.d.ts +5 -5
- package/dist/tools/monitor.js +3 -5
- package/dist/tools/task-list.d.ts +4 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +19 -3
- package/wire-changes/core@7.24.4.json +54 -0
- package/wire-changes/core@7.25.0.json +671 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type SegmentTokens, type ShellSegmentConnector } from "./bash-lexer.js";
|
|
2
|
-
import { type ShellScanDecline } from "./bash-program-position.js";
|
|
3
2
|
/**
|
|
4
3
|
* Bare command names `bash_readonly` permits out of the box, ALSO the default reversible set for the
|
|
5
4
|
* The {@link import("./fs-bash.js").bashReversibilityProbe} classifier. **Coarse first filter, NOT a security
|
|
@@ -314,148 +313,10 @@ export interface BashReadonlyRootBoundary {
|
|
|
314
313
|
* is the read-only verdict; everything else is a demotion.
|
|
315
314
|
*/
|
|
316
315
|
export interface CompoundReadonlyVerdict {
|
|
317
|
-
/** Demotion reason (the same string {@link classifyCompoundReadonly} returns), or undefined
|
|
316
|
+
/** Demotion reason (the same string {@link classifyCompoundReadonly} returns), or undefined: the
|
|
317
|
+
* command's SHAPE is not one the classify doctrine auto-allows. WHERE the command reads is not this
|
|
318
|
+
* verdict's business — that is the gate's shell read station (`shell-read-check.ts`). */
|
|
318
319
|
reason?: string;
|
|
319
|
-
/** Set when a listed reader's statically resolved path operand lies outside the roots. With `reason` the
|
|
320
|
-
* containment sentence itself, the roots were the SOLE obstacle (every segment a listed reader, no
|
|
321
|
-
* blocking/device/guarded-form arm fired, every operand resolved) and the narrow "allow reading from <dir>"
|
|
322
|
-
* approval would make the command auto-allowable; with `reason` a SHAPE refusal (follow mode, a device, a
|
|
323
|
-
* blocking stdin read, a guarded verb form, multi-cd) or an UNRESOLVABLE-operand refusal (another operand
|
|
324
|
-
* the walk could not read) the finding rides beside the sentence — the read of that path is the same read,
|
|
325
|
-
* so the boundary's objection is stated either way, while the command stays refused after any grant. */
|
|
326
|
-
outOfRootRead?: true;
|
|
327
|
-
/** The resolved out-of-root paths, de-duplicated, in first-seen order. Present iff `outOfRootRead`. */
|
|
328
|
-
outOfRootPaths?: readonly string[];
|
|
329
|
-
/** B-057 — an operand matched the deployment's read deny judge (`BashReadonlyRootBoundary.denyMatch`):
|
|
330
|
-
* the demotion was raised by a DECLARED read boundary, not by the classifier's own caution. The probe
|
|
331
|
-
* reads it to mint `mandated` — a stored allow rule or the read-only shell arm may retire a classify-
|
|
332
|
-
* tier question, never one the read face itself asked. Structural sibling of `outOfRootRead`. */
|
|
333
|
-
readDenied?: true;
|
|
334
|
-
/** The matched operand (resolved spelling) and the pattern — present iff {@link readDenied} (the
|
|
335
|
-
* `outOfRootRead`/`outOfRootPaths` idiom), so a refusal CARD names the target without parsing prose. */
|
|
336
|
-
readDeniedPath?: string;
|
|
337
|
-
readDeniedPattern?: string;
|
|
338
|
-
/**
|
|
339
|
-
* The paths this scan resolved INSIDE the roots (de-duplicated, first-seen order), i.e. the
|
|
340
|
-
* candidates a caller holding a filesystem should re-check with symlinks resolved. Absent when there
|
|
341
|
-
* are none.
|
|
342
|
-
*
|
|
343
|
-
* Why the ALLOW side is reported at all: containment here is LEXICAL (see
|
|
344
|
-
* {@link resolveOperandLexically}), so "inside the roots" means the written spelling has an in-root
|
|
345
|
-
* prefix — a symlink inside a root that points out of it (`<root>/vendor` → `/etc`) passes. Closing
|
|
346
|
-
* that needs `realpath`, which this function deliberately cannot do (synchronous, pure, no
|
|
347
|
-
* ExecutionEnv). Carrying the candidates is the seam: the ENFORCING caller canonicalizes them and
|
|
348
|
-
* re-compares (`createBashReadonlyTool` does exactly this — a target that resolves out of root is
|
|
349
|
-
* refused there). Typically 0-2 entries per command.
|
|
350
|
-
*
|
|
351
|
-
* Present on BOTH arms, not only the allow arm: a command can name one out-of-root path AND one
|
|
352
|
-
* in-root candidate (`cat /etc/passwd inroot.txt`), and a caller that EXEMPTS the out-of-root path
|
|
353
|
-
* (the engine's own overflow spool, or a non-canonical spelling that resolves back inside) then goes
|
|
354
|
-
* on to execute — with the other candidate still unverified. The field is what that caller checks
|
|
355
|
-
* before running, whichever way the lexical verdict came out.
|
|
356
|
-
*
|
|
357
|
-
* Includes bare-word operands (`cat linkfile.txt`), which are NOT lexical out-of-root candidates —
|
|
358
|
-
* the "a name with no separator can only reach the working directory" argument is true lexically and
|
|
359
|
-
* false under symlinks, so those are exactly the entries a filesystem-holding caller must resolve.
|
|
360
|
-
* They never affect {@link reason}: a bare word cannot demote and does not here either.
|
|
361
|
-
*/
|
|
362
|
-
checkedPaths?: readonly string[];
|
|
363
|
-
/**
|
|
364
|
-
* Operands whose spelling carries an UNEXPANDED glob (`*`, `?`, `[…]`, unquoted). Lexical
|
|
365
|
-
* containment is not decidable for them: the shell expands the pattern AFTER this verdict is made, and
|
|
366
|
-
* inside a root an expansion can land on a symlink that points out of it (`cat *` where the directory
|
|
367
|
-
* holds a link to an out-of-root file — reproduced end-to-end, not a theoretical shape).
|
|
368
|
-
*
|
|
369
|
-
* They are NOT in {@link checkedPaths}: a filesystem-holding resolver answers the unexpanded spelling
|
|
370
|
-
* with its deepest EXISTING ancestor — the directory itself — and reports nothing escaping, which is
|
|
371
|
-
* precisely the false "resolved, inside" answer this field exists to prevent.
|
|
372
|
-
*
|
|
373
|
-
* CONSUMER CONTRACT. A gate that auto-allows on containment must treat a verdict carrying this field as
|
|
374
|
-
* UNDECIDED: ask, or expand the patterns itself against the filesystem and judge every match. The
|
|
375
|
-
* read-only judgement itself is unaffected — `ls *` genuinely writes nothing, and {@link reason} stays
|
|
376
|
-
* absent — because a hard refusal here would take a routine idiom away from every caller, including the
|
|
377
|
-
* ones that CAN resolve it. The engine's own `bash_readonly` leg is such a caller: it expands and
|
|
378
|
-
* resolves each match before executing, so it keeps running these commands unprompted.
|
|
379
|
-
*
|
|
380
|
-
* `cd` is the one exception, and it fails closed: a glob there cannot be expanded into the single
|
|
381
|
-
* directory the compound face must track as the new working directory, so it demotes (see {@link reason}).
|
|
382
|
-
*
|
|
383
|
-
* ALSO carries the {@link recursiveReadPaths} entries (a subset): both families are operands whose
|
|
384
|
-
* real read set is not in the command text, and the consumer contract above ("non-empty ⇒ ask, or
|
|
385
|
-
* resolve yourself") is stated over this one field so an auto-allow gate cannot honour one family
|
|
386
|
-
* and miss the other.
|
|
387
|
-
*/
|
|
388
|
-
undecidedPaths?: readonly string[];
|
|
389
|
-
/** The KERNEL spellings of the {@link checkedPaths} / {@link undecidedPaths} entries whose operand
|
|
390
|
-
* traverses `..`: the same absolute path, `..` UNCOLLAPSED ({@link OperandSpellings.spelled}). A fold
|
|
391
|
-
* names a different file whenever a parent step follows a symlink — `<root>/link/../x` is
|
|
392
|
-
* `dirname(realpath(link))/x` to the kernel, `<root>/x` to the text. Absent when nothing traverses
|
|
393
|
-
* `..`, and never a replacement: a resolving caller judges these BESIDE the folded entries, list for
|
|
394
|
-
* list. Containment, deny judgment and every `reason` here stay decided on the FOLDED form — this file
|
|
395
|
-
* is zero-I/O, and only a caller holding the filesystem can tell the two apart. */
|
|
396
|
-
checkedSpellings?: readonly string[];
|
|
397
|
-
/** The kernel spellings of {@link undecidedPaths} — see {@link checkedSpellings}. */
|
|
398
|
-
undecidedSpellings?: readonly string[];
|
|
399
|
-
/** The kernel spellings of {@link outOfRootPaths} — see {@link checkedSpellings}. A caller GRANTING
|
|
400
|
-
* anything off a resolved out-of-root path judges this beside it: the two can disagree about whether
|
|
401
|
-
* the target is back inside. */
|
|
402
|
-
outOfRootSpellings?: readonly string[];
|
|
403
|
-
/**
|
|
404
|
-
* Operands (written spellings) the walk could NOT resolve at all — a `~user` form, a `~` under no declared
|
|
405
|
-
* home, an argument the shell rewrites (brace/variable/command expansion), a `cd` target it cannot follow
|
|
406
|
-
* (`cd -`, bare `cd`, a pattern). The STRUCTURAL form of the unresolvable sentence: `reason` carries the
|
|
407
|
-
* first one's sentence, this member every one of them, and both survive whatever other sentence (a shape
|
|
408
|
-
* refusal) takes precedence. CONSUMER CONTRACT: non-empty ⇒ the boundary could not read where the command
|
|
409
|
-
* reads — a fail-closed ask, never a vouch (plain on the classify seat, whose classifier asks beside it; MANDATED on the
|
|
410
|
-
* boundary-only seat, where nothing else would ask).
|
|
411
|
-
*/
|
|
412
|
-
unresolvedOperands?: readonly string[];
|
|
413
|
-
/**
|
|
414
|
-
* The grammar gate refused the WHOLE command before the walk ran (an escape, a substitution, a subshell, a
|
|
415
|
-
* redirection, a line break — see {@link rejectedSansRedirection} and the compound gate): no operand was judged,
|
|
416
|
-
* and what the shell would run is not knowable from the text (a reader can be spelled `ca\t`, fed by `<`, or hidden
|
|
417
|
-
* in `$(…)`). CONSUMER CONTRACT: present ⇒ the boundary judged nothing — MANDATED on both probe seats (a name-reading
|
|
418
|
-
* arm or a stored allow rule must not retire a read nobody judged). Structure, never the sentence.
|
|
419
|
-
*/
|
|
420
|
-
refusedWhole?: true;
|
|
421
|
-
/**
|
|
422
|
-
* Operands of a RECURSIVE/EXPANDING read form (`grep -r`, `ls -R`, `du`, … — see
|
|
423
|
-
* {@link RECURSIVE_READ_FORMS}) judged with a {@link BashReadonlyRootBoundary.denyMatch} seat wired.
|
|
424
|
-
* The judge sees only the operand's own resolved spelling, but a recursive verb reads the operand's whole
|
|
425
|
-
* SUBTREE — `grep -r x /home/user` touches `/home/user/.ssh/*` while the judged spelling matches nothing.
|
|
426
|
-
* The traversal's reach set is therefore not covered by the lexical check at all, and with zero I/O
|
|
427
|
-
* "provably not a directory" does not exist — so every such operand is UNDECIDED (no directory guessing,
|
|
428
|
-
* no bounded pre-check: both would be a false "resolved, inside" of exactly the kind
|
|
429
|
-
* {@link undecidedPaths} exists to prevent).
|
|
430
|
-
*
|
|
431
|
-
* Subset of {@link undecidedPaths} (same consumer contract: ask, never auto-allow); carried separately so
|
|
432
|
-
* a consumer minting prose can name the recursive-reach cause rather than the glob one. Minted ONLY when
|
|
433
|
-
* `denyMatch` is wired: without a judge there is nothing the traversal bypasses. EVERY face wiring the
|
|
434
|
-
* seat sees this field, the `bash_readonly` shell included, and answers it by its OWN rule for a demotion
|
|
435
|
-
* — that face has no approval channel, so what it cannot establish it REFUSES (an unbounded traversal is
|
|
436
|
-
* the same "the read set is not in the text" finding as a pattern the expander cannot enumerate).
|
|
437
|
-
*/
|
|
438
|
-
recursiveReadPaths?: readonly string[];
|
|
439
|
-
/**
|
|
440
|
-
* The ONE path whose addition to the declared roots would make every objection THIS WALK raised go away,
|
|
441
|
-
* MEASURED not argued: the walk runs again with the proposed root in place and the value is offered only
|
|
442
|
-
* if that walk has no objection left (`..` spellings are excluded on their own account — a folded-vs-kernel
|
|
443
|
-
* disagreement no replay of the text settles). It rides the containment sentence, and a per-segment NAME
|
|
444
|
-
* refusal ({@link segmentNameRefusal}), which is not a refusal of the READ; a SHAPE refusal takes it with
|
|
445
|
-
* it. VALUE: the deepest directory PROPERLY containing every {@link outOfRootPaths} entry, folded (zero
|
|
446
|
-
* I/O, so a directory contributes its PARENT); where that would contain the declared home or a root, the
|
|
447
|
-
* single path read is offered VERBATIM instead ({@link outOfRootGrantCoversExactPath}) — `cat
|
|
448
|
-
* ~/.gitconfig` is one file and the alternative is a card with no way out. Contract:
|
|
449
|
-
* {@link import("../../core/tool-spec.js").ReadRootGrantCandidate}.
|
|
450
|
-
*/
|
|
451
|
-
outOfRootGrantCandidate?: string;
|
|
452
|
-
/** {@link outOfRootGrantCandidate} is the path the command NAMED, offered verbatim, rather than a
|
|
453
|
-
* directory containing it — minted where that directory would have handed out the home or the workspace.
|
|
454
|
-
* A consumer adds the string ITSELF; expanding it into a directory pattern is what this prevents. */
|
|
455
|
-
outOfRootGrantCoversExactPath?: true;
|
|
456
|
-
/** The verdict's {@link reason} is a per-segment NAME refusal (an unlisted program, a path-prefixed name,
|
|
457
|
-
* a control word before a reader) rather than the walk's own sentence or a refused SHAPE. */
|
|
458
|
-
segmentNameRefusal?: true;
|
|
459
320
|
}
|
|
460
321
|
/**
|
|
461
322
|
* The single minting point for the out-of-root-read approval option text, so a gate rendering
|
|
@@ -468,18 +329,6 @@ export interface CompoundReadonlyVerdict {
|
|
|
468
329
|
* uses the host's native separator.
|
|
469
330
|
*/
|
|
470
331
|
export declare function formatOutOfRootReadApprovalOption(directory: string): string;
|
|
471
|
-
/**
|
|
472
|
-
* The deepest directory containing both, or `undefined` when they share none (two path families).
|
|
473
|
-
*
|
|
474
|
-
* LINEAR, and that is the point: both paths are already folded, so the answer is their common prefix cut
|
|
475
|
-
* at a separator. Climbing one directory at a time re-folds the whole remaining path each step — the same
|
|
476
|
-
* answer at quadratic cost, measured at 4.9 s of synchronous work for one 32 KB command naming a
|
|
477
|
-
* 16 000-segment operand, on a walk that runs BEFORE the gate arms its probe deadline. The family-aware
|
|
478
|
-
* comparator keeps the last word: a prefix it cannot confirm contains both paths yields no candidate.
|
|
479
|
-
* Exported for its unit tests — the family confirmation is reachable from a command only through shapes
|
|
480
|
-
* the compound grammar refuses first, so a command-level pin cannot tell a live guard from an absent one.
|
|
481
|
-
*/
|
|
482
|
-
export declare function deepestCommonDir(a: string, b: string): string | undefined;
|
|
483
332
|
/** Programs whose operands are NEVER filesystem paths, so no operand of theirs is checked:
|
|
484
333
|
* `echo`'s words are literal text, `which`'s are PATH lookups, `tr`'s are character sets, and
|
|
485
334
|
* `basename`/`dirname` do pure string surgery on a path without ever opening it. Everything else —
|
|
@@ -550,27 +399,6 @@ export declare function resolveOperandSpellings(base: string | undefined, operan
|
|
|
550
399
|
* exactly as `sh` does, and a deployment that meets one of them meets the whole member. GNU `parallel` is a member
|
|
551
400
|
* for the reason it is not a launcher: it hands a BUILT command line to a shell. */
|
|
552
401
|
export declare const SHELL_REENTRY_PROGRAM_RE: RegExp;
|
|
553
|
-
/** Whether a command the grammar gate REFUSED WHOLE may READ a file the boundary never judged. The answer is the
|
|
554
|
-
* complement of a WHITELIST: the command is exempt only when every part of it is a shape the scan fully understands
|
|
555
|
-
* and none of those shapes reads a file outside the walk's reach — everything else is read evidence, so a token the
|
|
556
|
-
* scan does not understand mandates rather than passes. Exempt shapes, per segment: a program that reads no path
|
|
557
|
-
* (`echo`, `python3 -`, `tee out.txt`, an unlisted program); `cat` / `wc` with payload-free flags whose input is a
|
|
558
|
-
* here-document, a here-string or a literal in-root stdin file; a directory change (which then makes every relative
|
|
559
|
-
* stdin literal in the command unjudged). A substitution body (`$(…)`, backticks, `<(…)`) is judged on its own: green
|
|
560
|
-
* under the compound walk, or exempt under this whitelist; a here-document body is text, not commands. */
|
|
561
|
-
export declare function refusedCommandMayRead(command: string, allow: ReadonlySet<string>, boundary: BashReadonlyRootBoundary | undefined,
|
|
562
|
-
/** The {@link ShellScanDecline} members THIS SEAT reads as evidence (tables and argument: `fs-bash.ts`); one
|
|
563
|
-
* the table omits ends the scan with "exempt". */
|
|
564
|
-
declinesAreEvidence: ReadonlySet<ShellScanDecline>): RefusedCommandScan;
|
|
565
|
-
/** What ONE scan of a refused command found — TWO facts, because a seat's table answers only the first. `mayRead`
|
|
566
|
-
* is the whitelist's verdict ("may read a file the boundary never judged"), which a seat turns into a MANDATE.
|
|
567
|
-
* `declined` is the scan's own report that it could not judge where a segment reads AT ALL
|
|
568
|
-
* ({@link SHELL_SCAN_DECLINES}), recorded BEFORE the seat's table and so the SAME on every seat: a seat omitting
|
|
569
|
-
* that decline has ruled only that an ANSWERER may retire the ask, never that nothing happened. */
|
|
570
|
-
export interface RefusedCommandScan {
|
|
571
|
-
readonly mayRead: boolean;
|
|
572
|
-
readonly declined: boolean;
|
|
573
|
-
}
|
|
574
402
|
/**
|
|
575
403
|
* Does this RAW token carry a metacharacter the shell EXPANDS, unquoted?
|
|
576
404
|
*
|
|
@@ -866,6 +694,9 @@ export interface SedScriptNames {
|
|
|
866
694
|
readonly writes: readonly string[];
|
|
867
695
|
readonly commands: readonly string[];
|
|
868
696
|
readonly executesPatternSpace: boolean;
|
|
697
|
+
/** Why the script's names are NOT all here, when they are not: the script is supplied from a FILE whose text is not
|
|
698
|
+
* in the command (one reading), or no reading could parse it to its end. Absent when every name is listed. */
|
|
699
|
+
readonly unread?: string;
|
|
869
700
|
}
|
|
870
701
|
/**
|
|
871
702
|
* Does this verb's SCRIPT SLOT put a file name or a command line somewhere the walk's operand reading
|
|
@@ -1037,83 +868,12 @@ export declare function foldHomeVariableSpelling(command: string, homeDir: strin
|
|
|
1037
868
|
* out-of-root signal; the boundary scan below only ever converts a would-be READ-ONLY verdict into a
|
|
1038
869
|
* demotion, which is why omitting `boundary` reproduces the name-only behaviour exactly.
|
|
1039
870
|
*/
|
|
1040
|
-
export declare function classifyCompoundReadonlyDetailed(command: string, allow: ReadonlySet<string>,
|
|
1041
|
-
/** The
|
|
1042
|
-
*
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
* against its real per-iteration base, so the escape a climbing loop opens is still caught. */
|
|
1046
|
-
opts?: {
|
|
1047
|
-
readonly iterated?: boolean;
|
|
871
|
+
export declare function classifyCompoundReadonlyDetailed(command: string, allow: ReadonlySet<string>,
|
|
872
|
+
/** The environment's declared home, so the `$HOME` / `${HOME}` word spellings fold the way the read station
|
|
873
|
+
* folds them before the shape is read. Nothing else is read off it: WHERE a command reads is the station's. */
|
|
874
|
+
boundary?: {
|
|
875
|
+
readonly homeDir?: string;
|
|
1048
876
|
}): CompoundReadonlyVerdict;
|
|
1049
|
-
/**
|
|
1050
|
-
* The read boundary ALONE, for the `bash_readonly` face.
|
|
1051
|
-
*
|
|
1052
|
-
* `bash_readonly` declares `effect:"read"` and is admitted by {@link coarseReadonlyCheck}, an argv[0]-NAME
|
|
1053
|
-
* filter: path operands were invisible to it, so an allowlisted reader could name any file on the host
|
|
1054
|
-
* (`cat /etc/passwd`) and — with no approval gate on the read effect — get it. The compound face above
|
|
1055
|
-
* answers a different question for a different consumer (a gate that can ASK); this face answers only
|
|
1056
|
-
* "does this command read outside the roots", leaving the availability arms (blocking stdin, `tail -f`,
|
|
1057
|
-
* device reads) untouched so a caller wiring it gains the boundary and nothing else.
|
|
1058
|
-
*
|
|
1059
|
-
* PRECONDITION: `command` has already passed {@link coarseReadonlyCheck}, i.e. it is ONE simple command
|
|
1060
|
-
* with no connectors, redirects, substitutions or escapes anywhere. That is what makes a whitespace split
|
|
1061
|
-
* exact here. Feeding it an unvetted compound is not unsafe (the connector ends up glued to a token, which
|
|
1062
|
-
* only ever produces MORE path candidates and a stricter verdict) but the reason text would name a
|
|
1063
|
-
* nonsense command, so callers vet first.
|
|
1064
|
-
*
|
|
1065
|
-
* Returns the same {@link CompoundReadonlyVerdict} the compound face returns: `reason === undefined` means
|
|
1066
|
-
* every path operand resolved inside the roots. A caller with no approval channel treats BOTH demotion
|
|
1067
|
-
* kinds — resolved-and-outside (`outOfRootRead`, with `outOfRootPaths`) and could-not-resolve (reason
|
|
1068
|
-
* only) — as a refusal; there is nowhere to degrade to.
|
|
1069
|
-
*
|
|
1070
|
-
* SCOPE OF THE GUARANTEE (both directions): "inside the roots" is LEXICAL containment — see
|
|
1071
|
-
* {@link resolveOperandLexically}. A symlink inside a root that points out of it reads as in-root here,
|
|
1072
|
-
* so this face bounds where a command may NAME a file, not where the kernel will ultimately take it.
|
|
1073
|
-
* A deployment that needs filesystem-true containment resolves the operands itself, and this
|
|
1074
|
-
* verdict hands it the list to resolve ({@link CompoundReadonlyVerdict.checkedPaths}, on both arms).
|
|
1075
|
-
* The engine's own shell legs are that caller on a local env — the read-only face before it runs an
|
|
1076
|
-
* allowlisted command, the full face after its gate allowed one.
|
|
1077
|
-
*/
|
|
1078
|
-
export declare function classifySimpleCommandReadBoundary(command: string, boundary: BashReadonlyRootBoundary): CompoundReadonlyVerdict;
|
|
1079
|
-
/**
|
|
1080
|
-
* What {@link classifyOutOfRootReadGate} answers. Two members and one rule between them:
|
|
1081
|
-
* `paths` is EVIDENCE for a `gated` answer, never the answer itself. A caller decides on `gated`.
|
|
1082
|
-
*/
|
|
1083
|
-
export interface OutOfRootReadGateVerdict {
|
|
1084
|
-
/** True ⇔ this command is structurally gated on the read boundary. */
|
|
1085
|
-
readonly gated: boolean;
|
|
1086
|
-
/** The RESOLVED out-of-root paths behind a `gated` answer. EMPTY when the gate fired for a reason
|
|
1087
|
-
* that resolves none — a working-directory move this walk cannot follow, after which every relative
|
|
1088
|
-
* operand is placed against a base bash has already left, so no path can be named honestly. Read it
|
|
1089
|
-
* as display/triage evidence; emptiness is not "not gated". */
|
|
1090
|
-
readonly paths: readonly string[];
|
|
1091
|
-
/** Did the deployment's DENY judge speak about an operand this command names? The other half of the same
|
|
1092
|
-
* question, over the very same readers, in a second pass WITH the deny judge — which is what lets the
|
|
1093
|
-
* containment answer above stay deny-free and keep its `paths` contract. Absent ⇔ it said nothing.
|
|
1094
|
-
*
|
|
1095
|
-
* It carries the FINDING, not a boolean: this is the arm a command the grammar gate refused WHOLE
|
|
1096
|
-
* (`cat .env > out.txt`, a subshell, a substitution) reaches, and there the compound verdict carries no
|
|
1097
|
-
* deny fields at all — so a caller disclosing the hit has nowhere else to read the path and the pattern
|
|
1098
|
-
* from, and said nothing. The second pass already computed both; throwing them away was the whole bug. */
|
|
1099
|
-
readonly denied?: {
|
|
1100
|
-
readonly path: string;
|
|
1101
|
-
readonly pattern: string;
|
|
1102
|
-
};
|
|
1103
|
-
/** The reader operands this pass RESOLVED and placed inside the roots, plus the expansions it parsed but
|
|
1104
|
-
* cannot enumerate — the candidates an execute-time recheck must canonicalize.
|
|
1105
|
-
*
|
|
1106
|
-
* Published because this pass reads SHAPES the compound walk hands nothing out for: a launcher whose
|
|
1107
|
-
* program it cannot name (`env -- cat f`, `sudo -u root cat f`), a stream-fed one (`… | xargs cat f`).
|
|
1108
|
-
* There the compound verdict carries no candidates at all, so the recheck had nothing to resolve and an
|
|
1109
|
-
* in-root symlink to a deny-listed target RAN after an approval — while the same read spelled without
|
|
1110
|
-
* the launcher was refused. Same operands, same judge, one more pass allowed to hand them over.
|
|
1111
|
-
*
|
|
1112
|
-
* NOT the out-of-root ones: those ride {@link paths}, they are what the boundary RAISED, and a leg that
|
|
1113
|
-
* re-refused them would be a second adjudicator of a decision already made. */
|
|
1114
|
-
readonly checked: readonly string[];
|
|
1115
|
-
readonly undecided: readonly string[];
|
|
1116
|
-
}
|
|
1117
877
|
/** Working-directory moves {@link evaluateReadBoundary} does NOT model. `cd` is deliberately absent —
|
|
1118
878
|
* it IS modelled (the walk threads the base through it), which is the whole difference: a move the
|
|
1119
879
|
* walk follows keeps the verdict decidable, and one it cannot follow does not. */
|
|
@@ -1124,61 +884,6 @@ export declare const UNMODELLED_BASE_MOVERS: ReadonlySet<string>;
|
|
|
1124
884
|
* does NOT look through a launcher: there every segment whose program is not a listed reader is judged whole,
|
|
1125
885
|
* launcher or not, so looking through one would only narrow what it reads. */
|
|
1126
886
|
export declare const COMMAND_LAUNCHERS_FOR_TEST: ReadonlySet<string>;
|
|
1127
|
-
/**
|
|
1128
|
-
* The OUT-OF-ROOT READ GATE asked as its OWN question: does any listed reader in this command
|
|
1129
|
-
* name a path outside the allowed directories?
|
|
1130
|
-
*
|
|
1131
|
-
* {@link classifyCompoundReadonlyDetailed} answers a different question — "is this whole command
|
|
1132
|
-
* auto-allowable?" — and it stops at the FIRST obstacle. That makes its `outOfRootRead` signal precise
|
|
1133
|
-
* for the shape it was built for and silent for three shapes where the containment fact is just as
|
|
1134
|
-
* true: a compound whose OTHER segment is not a listed reader (`cat <outside> | curl …` refuses on the
|
|
1135
|
-
* unlisted name and never reaches the boundary walk at all), an operand that ALSO matches the
|
|
1136
|
-
* sensitive-path deny judge (whose more-specific reason short-circuits the walk), and a recursive form
|
|
1137
|
-
* whose traversal root sits outside. This face answers the containment question in all of them, so a
|
|
1138
|
-
* consumer that must treat "reads outside the roots" as a STRUCTURAL gate rather than as one possible
|
|
1139
|
-
* reason for a demotion has one predicate to read.
|
|
1140
|
-
*
|
|
1141
|
-
* What it judges and what it does not:
|
|
1142
|
-
* · EVERY segment, under one of two readings. A segment whose PROGRAM is in `allow` — the deployment's
|
|
1143
|
-
* own read-command table, the analogue of the reference implementation's path-restricted command
|
|
1144
|
-
* list — is read with that verb's own slots (a pattern operand, a recursive form). The program is
|
|
1145
|
-
* read after the two decorations the shell itself strips before choosing it (leading `NAME=value`
|
|
1146
|
-
* assignments, a path prefix), so `/bin/cat` is judged as `cat`: recognizing readers by their literal
|
|
1147
|
-
* spelling alone let a rule the persisted grammar admits (`Bash(/bin/cat:*)`) carry the exact read
|
|
1148
|
-
* this gate exists for. Every OTHER segment is judged whole and verb-blind — its tokens are offered
|
|
1149
|
-
* as path candidates under a head with no per-verb behaviour — because a program not on the table is
|
|
1150
|
-
* not a program known to read nothing: skipping it made the gate silent for every reader the
|
|
1151
|
-
* deployment had not enumerated, which is the one thing a name table cannot enumerate.
|
|
1152
|
-
* · `cd` is judged whether or not the deployment listed it, because the walk MODELS it (it threads the
|
|
1153
|
-
* base across segments) and dropping it would place every later relative operand against a base bash
|
|
1154
|
-
* has already left. A move the walk cannot model (`pushd`/`popd`) is not silently tolerated either —
|
|
1155
|
-
* see the `paths`-less gated answer below.
|
|
1156
|
-
* · BOTH halves from ONE extraction, in TWO passes: the CONTAINMENT pass alone is deny-free, because
|
|
1157
|
-
* {@link BashReadonlyRootBoundary.denyMatch} outranks containment inside the walk and returns early,
|
|
1158
|
-
* which would let a deny-listed operand HIDE the fact that it also sits outside the roots. A SECOND
|
|
1159
|
-
* pass over the SAME readers does carry that judge, and is what answers `denied` (see below).
|
|
1160
|
-
* · `face: "open"` is honoured: under the open read face containment is structurally satisfied, an
|
|
1161
|
-
* out-of-roots operand is not a finding, and this gate is silent — a full-read deployment sees no
|
|
1162
|
-
* gate here, by the same one mechanism the face already defines.
|
|
1163
|
-
*
|
|
1164
|
-
* Answers `gated` with the resolved paths behind it, and NOT gated for every whole-string refusal (an
|
|
1165
|
-
* unparseable grammar, a substitution, a redirection) and for an operand that cannot be resolved
|
|
1166
|
-
* statically. That silence is the honest answer, not a miss: those shapes name no concrete directory,
|
|
1167
|
-
* so there is nothing for a path gate to be about.
|
|
1168
|
-
*
|
|
1169
|
-
* It deliberately does NOT reuse {@link CompoundReadonlyVerdict}: one arm of this face is gated while
|
|
1170
|
-
* resolving no path at all (the unmodelled working-directory move), and that arm cannot fill an
|
|
1171
|
-
* `outOfRootPaths` whose contract says the entries ARE resolved out-of-root paths. A result type that
|
|
1172
|
-
* can say "gated, and here is nothing" is the one that lets the arm exist without lying.
|
|
1173
|
-
*
|
|
1174
|
-
* The bounded poll-loop grammar is judged through its BODY, the same text that face re-classifies —
|
|
1175
|
-
* without it, the one control structure this file auto-allows would be the one shape whose reads no
|
|
1176
|
-
* gate looks at. The body is judged in a single pass (the per-iteration `cd` accumulation the
|
|
1177
|
-
* auto-allow face models is not replicated here): a loop that climbs out of the roots over its beats
|
|
1178
|
-
* is refused by that face on its own terms, and under-reporting here can only cost a gate, never grant
|
|
1179
|
-
* one.
|
|
1180
|
-
*/
|
|
1181
|
-
export declare function classifyOutOfRootReadGate(command: string, allow: ReadonlySet<string>, boundary: BashReadonlyRootBoundary): OutOfRootReadGateVerdict;
|
|
1182
887
|
/**
|
|
1183
888
|
* Compound read-only classification, reason-only face. Returns the demotion reason, or
|
|
1184
889
|
* undefined when the command classifies read-only. ⚠️ `undefined` is NOT "safe to auto-execute":
|
|
@@ -1190,7 +895,9 @@ export declare function classifyOutOfRootReadGate(command: string, allow: Readon
|
|
|
1190
895
|
* detailed face when the caller wants to know WHY, e.g. to offer the narrow "allow reading from
|
|
1191
896
|
* <dir>" approval); without it the verdict is exactly what it always was.
|
|
1192
897
|
*/
|
|
1193
|
-
export declare function classifyCompoundReadonly(command: string, allow: ReadonlySet<string>, boundary?:
|
|
898
|
+
export declare function classifyCompoundReadonly(command: string, allow: ReadonlySet<string>, boundary?: {
|
|
899
|
+
readonly homeDir?: string;
|
|
900
|
+
}): string | undefined;
|
|
1194
901
|
/**
|
|
1195
902
|
* Iteration cap for {@link classifyBoundedReadonlyPollLoop}. The bound must be a LITERAL in the
|
|
1196
903
|
* command text (that is what makes "bounded" provable without executing anything); this cap keeps a
|
|
@@ -1287,7 +994,9 @@ export declare const POLL_LOOP_MAX_SLEEP_SECONDS = 600;
|
|
|
1287
994
|
* consequence case by case, the whole command is rejected up front if it carries any such character
|
|
1288
995
|
* ({@link NON_ASCII_WHITESPACE}), so the grammar's tokenization and bash's stay identical.
|
|
1289
996
|
*/
|
|
1290
|
-
export declare function classifyBoundedReadonlyPollLoop(command: string, allow: ReadonlySet<string>, boundary?:
|
|
997
|
+
export declare function classifyBoundedReadonlyPollLoop(command: string, allow: ReadonlySet<string>, boundary?: {
|
|
998
|
+
readonly homeDir?: string;
|
|
999
|
+
}): string | undefined;
|
|
1291
1000
|
/**
|
|
1292
1001
|
* The poll-loop face's STRUCTURED verdict — the same {@link CompoundReadonlyVerdict} the compound face
|
|
1293
1002
|
* returns, so a consumer reads the boundary's structural findings (deny row, containment, recursive or
|
|
@@ -1296,4 +1005,6 @@ export declare function classifyBoundedReadonlyPollLoop(command: string, allow:
|
|
|
1296
1005
|
* projection. A refusal of the LOOP GRAMMAR itself (not a bounded loop, an unlisted body program, an
|
|
1297
1006
|
* unvetted sleep) carries no structure: no reader segment was walked.
|
|
1298
1007
|
*/
|
|
1299
|
-
export declare function classifyBoundedReadonlyPollLoopDetailed(command: string, allow: ReadonlySet<string>, boundary?:
|
|
1008
|
+
export declare function classifyBoundedReadonlyPollLoopDetailed(command: string, allow: ReadonlySet<string>, boundary?: {
|
|
1009
|
+
readonly homeDir?: string;
|
|
1010
|
+
}): CompoundReadonlyVerdict;
|