approval-md 0.4.5 → 0.4.7
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/SPEC.md +24 -3
- package/dist/src/channels/contract.d.ts +15 -0
- package/dist/src/channels/contract.js +23 -1
- package/dist/src/channels/contract.js.map +1 -1
- package/dist/src/channels/telegram.d.ts +180 -33
- package/dist/src/channels/telegram.js +256 -87
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/channel-telegram.d.ts +114 -35
- package/dist/src/cli/channel-telegram.js +259 -120
- package/dist/src/cli/channel-telegram.js.map +1 -1
- package/dist/src/cli/doctor.js +33 -13
- package/dist/src/cli/doctor.js.map +1 -1
- package/dist/src/cli/help.d.ts +2 -0
- package/dist/src/cli/help.js +39 -3
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hook-exec-code.d.ts +150 -0
- package/dist/src/cli/hook-exec-code.js +357 -0
- package/dist/src/cli/hook-exec-code.js.map +1 -0
- package/dist/src/cli/hook.d.ts +154 -1
- package/dist/src/cli/hook.js +2344 -111
- package/dist/src/cli/hook.js.map +1 -1
- package/dist/src/cli/instructions.js +9 -6
- package/dist/src/cli/instructions.js.map +1 -1
- package/dist/src/cli/main.js +9 -0
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/study.d.ts +22 -0
- package/dist/src/cli/study.js +153 -0
- package/dist/src/cli/study.js.map +1 -0
- package/dist/src/cli/verb-registry.d.ts +14 -0
- package/dist/src/cli/verb-registry.js +37 -0
- package/dist/src/cli/verb-registry.js.map +1 -1
- package/dist/src/cli/wordmark.d.ts +2 -2
- package/dist/src/cli/wordmark.js +2 -2
- package/dist/src/core/command-class.d.ts +349 -1
- package/dist/src/core/command-class.js +2347 -87
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/exec-code.d.ts +77 -0
- package/dist/src/core/exec-code.js +60 -0
- package/dist/src/core/exec-code.js.map +1 -0
- package/dist/src/core/gate.d.ts +38 -0
- package/dist/src/core/gate.js +26 -0
- package/dist/src/core/gate.js.map +1 -1
- package/dist/src/core/git-push-argv.d.ts +111 -0
- package/dist/src/core/git-push-argv.js +375 -0
- package/dist/src/core/git-push-argv.js.map +1 -0
- package/dist/src/core/harness-version.d.ts +71 -30
- package/dist/src/core/harness-version.js +101 -34
- package/dist/src/core/harness-version.js.map +1 -1
- package/dist/src/core/policy-load.d.ts +8 -0
- package/dist/src/core/policy-load.js.map +1 -1
- package/dist/src/core/sender-identity.d.ts +13 -1
- package/dist/src/core/sender-identity.js +15 -0
- package/dist/src/core/sender-identity.js.map +1 -1
- package/dist/src/core/study-withdraw.d.ts +137 -0
- package/dist/src/core/study-withdraw.js +280 -0
- package/dist/src/core/study-withdraw.js.map +1 -0
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/dist/src/mcp/server.js +1 -1
- package/dist/src/mcp/server.js.map +1 -1
- package/docs/README-extended.md +3 -2
- package/docs/cli-reference.md +83 -7
- package/package.json +2 -2
- package/schema/event.schema.json +213 -1
- package/schema/fixtures/event/invalid/approval-granted-study.json +23 -0
- package/schema/fixtures/event/invalid/approval-rejected-looks-wrong-cli-channel.json +23 -0
- package/schema/fixtures/event/invalid/approval-rejected-looks-wrong-operator.json +24 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-extra-key.json +24 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-unknown-reason.json +23 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-email-operator.json +20 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-no-operator.json +19 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-no-ref.json +19 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-wrong-actor.json +20 -0
- package/schema/fixtures/event/invalid/approval-rejected-study-withdrawn-wrong-channel.json +20 -0
- package/schema/fixtures/event/invalid/approval-revoked-study.json +20 -0
- package/schema/fixtures/event/valid/approval-rejected-looks-wrong.json +23 -0
- package/schema/fixtures/event/valid/approval-rejected-study-withdrawn.json +20 -0
- package/schema/fixtures/policy/invalid/exec-code-custody-boolean.json +11 -0
- package/schema/fixtures/policy/invalid/exec-code-execute-code-value.json +11 -0
- package/schema/fixtures/policy/invalid/exec-code-unknown-key.json +12 -0
- package/schema/fixtures/policy/valid/exec-code-both.json +17 -0
- package/schema/fixtures/policy/valid/exec-code-custody.json +11 -0
- package/schema/policy.schema.json +23 -0
|
@@ -82,7 +82,32 @@ export interface ClassifiedSegment {
|
|
|
82
82
|
* profile is the caller's, and a profile a caller wrote can allow anything.
|
|
83
83
|
*/
|
|
84
84
|
sandbox?: SandboxWrapper;
|
|
85
|
+
/**
|
|
86
|
+
* The program an `exec.code` segment runs, present only on such a segment
|
|
87
|
+
* (APRV-510). Text only, like everything this file returns: the hook hashes
|
|
88
|
+
* the inline source, or reads and hashes the file, and binds the hash and the
|
|
89
|
+
* length, never the text (SPEC.md §11.1 invariant 3).
|
|
90
|
+
*/
|
|
91
|
+
execCode?: ExecCodeSource;
|
|
85
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* What an `exec.code` segment runs, as the classifier read it (APRV-510).
|
|
95
|
+
*
|
|
96
|
+
* `source` is the inline program (`python3 -c <source>`, `node -e <source>`,
|
|
97
|
+
* `deno eval <source>`) or the file path as written (`python3 <source>`,
|
|
98
|
+
* `bun <source>`). `argv` is the segment's argv from the binary on, verbatim,
|
|
99
|
+
* and `sourceIndex` names the word of `argv` that carries `source`, so a
|
|
100
|
+
* caller can bind the argv with that one word replaced by its hash.
|
|
101
|
+
*/
|
|
102
|
+
export interface ExecCodeSource {
|
|
103
|
+
interpreter: string;
|
|
104
|
+
mode: ExecCodeMode;
|
|
105
|
+
source: string;
|
|
106
|
+
argv: string[];
|
|
107
|
+
sourceIndex: number;
|
|
108
|
+
}
|
|
109
|
+
/** How an `exec.code` segment carries its program (APRV-510). */
|
|
110
|
+
export type ExecCodeMode = "inline" | "file";
|
|
86
111
|
/**
|
|
87
112
|
* Who wrote the profile a wrapped command runs under (APRV-193).
|
|
88
113
|
*
|
|
@@ -222,6 +247,35 @@ interface ProtectedEntry {
|
|
|
222
247
|
* skipped validation.
|
|
223
248
|
*/
|
|
224
249
|
export declare function parseProtectedEntry(entry: ProtectedPathEntry): ProtectedEntry | null;
|
|
250
|
+
/**
|
|
251
|
+
* The words bash's brace expansion makes of `word`, or `null` when there would
|
|
252
|
+
* be more than {@link BRACE_EXPANSION_CAP}, or making them would take more than
|
|
253
|
+
* {@link BRACE_EXPANSION_STEPS} expansions (APRV-511 refuter M2, recheck S(new)).
|
|
254
|
+
*
|
|
255
|
+
* A group is `{` … `}` holding a top-level comma (`{a,b}`, `{,s}`) or a
|
|
256
|
+
* sequence (`{1..3}`, `{a..c}`, `{1..9..2}`); a `{` right after `$` opens a
|
|
257
|
+
* parameter expansion and is never a group, and anything else stays literal,
|
|
258
|
+
* as it does in bash. Groups nest and multiply. The lexer has already removed
|
|
259
|
+
* quoting, so a quoted brace pair reads as a group too, which can only make an
|
|
260
|
+
* answer stricter.
|
|
261
|
+
*
|
|
262
|
+
* The expansion is iterative, never recursive: a work list holds the words
|
|
263
|
+
* still carrying a group, and both bounds are tested BEFORE a word is
|
|
264
|
+
* expanded further, so no input reaches the stack limit or runs unbounded.
|
|
265
|
+
* Every caller reads `null` as the stricter class.
|
|
266
|
+
*/
|
|
267
|
+
export declare function braceExpansions(word: string): string[] | null;
|
|
268
|
+
/**
|
|
269
|
+
* The words a path word may stand for once bash has expanded its brace groups,
|
|
270
|
+
* plus, for any word that still carries a stray `{` or `}` outside a parameter
|
|
271
|
+
* expansion, the same word with the stray braces removed (APRV-511 review).
|
|
272
|
+
* Bash keeps a stray brace literally (`{~/.hermes/.op.env` names a directory
|
|
273
|
+
* called `{~`), but a word whose only distance from an organ or a credential is
|
|
274
|
+
* a stray brace is judged as that organ: the stricter of the two readings.
|
|
275
|
+
* `null` when the expansion exceeds {@link BRACE_EXPANSION_CAP}, unless every
|
|
276
|
+
* group is a sequence or list of plain numbers ({@link numericRepresentative}).
|
|
277
|
+
*/
|
|
278
|
+
export declare function braceCandidates(word: string): string[] | null;
|
|
225
279
|
/**
|
|
226
280
|
* Which protected class does this path name, if any? (APRV-198.)
|
|
227
281
|
*
|
|
@@ -384,6 +438,58 @@ export declare const NON_SECRET_ENV_NAMES: readonly string[];
|
|
|
384
438
|
* environment rather than of command text, and it must ask it the same way.
|
|
385
439
|
*/
|
|
386
440
|
export declare function isSecretEnvName(name: string): boolean;
|
|
441
|
+
/**
|
|
442
|
+
* Does this git argv only WRITE the paths it names? (APRV-522.) `true` only
|
|
443
|
+
* when the verb is read unambiguously, is one of
|
|
444
|
+
* {@link GIT_PATH_WRITE_OPTIONS}, and every option before `--` is on that
|
|
445
|
+
* verb's allow-list.
|
|
446
|
+
*
|
|
447
|
+
* The verb is found the way APRV-520's {@link gitGlobalRefusal} reads the
|
|
448
|
+
* globals: zero-value globals are skipped, and ANY other flag before the verb
|
|
449
|
+
* (a global that takes a value, `-C dir`, `-c k=v`, `--git-dir x`, a joined
|
|
450
|
+
* `--work-tree=`, or one this table does not know) leaves the verb unknown, so
|
|
451
|
+
* the argv is a READER. `classifySegment` refuses those globals anyway; this
|
|
452
|
+
* does not rely on that ordering.
|
|
453
|
+
*
|
|
454
|
+
* The options are a closed list, never a deny-list: git accepts any unique
|
|
455
|
+
* prefix of a long option (`--pat` is `--patch`), several verbs print the
|
|
456
|
+
* file's hunks (`git add -p`, `git checkout -p`, `git restore --patch`), and
|
|
457
|
+
* `git checkout-index --prefix=/tmp/` writes a copy elsewhere. So an option
|
|
458
|
+
* not listed for the verb (an abbreviation, a short letter bundled with one
|
|
459
|
+
* not listed, `-fp`, an unknown flag) makes the argv a reader, and the
|
|
460
|
+
* credential rule judges what it names.
|
|
461
|
+
*/
|
|
462
|
+
/**
|
|
463
|
+
* Where git's verb sits in its argv, read the way {@link gitWritesNamedPathsOnly}
|
|
464
|
+
* reads it (APRV-522 fix round 2): only zero-value globals may come before it,
|
|
465
|
+
* and any other flag leaves the verb unknown (`null`). The hook's credential
|
|
466
|
+
* pass uses it so that, under a directory a `CDPATH` lookup may have chosen,
|
|
467
|
+
* the verb word is not judged as a path: git never opens its verb.
|
|
468
|
+
*/
|
|
469
|
+
export declare function gitVerbIndex(args: readonly string[]): number | null;
|
|
470
|
+
/**
|
|
471
|
+
* The words of a segment the credential rule reads as paths (APRV-522), for
|
|
472
|
+
* the pure classifier and the hook's disk pass alike: one judgement, so the two
|
|
473
|
+
* cannot disagree about which words a reader opens.
|
|
474
|
+
*
|
|
475
|
+
* Empty for a binary that only WRITES what it names
|
|
476
|
+
* ({@link credentialWritesOnly}). Otherwise:
|
|
477
|
+
*
|
|
478
|
+
* - every positional (the words that are not flags, a flag's separate value
|
|
479
|
+
* included);
|
|
480
|
+
* - the value of every `--name=value` flag and of every short flag carrying
|
|
481
|
+
* its value attached (`-fFILE`), because a reader's file operand may arrive
|
|
482
|
+
* inside its own option (M6);
|
|
483
|
+
* - the file half of an upload spelling, `@FILE` and `name=@FILE;type=…`, and
|
|
484
|
+
* `name=<FILE`, which curl reads the same way;
|
|
485
|
+
* - for git, the path half of a `<rev>:<path>` or `:<path>` word, pathspec
|
|
486
|
+
* magic (`:(top)`, `:/`) stripped, because show and cat-file print the blob
|
|
487
|
+
* such a word names.
|
|
488
|
+
*
|
|
489
|
+
* Over-reading a word costs an approval prompt when it happens to spell a
|
|
490
|
+
* credential path; under-reading one costs the property.
|
|
491
|
+
*/
|
|
492
|
+
export declare function credentialReadWords(basename: string, args: readonly string[]): string[];
|
|
387
493
|
interface LexWord {
|
|
388
494
|
/** Literal text, quotes removed and backslash escapes applied. */
|
|
389
495
|
text: string;
|
|
@@ -391,6 +497,36 @@ interface LexWord {
|
|
|
391
497
|
quoted: boolean;
|
|
392
498
|
/** Inner text of each `$(…)` in this word, for recursive classification. */
|
|
393
499
|
substitutions: string[];
|
|
500
|
+
/**
|
|
501
|
+
* An UNQUOTED variable or `$(…)` is part of the word, so the shell may split
|
|
502
|
+
* its value into several words (APRV-511 fix round 3, S(open)).
|
|
503
|
+
*/
|
|
504
|
+
splits: boolean;
|
|
505
|
+
/**
|
|
506
|
+
* An UNQUOTED `{`, `*`, `?` or `[` is part of the word, so brace expansion or
|
|
507
|
+
* a glob may make it several words or change its head (fix round 3).
|
|
508
|
+
*/
|
|
509
|
+
expands: boolean;
|
|
510
|
+
/**
|
|
511
|
+
* Is `text` exactly the bytes the shell would hand the command for this
|
|
512
|
+
* word? (APRV-510.) `false` when the shell would still change it: a `$`
|
|
513
|
+
* outside single quotes (a parameter expansion), an unquoted glob or brace
|
|
514
|
+
* character, a leading unquoted `~`, or a backslash inside double quotes
|
|
515
|
+
* before a character the shell keeps it for (this lexer drops every such
|
|
516
|
+
* backslash, the shell drops only five). Read by the `exec.code` shape alone,
|
|
517
|
+
* which hashes a word and so needs the word's bytes to be the program's
|
|
518
|
+
* bytes; every other reader keeps reading `text` exactly as before.
|
|
519
|
+
*/
|
|
520
|
+
exact: boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Is this word an assignment to bash (APRV-523)? bash decides that on the
|
|
523
|
+
* word as WRITTEN, before quote removal: the name, an optional subscript, the
|
|
524
|
+
* `+` and the `=` must carry no quoting or escaping character, and the name
|
|
525
|
+
* no `$`, backtick or brace. {@link ASSIGNMENT} applied to the source
|
|
526
|
+
* spelling, not to `text`: a word whose name is quoted or escaped has the
|
|
527
|
+
* same `text` as an assignment and is a command word, the program bash runs.
|
|
528
|
+
*/
|
|
529
|
+
assignment: boolean;
|
|
394
530
|
}
|
|
395
531
|
interface LexRedirect {
|
|
396
532
|
/** `>` and `>>` write; `<` reads. */
|
|
@@ -409,7 +545,40 @@ interface LexSegment {
|
|
|
409
545
|
bodySubstitutions: string[];
|
|
410
546
|
/** Set when the segment contains a construct whose effect cannot be read. */
|
|
411
547
|
opaque: string | null;
|
|
548
|
+
/**
|
|
549
|
+
* Did this segment open a heredoc (`<<EOF`, `<<-EOF`)? (APRV-510.) The body
|
|
550
|
+
* is skipped and never classified, and an unquoted body still expands
|
|
551
|
+
* (APRV-504), so the `exec.code` shape declines a segment carrying one.
|
|
552
|
+
*/
|
|
553
|
+
heredoc: boolean;
|
|
554
|
+
/**
|
|
555
|
+
* Does this segment's stdin come from somewhere the text names as input:
|
|
556
|
+
* the previous segment of a pipeline (`a | b`, `a |& b`), or a descriptor
|
|
557
|
+
* duplicated onto it (`<&3`)? (APRV-510, security review round 2.) A program
|
|
558
|
+
* can read its real source from stdin (`echo '<src>' | python3 -c
|
|
559
|
+
* 'exec(sys.stdin.read())'`), outside the hash, so the `exec.code` shape
|
|
560
|
+
* declines a segment fed this way.
|
|
561
|
+
*/
|
|
562
|
+
stdinFed: boolean;
|
|
563
|
+
/**
|
|
564
|
+
* Is this segment inside a subshell `( … )`? (APRV-510, refuter rS2.) A
|
|
565
|
+
* redirection or heredoc written after the closing `)` feeds the stdin of
|
|
566
|
+
* every command inside it (`(python3 -c '…') < evil.py`), and lands on a
|
|
567
|
+
* segment of its own, so `stdinFed`, `heredoc` and `redirects` never reach
|
|
568
|
+
* the program's segment. The `exec.code` shape declines any segment inside a
|
|
569
|
+
* subshell at all, the stricter form of "a subshell carrying a redirection".
|
|
570
|
+
* A depth that goes negative (an unmatched `)`, as in a `case` pattern)
|
|
571
|
+
* counts as inside.
|
|
572
|
+
*/
|
|
573
|
+
subshell: boolean;
|
|
412
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* What a `$(…)` leaves in a word's text (APRV-511 refuter M1): an unexpanded
|
|
577
|
+
* variable, which is exactly what the shell's substitution is to a classifier
|
|
578
|
+
* that does not run it. The name cannot collide with a real expansion a
|
|
579
|
+
* command would carry, and it is braced so no brace-expansion reading applies.
|
|
580
|
+
*/
|
|
581
|
+
export declare const SUBSTITUTION_SENTINEL = "${__APPROVAL_SUBST__}";
|
|
413
582
|
/**
|
|
414
583
|
* Facts about the machine the command will run on, resolved by the CALLER
|
|
415
584
|
* (APRV-267).
|
|
@@ -471,6 +640,32 @@ export interface ClassifierContext {
|
|
|
471
640
|
* here, forgetting it cannot loosen anything.
|
|
472
641
|
*/
|
|
473
642
|
unwrapShell?: boolean;
|
|
643
|
+
/**
|
|
644
|
+
* A variable git could inherit that the command line's own shell sets
|
|
645
|
+
* (APRV-521, {@link gitEnvironmentWrite}). {@link classifyCommand} fills it
|
|
646
|
+
* from its own segments and hands it down to a substitution and an
|
|
647
|
+
* unwrapped script, which run in a shell that inherits it; every `git`
|
|
648
|
+
* segment under it is `unclassified`. A caller that sets it only refuses
|
|
649
|
+
* more.
|
|
650
|
+
*/
|
|
651
|
+
shellEnvWrite?: string;
|
|
652
|
+
/**
|
|
653
|
+
* May an opaque PROGRAM be classified `exec.code` rather than refused
|
|
654
|
+
* (APRV-510)?
|
|
655
|
+
*
|
|
656
|
+
* `false` by default, and the default is today's answer exactly: `python3 -c`,
|
|
657
|
+
* `node -e`, `perl -e`, `ruby -e` and `deno eval` stay `opaque`, `bun -e`,
|
|
658
|
+
* `python3 <file>` and `bun <file>.ts` stay `unclassified`. `src/cli/hook.ts`
|
|
659
|
+
* passes `true` only when the loaded policy declares
|
|
660
|
+
* `exec.code.custody: attested`, the operator's statement that the agent's
|
|
661
|
+
* code cannot reach credentials it should not hold. Like `scratchRoots`, a
|
|
662
|
+
* caller that forgets it gets the stricter answer: a refusal.
|
|
663
|
+
*
|
|
664
|
+
* It opens a SHAPE and nothing wider ({@link execCodeShape}): `bash -c`,
|
|
665
|
+
* `sh -c`, `eval`, a pipe into an interpreter and `python3 -` (stdin) are
|
|
666
|
+
* refused whatever this says.
|
|
667
|
+
*/
|
|
668
|
+
execCode?: boolean;
|
|
474
669
|
}
|
|
475
670
|
/** Everything a refinement needs: the binary and the words that followed it. */
|
|
476
671
|
interface RuleContext {
|
|
@@ -496,6 +691,12 @@ interface RuleContext {
|
|
|
496
691
|
* `PYTHONPATH`, `PYTHONSTARTUP`, `HOME`).
|
|
497
692
|
*/
|
|
498
693
|
assigned: boolean;
|
|
694
|
+
/**
|
|
695
|
+
* Those assignments, verbatim (APRV-517). `git push` reads them: a `GIT_*`
|
|
696
|
+
* variable or `HOME` decides which configuration the push runs under, and
|
|
697
|
+
* configuration can carry a deleting refspec.
|
|
698
|
+
*/
|
|
699
|
+
assignments: readonly string[];
|
|
499
700
|
/** What the caller knows about the machine (APRV-267). Never read from here. */
|
|
500
701
|
context: ClassifierContext;
|
|
501
702
|
}
|
|
@@ -571,6 +772,14 @@ export interface CommandRule {
|
|
|
571
772
|
* `NAME=value` words in front of the binary, verbatim.
|
|
572
773
|
*/
|
|
573
774
|
shape?: (args: readonly string[], assignments: readonly string[]) => boolean;
|
|
775
|
+
/**
|
|
776
|
+
* Match only an argv in the `exec.code` shape, and only when the caller
|
|
777
|
+
* opened the class (APRV-510, {@link ClassifierContext.execCode},
|
|
778
|
+
* {@link execCodeShape}). The `probe`, `skillScript` and `shape` device again:
|
|
779
|
+
* an argv outside the shape does not match the row at all, so it keeps the
|
|
780
|
+
* answer it has today.
|
|
781
|
+
*/
|
|
782
|
+
execCode?: boolean;
|
|
574
783
|
}
|
|
575
784
|
/**
|
|
576
785
|
* The suffix that marks a Muse model as training on what it is shown.
|
|
@@ -621,6 +830,57 @@ export declare function isSkillScriptPath(word: string): boolean;
|
|
|
621
830
|
* `classifySegment`, which runs after the table and can only tighten.
|
|
622
831
|
*/
|
|
623
832
|
export declare function skillScriptArg(bin: string, args: readonly string[]): string | null;
|
|
833
|
+
/**
|
|
834
|
+
* The class an opaque program takes once a policy opens it: an inline program
|
|
835
|
+
* or an interpreter run of a file the gate cannot read as a skill script.
|
|
836
|
+
* Under `exec.*` (SPEC.md §7) beside `exec.local`, and deliberately not under
|
|
837
|
+
* it: `exec.local` is code in the workspace the runtime may name, this is code
|
|
838
|
+
* whose effect nothing here reads. A record of it says the program EXISTED
|
|
839
|
+
* (its hash and length), never what it did: every tool call the program then
|
|
840
|
+
* makes through the harness is its own hook event, and a shell command it
|
|
841
|
+
* spawns is not seen at all.
|
|
842
|
+
*/
|
|
843
|
+
export declare const EXEC_CODE_CLASS = "exec.code";
|
|
844
|
+
/** The table row that matches the `exec.code` shape. */
|
|
845
|
+
export declare const EXEC_CODE_ROW = "exec-code";
|
|
846
|
+
/** The rule id of an inline program (`python3 -c`, `node -e`, ...). */
|
|
847
|
+
export declare const EXEC_CODE_INLINE_RULE = "exec-code-inline";
|
|
848
|
+
/** The rule id of an interpreter running a file (`python3 x.py`, `bun x.ts`). */
|
|
849
|
+
export declare const EXEC_CODE_FILE_RULE = "exec-code-file";
|
|
850
|
+
/** Where an `exec.code` argv carries its program (an index into the args). */
|
|
851
|
+
export interface ExecCodeShape {
|
|
852
|
+
mode: ExecCodeMode;
|
|
853
|
+
/** Index into the args (the words after the binary) of the source word. */
|
|
854
|
+
index: number;
|
|
855
|
+
}
|
|
856
|
+
/** A word as {@link execCodeShape} needs it: its text and whether the shell would change it. */
|
|
857
|
+
interface ExecShapeWord {
|
|
858
|
+
exact: boolean;
|
|
859
|
+
substitutions: readonly string[];
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* The `exec.code` shape of an interpreter argv, or `null` (APRV-510).
|
|
863
|
+
*
|
|
864
|
+
* An ALLOWLIST, like {@link skillScriptArg}, and the same flags are inert here:
|
|
865
|
+
*
|
|
866
|
+
* - inline: `[inert flags…] <inline flag> <program>`, and NOTHING after the
|
|
867
|
+
* program, for every interpreter. A word after it is handed to the program
|
|
868
|
+
* (`sys.argv`, `process.argv`, `ARGV`), and a one-line loader
|
|
869
|
+
* (`python3 -c 'import sys; exec(sys.argv[1])' '<real program>'`) would put
|
|
870
|
+
* the real program there, outside the hash and inside the argv the payload
|
|
871
|
+
* carries verbatim (security review of APRV-510, finding 3a). `deno` is
|
|
872
|
+
* exactly `deno eval <program>`;
|
|
873
|
+
* - file (python, python3 and bun only): `[inert flags…] <path> [args…]`,
|
|
874
|
+
* where the path is not a skill script (those are `exec.local`, APRV-502)
|
|
875
|
+
* and, for `bun`, carries a script extension.
|
|
876
|
+
*
|
|
877
|
+
* Anything else is not the shape: a value-taking flag, `--flag=value`, `-m`,
|
|
878
|
+
* a lone `-` (stdin), an inline flag with no program, two programs, a cluster
|
|
879
|
+
* hiding the inline flag (`-Ic`), and, when `words` is given, a source word the
|
|
880
|
+
* shell would still change (`LexWord.exact`) or one built from a substitution:
|
|
881
|
+
* the hash binds the program's bytes, so the word must BE them.
|
|
882
|
+
*/
|
|
883
|
+
export declare function execCodeShape(bin: string, args: readonly string[], words?: readonly ExecShapeWord[] | null): ExecCodeShape | null;
|
|
624
884
|
/**
|
|
625
885
|
* The table.
|
|
626
886
|
*
|
|
@@ -660,7 +920,16 @@ export declare const COMMAND_RULES: readonly CommandRule[];
|
|
|
660
920
|
* - a substitution in any of the three words, whose effect happens before the
|
|
661
921
|
* shell even starts;
|
|
662
922
|
* - a nested shell inside the script, refused by {@link ClassifierContext} when
|
|
663
|
-
* the recursion runs (the inner classification unwraps nothing)
|
|
923
|
+
* the recursion runs (the inner classification unwraps nothing);
|
|
924
|
+
* - a shell word that is not the system's binary ({@link isSystemBinWord}): a
|
|
925
|
+
* relative path, or an absolute one outside the system-bin directories, is
|
|
926
|
+
* whatever program sits there, which its last path segment does not name
|
|
927
|
+
* (APRV-523 fix round, review-b S1);
|
|
928
|
+
* - a shell word that is assignment-shaped, in its source spelling
|
|
929
|
+
* ({@link LexWord.assignment}) or its dequoted text ({@link ASSIGNMENT}).
|
|
930
|
+
* bash runs a word whose name is quoted or escaped as a program named by the
|
|
931
|
+
* whole word, and {@link classifySegment} refuses that segment; unwrapping
|
|
932
|
+
* it here would answer before that check runs.
|
|
664
933
|
*
|
|
665
934
|
* The BINDING does not move. `cli/hook.ts` binds the outer command and argv
|
|
666
935
|
* exactly as APRV-362 built them; what this changes is only which text is
|
|
@@ -668,6 +937,59 @@ export declare const COMMAND_RULES: readonly CommandRule[];
|
|
|
668
937
|
* bound elsewhere and unchanged.
|
|
669
938
|
*/
|
|
670
939
|
export declare function loginShellScript(segment: LexSegment): string | null;
|
|
940
|
+
/**
|
|
941
|
+
* How the shell may still change one argv word before the program sees it
|
|
942
|
+
* (APRV-511 fix round 3, S(open)): `splits`, an unquoted variable or `$(…)` is
|
|
943
|
+
* in it; `expands`, an unquoted brace or glob character is. A caller that
|
|
944
|
+
* cannot say passes neither, and only the text is read.
|
|
945
|
+
*/
|
|
946
|
+
export interface ArgExpansion {
|
|
947
|
+
splits: boolean;
|
|
948
|
+
expands: boolean;
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* The first word of a `cp`, `mv`, `ln` or `install` argv that makes its write
|
|
952
|
+
* targets unknowable (APRV-511 fix round 3, S(open)), or `null`. GNU permutes
|
|
953
|
+
* options, so every word before `--` is in an option slot.
|
|
954
|
+
*/
|
|
955
|
+
export declare function copyArgvUnknown(args: readonly string[], flags?: readonly ArgExpansion[]): string | null;
|
|
956
|
+
/**
|
|
957
|
+
* The destination directories a `cp`, `mv`, `ln` or `install` argv names by
|
|
958
|
+
* flag (APRV-511 refuter S3, recheck S(open)): `-t DIR`, `-tDIR`, `-t` anywhere
|
|
959
|
+
* in a short cluster (`-vtDIR`, `-rt DIR`, `-ftDIR`, `-stDIR`), and the long
|
|
960
|
+
* option by any unique prefix GNU accepts (`--target-directory=DIR`,
|
|
961
|
+
* `--target-dir=DIR`, `--t DIR`). A cluster is read from its first `t` even
|
|
962
|
+
* where an earlier letter would have taken the rest as its own value: one more
|
|
963
|
+
* target judged can only make the answer stricter. Words after `--` are
|
|
964
|
+
* operands. An absent value is not returned (the operand scan judges the rest).
|
|
965
|
+
*/
|
|
966
|
+
export declare function copyTargetDirectories(args: readonly string[]): string[];
|
|
967
|
+
/**
|
|
968
|
+
* Where `curl` and `wget` write (APRV-511 refuter S1 and review): `files`,
|
|
969
|
+
* paths written by name (`curl -o F`, `-sSLoF`, `--output F`, `--outp=F`,
|
|
970
|
+
* `-D`, `-c`, `--trace`, `--stderr`; `wget -O F`, `--output-doc=F`, `-o`/`-a`
|
|
971
|
+
* logs), and `directories`, directories written with a name the server
|
|
972
|
+
* chooses (`curl -O` into `--output-dir` or `.`; `wget` without `-O` into
|
|
973
|
+
* `-P`/`--directory-p…` or `.`). `unknown` names the first option that makes
|
|
974
|
+
* the outputs unknowable: one this table does not know, an ambiguous prefix,
|
|
975
|
+
* or a config source (`curl -K`/`--config`, `wget -e`/`--execute`/`--config`).
|
|
976
|
+
* `-` is standard output and writes nothing. Any other binary writes nothing.
|
|
977
|
+
*
|
|
978
|
+
* Fix round 4 (M(L5f-1)): `sources` are the files an upload or request-body
|
|
979
|
+
* option READS ({@link uploadSourcePaths}), in every spelling: separated
|
|
980
|
+
* (`-T F`, `--data @F`), joined by `=` (`--upload-file=F`, `--data=@F`) and
|
|
981
|
+
* glued (`-TF`, `-sd@F`, `-Ff=@F`). `sourceUnknown` is the first option word
|
|
982
|
+
* whose EMBEDDED value names a file this text cannot read (a variable, a
|
|
983
|
+
* substitution, a glob or a brace in it). A separated value is a word of its
|
|
984
|
+
* own and is judged as the positional it also is.
|
|
985
|
+
*/
|
|
986
|
+
export declare function fetchOutputs(bin: string, args: readonly string[], flags?: readonly ArgExpansion[]): {
|
|
987
|
+
files: string[];
|
|
988
|
+
directories: string[];
|
|
989
|
+
unknown: string | null;
|
|
990
|
+
sources: string[];
|
|
991
|
+
sourceUnknown: string | null;
|
|
992
|
+
};
|
|
671
993
|
/**
|
|
672
994
|
* The rules whose commands RUN CODE THIS RUNTIME DID NOT AUTHOR (APRV-193).
|
|
673
995
|
*
|
|
@@ -815,9 +1137,35 @@ export interface CommandSegmentShape {
|
|
|
815
1137
|
text: string;
|
|
816
1138
|
bin: string | null;
|
|
817
1139
|
args: string[];
|
|
1140
|
+
/** What the shell may still do to each of `args` (APRV-511 fix round 3). */
|
|
1141
|
+
argFlags: ArgExpansion[];
|
|
818
1142
|
writes: string[];
|
|
819
1143
|
/** Paths read through `<` (APRV-445 recheck 3, SF3c). */
|
|
820
1144
|
reads: string[];
|
|
1145
|
+
/** Is this segment's stdin the previous segment's output? (APRV-522.) */
|
|
1146
|
+
stdinFed: boolean;
|
|
1147
|
+
/**
|
|
1148
|
+
* The inner text of every `$(…)` this segment expands: in its words, its
|
|
1149
|
+
* redirection targets and an unquoted heredoc's body (APRV-522), so the
|
|
1150
|
+
* hook's disk pass judges the reads a substitution makes.
|
|
1151
|
+
*/
|
|
1152
|
+
substitutions: string[];
|
|
1153
|
+
/**
|
|
1154
|
+
* The `NAME=value` words before the binary (APRV-522 fix round 1, M-B): a
|
|
1155
|
+
* prefix such as `CDPATH=` or `HOME=` steers where a `cd` lands.
|
|
1156
|
+
*/
|
|
1157
|
+
assignments: string[];
|
|
1158
|
+
/**
|
|
1159
|
+
* The script of a login-shell wrapper, exactly as {@link loginShellScript}
|
|
1160
|
+
* reads it for `classifyCommand` (APRV-522 fix round 1, M-A), or `null`.
|
|
1161
|
+
*/
|
|
1162
|
+
script: string | null;
|
|
1163
|
+
/**
|
|
1164
|
+
* A known shell handed an inline-script flag in a shape
|
|
1165
|
+
* {@link loginShellScript} does not unwrap (APRV-522 fix round 1, M-A): what
|
|
1166
|
+
* its script reads cannot be judged, so the hook's disk pass fails closed.
|
|
1167
|
+
*/
|
|
1168
|
+
opaqueShell: boolean;
|
|
821
1169
|
}
|
|
822
1170
|
export declare function commandSegmentShapes(command: string): CommandSegmentShape[] | null;
|
|
823
1171
|
export declare function commandSegmentWords(command: string): CommandSegmentWords[] | null;
|