approval-md 0.4.3 → 0.4.6
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 +13 -2
- package/dist/src/channels/telegram.d.ts +105 -31
- package/dist/src/channels/telegram.js +157 -81
- package/dist/src/channels/telegram.js.map +1 -1
- package/dist/src/cli/channel-telegram.d.ts +100 -35
- package/dist/src/cli/channel-telegram.js +216 -119
- 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.js +3 -3
- 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 +125 -1
- package/dist/src/cli/hook.js +2177 -107
- package/dist/src/cli/hook.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 +368 -1
- package/dist/src/core/command-class.js +2830 -93
- package/dist/src/core/command-class.js.map +1 -1
- package/dist/src/core/date-argv.d.ts +83 -0
- package/dist/src/core/date-argv.js +212 -0
- package/dist/src/core/date-argv.js.map +1 -0
- 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/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/read-scope.js +1 -0
- package/dist/src/core/read-scope.js.map +1 -1
- package/dist/src/core/sysinfo-argv.d.ts +48 -0
- package/dist/src/core/sysinfo-argv.js +340 -0
- package/dist/src/core/sysinfo-argv.js.map +1 -0
- package/dist/src/daemon/git-evidence.d.ts +2 -2
- package/dist/src/daemon/git-evidence.js +1 -1
- package/docs/README-extended.md +3 -2
- package/docs/cli-reference.md +20 -7
- package/package.json +2 -2
- package/schema/event.schema.json +1 -1
- 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
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* half-blocks, no box-drawing, no emoji, because those are the characters that
|
|
11
11
|
* break in Terminal.app's default font and in every CI log. And it DEGRADES —
|
|
12
12
|
* with colour off it collapses to a single line, so a pipe, a `NO_COLOR`
|
|
13
|
-
* terminal or an ASCII locale gets `approval.md v0.4.
|
|
13
|
+
* terminal or an ASCII locale gets `approval.md v0.4.6` and no decoration at
|
|
14
14
|
* all. The banner is ornament; the name and the version are the information,
|
|
15
15
|
* and only the information survives the degradation.
|
|
16
16
|
*/
|
|
17
17
|
import { type Style } from "./style.js";
|
|
18
18
|
/** Kept in step with package.json by a test rather than by memory. */
|
|
19
|
-
export declare const VERSION = "0.4.
|
|
19
|
+
export declare const VERSION = "0.4.6";
|
|
20
20
|
export declare const TAGLINE = "human approval for agent actions";
|
|
21
21
|
/** The one-line form, and the only form a pipe ever sees. */
|
|
22
22
|
export declare function plainWordmark(): string;
|
package/dist/src/cli/wordmark.js
CHANGED
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* half-blocks, no box-drawing, no emoji, because those are the characters that
|
|
11
11
|
* break in Terminal.app's default font and in every CI log. And it DEGRADES —
|
|
12
12
|
* with colour off it collapses to a single line, so a pipe, a `NO_COLOR`
|
|
13
|
-
* terminal or an ASCII locale gets `approval.md v0.4.
|
|
13
|
+
* terminal or an ASCII locale gets `approval.md v0.4.6` and no decoration at
|
|
14
14
|
* all. The banner is ornament; the name and the version are the information,
|
|
15
15
|
* and only the information survives the degradation.
|
|
16
16
|
*/
|
|
17
17
|
import { style as processStyle } from "./style.js";
|
|
18
18
|
/** Kept in step with package.json by a test rather than by memory. */
|
|
19
|
-
export const VERSION = "0.4.
|
|
19
|
+
export const VERSION = "0.4.6";
|
|
20
20
|
export const TAGLINE = "human approval for agent actions";
|
|
21
21
|
/**
|
|
22
22
|
* The six-line form. Written as a raw array so no editor reflows it: every
|
|
@@ -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;
|
|
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;
|
|
85
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. */
|
|
@@ -401,9 +537,48 @@ interface LexSegment {
|
|
|
401
537
|
text: string;
|
|
402
538
|
words: LexWord[];
|
|
403
539
|
redirects: LexRedirect[];
|
|
540
|
+
/**
|
|
541
|
+
* The `$(…)` found in the bodies of unquoted heredocs consumed on this
|
|
542
|
+
* segment's line (APRV-504, folded into APRV-508). The shell expands them
|
|
543
|
+
* when the body is read, so they are classified like any other.
|
|
544
|
+
*/
|
|
545
|
+
bodySubstitutions: string[];
|
|
404
546
|
/** Set when the segment contains a construct whose effect cannot be read. */
|
|
405
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;
|
|
406
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__}";
|
|
407
582
|
/**
|
|
408
583
|
* Facts about the machine the command will run on, resolved by the CALLER
|
|
409
584
|
* (APRV-267).
|
|
@@ -465,6 +640,32 @@ export interface ClassifierContext {
|
|
|
465
640
|
* here, forgetting it cannot loosen anything.
|
|
466
641
|
*/
|
|
467
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;
|
|
468
669
|
}
|
|
469
670
|
/** Everything a refinement needs: the binary and the words that followed it. */
|
|
470
671
|
interface RuleContext {
|
|
@@ -490,6 +691,12 @@ interface RuleContext {
|
|
|
490
691
|
* `PYTHONPATH`, `PYTHONSTARTUP`, `HOME`).
|
|
491
692
|
*/
|
|
492
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[];
|
|
493
700
|
/** What the caller knows about the machine (APRV-267). Never read from here. */
|
|
494
701
|
context: ClassifierContext;
|
|
495
702
|
}
|
|
@@ -552,6 +759,27 @@ export interface CommandRule {
|
|
|
552
759
|
* than being refined into some other refusal.
|
|
553
760
|
*/
|
|
554
761
|
skillScript?: boolean;
|
|
762
|
+
/**
|
|
763
|
+
* Match only an argv this predicate accepts (APRV-508).
|
|
764
|
+
*
|
|
765
|
+
* The general form of `probe` and `skillScript`: the row is a SHAPE, and an
|
|
766
|
+
* argv outside it does not match the row at all, so the segment keeps the
|
|
767
|
+
* answer it had before the row existed (`unclassified`, for a binary no other
|
|
768
|
+
* row names). Before the predicate is asked, a shape row is declined by a
|
|
769
|
+
* substituted word (its text is a hole) and by a binary spelled with a path
|
|
770
|
+
* outside the system directories ({@link isSystemBinWord}: `./date` is
|
|
771
|
+
* whatever program sits in the working directory). `assignments` are the
|
|
772
|
+
* `NAME=value` words in front of the binary, verbatim.
|
|
773
|
+
*/
|
|
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;
|
|
555
783
|
}
|
|
556
784
|
/**
|
|
557
785
|
* The suffix that marks a Muse model as training on what it is shown.
|
|
@@ -602,6 +830,57 @@ export declare function isSkillScriptPath(word: string): boolean;
|
|
|
602
830
|
* `classifySegment`, which runs after the table and can only tighten.
|
|
603
831
|
*/
|
|
604
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;
|
|
605
884
|
/**
|
|
606
885
|
* The table.
|
|
607
886
|
*
|
|
@@ -641,7 +920,16 @@ export declare const COMMAND_RULES: readonly CommandRule[];
|
|
|
641
920
|
* - a substitution in any of the three words, whose effect happens before the
|
|
642
921
|
* shell even starts;
|
|
643
922
|
* - a nested shell inside the script, refused by {@link ClassifierContext} when
|
|
644
|
-
* 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.
|
|
645
933
|
*
|
|
646
934
|
* The BINDING does not move. `cli/hook.ts` binds the outer command and argv
|
|
647
935
|
* exactly as APRV-362 built them; what this changes is only which text is
|
|
@@ -649,6 +937,59 @@ export declare const COMMAND_RULES: readonly CommandRule[];
|
|
|
649
937
|
* bound elsewhere and unchanged.
|
|
650
938
|
*/
|
|
651
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
|
+
};
|
|
652
993
|
/**
|
|
653
994
|
* The rules whose commands RUN CODE THIS RUNTIME DID NOT AUTHOR (APRV-193).
|
|
654
995
|
*
|
|
@@ -796,9 +1137,35 @@ export interface CommandSegmentShape {
|
|
|
796
1137
|
text: string;
|
|
797
1138
|
bin: string | null;
|
|
798
1139
|
args: string[];
|
|
1140
|
+
/** What the shell may still do to each of `args` (APRV-511 fix round 3). */
|
|
1141
|
+
argFlags: ArgExpansion[];
|
|
799
1142
|
writes: string[];
|
|
800
1143
|
/** Paths read through `<` (APRV-445 recheck 3, SF3c). */
|
|
801
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;
|
|
802
1169
|
}
|
|
803
1170
|
export declare function commandSegmentShapes(command: string): CommandSegmentShape[] | null;
|
|
804
1171
|
export declare function commandSegmentWords(command: string): CommandSegmentWords[] | null;
|