@blogic-cz/agent-tools 1.6.4 → 1.6.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/README.md +3 -1
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential-guard/index.ts +170 -20
package/README.md
CHANGED
|
@@ -670,7 +670,9 @@ Python JSON parsing, dictionary iteration, and printing are not themselves evide
|
|
|
670
670
|
|
|
671
671
|
The bounded Python subprocess proof supports one immutable literal argv binding followed by one `subprocess.run` call. A literal `cwd` is replayed as a quoted, success-gated directory change before the nested argv, so relative reader operands and named directories receive the ordinary path policies. Supported directories are literal relative or absolute paths, including quoted spaces. Empty, dynamic, Unicode, escaped, tilde and other unsupported directory forms refuse rather than dropping the context. Cwd proof recursively unwraps only supported command, RTK proxy and environment wrappers. Environment directory/split-string options and unproved wrapper forms refuse. Only supported passive text, reader, metadata, literal Bun script and blocked CLI roles receive nested replay; shell executors, inline interpreter programs, unproved sed programs, find execution actions and unknown command roles refuse. Nested blocked CLI, environment, sensitive-path and custom-command policies remain active. Outer shell redirects retain their original shell directory. Arbitrary multi-call Python and `check_output` programs remain unsupported; direct Git metadata commands provide index/tree inspection.
|
|
672
672
|
|
|
673
|
-
Supported runtime script/tool operands form an argument boundary: a later tool `-c` configuration flag is data. Genuine eval and loader flags, Bun exec/repl and unproved startup-option arity still refuse. Literal Herdr prompts can compose with
|
|
673
|
+
Supported runtime script/tool operands form an argument boundary: a later tool `-c` configuration flag is data. Genuine eval and loader flags, Bun exec/repl and unproved startup-option arity still refuse. Literal Herdr prompts can compose with literal agent-get, pane-read and agent-read forms and passive consumers. Both read forms require a literal identifier followed by `--source recent-unwrapped --lines N`, with positive integer `N`. A separate `sleep N` command accepts one literal nonnegative integer or decimal duration; delay pipelines, options and extra operands receive no composition proof. Delay redirects receive the ordinary sensitive-path checks. The wrapper sleep-polling guard still applies. One literal `--sql` operand on the supported `bun run db-tool sql/query` shape can contain SQL braces; substitutions, duplicate SQL operands and unknown wrapper options receive no exception. SQL output pipelines require closed passive consumers, so shell, xargs and unknown executors refuse. Numeric `$?` can appear only in passive echo/printf data operands, with passive pipeline consumers. Explicitly approved environment names can appear in quoted equality or blankness tests, including the quoted empty default `${NAME:-}`. This does not add default-approved names or permit shell evaluators. One sed print program with numeric or escaped-regex addresses/ranges and known `-n`/`-E` flags is passive; execution/read/write callbacks, extra programs and script files receive no print proof. Local file bindings use that same closed print proof and never a generic sed-word fallback. Ordinary path and configured command policies still apply to all these forms.
|
|
674
|
+
|
|
675
|
+
Literal shell bodies receive recursive shared policy replay under exact `sh -c`/`bash -c` and `zsh -f -c` invocations, using only these bare executable names or their `/bin/` and `/usr/bin/` paths and known RTK/command wrappers. Both protected paths and blocked CLIs remain enforced inside the body, along with environment and custom-command policies. A conventional shell-family name or numeric version in an unproved executing operand/control position refuses; proved literal text, reader and file-metadata operands remain data. Only the listed sh/bash/zsh identities receive body replay; rbash/rksh/ksh93 and versioned names refuse, and arbitrary renamed executables are outside this identity proof. Replay permits only existing closed body roles/materializations, at most eight shell levels and 64 KiB per invocation. Unproved options, environment prefixes, active exec/nohup/sudo wrappers, positional/script/stdin forms, executable binders, control flow and outer directory changes around a wrapper refuse. A body-internal proved cd affects body readers; outer redirects retain the outer shell directory. This is a textual body proof, not verification of native binaries or all host startup code. In particular, zsh -f still sources the global zshenv file. For unsupported executable inventories, use separate literal metadata requests and literal candidate invocations; no program name or --version flag receives a special exception.
|
|
674
676
|
|
|
675
677
|
A separate bounded Node proof supports literal `package.json` inventory loops that print `bin` metadata or use the supported scripts filter. The complete invocation must preserve loader identity through known passive commands, closed literal writers, or proved inventories. Startup/environment mutations, unknown commands, loader changes, and other dynamic loading remain unproved and are refused.
|
|
676
678
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credential-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAK5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AAQF,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjF,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACxD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/credential-guard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAmB,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAK5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAM/D,8CAA8C;AAC9C,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,+CAA+C;AAC/C,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AAQF,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG;IAC5B,uBAAuB,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC7C,sBAAsB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjF,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACxD,CAAC;AA8oFF,6CAA6C;AAC7C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAErE;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEpE;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEpE;AAMD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,eAAe,CA4VrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAphGD,SAAS,UAAU,UAAU,KAAK,IAohGM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAthGC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAshGjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aAxhGE,MAAM,KAAK,OAwhGgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aA1hGE,MAAM,KAAK,OA0hGgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YA5hGC,MAAM,KAAK,OA4hG0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YA9hGC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IA8hGf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YAhiGC,MAAM,KAAK,OAgiGsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YAliGC,MAAM,KAAK,MAAM,GAAG,IAkiGa,CAAC"}
|
package/package.json
CHANGED
|
@@ -510,26 +510,130 @@ function isLiteralLoopCommand(words: string[], marker: string, value: string): b
|
|
|
510
510
|
);
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
function
|
|
513
|
+
function isLiteralHerdrRead(argv: string[]): boolean {
|
|
514
514
|
return (
|
|
515
|
-
argv
|
|
516
|
-
argv
|
|
515
|
+
argv[0] === "herdr" &&
|
|
516
|
+
!argv[3]?.startsWith("-") &&
|
|
517
517
|
/^[A-Za-z0-9_.:-]+$/.test(argv[3] ?? "") &&
|
|
518
|
-
argv[
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
518
|
+
((argv.length === 4 && argv[1] === "agent" && argv[2] === "get") ||
|
|
519
|
+
(argv.length === 8 &&
|
|
520
|
+
["pane", "agent"].includes(argv[1] ?? "") &&
|
|
521
|
+
argv[2] === "read" &&
|
|
522
|
+
argv[4] === "--source" &&
|
|
523
|
+
argv[5] === "recent-unwrapped" &&
|
|
524
|
+
argv[6] === "--lines" &&
|
|
525
|
+
/^[1-9]\d*$/.test(argv[7] ?? "")))
|
|
522
526
|
);
|
|
523
527
|
}
|
|
524
528
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
/^[A-Za-z0-9_.:-]+$/.test(argv[3] ?? "")
|
|
529
|
+
/** Conventional platform shell names and numeric versions; not arbitrary renamed executables. */
|
|
530
|
+
function isShellWord(word: string): boolean {
|
|
531
|
+
return /^(?:sh|bash|rbash|zsh|dash|ash|ksh|rksh|csh|tcsh|fish)(?:[.-]?[0-9]+(?:[.][0-9]+)*)?$/.test(
|
|
532
|
+
word.split("/").at(-1) ?? "",
|
|
530
533
|
);
|
|
531
534
|
}
|
|
532
535
|
|
|
536
|
+
/** Shell-named operands are data only under an existing non-executing argument role. */
|
|
537
|
+
function hasProvedShellDataOperands(argv: string[]): boolean {
|
|
538
|
+
const name = argv[0]?.split("/").at(-1) ?? "";
|
|
539
|
+
if (isPassiveTextCommand(argv) || isLiteralHerdrRead(argv) || isStaticNavigationCommand(argv))
|
|
540
|
+
return true;
|
|
541
|
+
if (["cat", "ls", "uniq", "cut"].includes(name)) return true;
|
|
542
|
+
if (["find", "wc", "sort"].includes(name)) return isProvedCwdSubprocessRole(argv);
|
|
543
|
+
return name === "test" && argv.length === 3 && argv[1] === "-e" && !/[$`]/.test(argv[2] ?? "");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const SHELL_IDENTITIES = new Set([
|
|
547
|
+
"sh",
|
|
548
|
+
"bash",
|
|
549
|
+
"zsh",
|
|
550
|
+
"/bin/sh",
|
|
551
|
+
"/usr/bin/sh",
|
|
552
|
+
"/bin/bash",
|
|
553
|
+
"/usr/bin/bash",
|
|
554
|
+
"/bin/zsh",
|
|
555
|
+
"/usr/bin/zsh",
|
|
556
|
+
]);
|
|
557
|
+
|
|
558
|
+
/** Literal argv only. Undefined means a shell was found without a complete invocation proof. */
|
|
559
|
+
function literalShellBodies(command: string): string[] | undefined {
|
|
560
|
+
const parsed = parseStaticShellCommands(command);
|
|
561
|
+
const operands = parsed ?? parseStaticShellCommands(command, true);
|
|
562
|
+
if (!operands || typeof operands === "string") return [];
|
|
563
|
+
const commands = operands.pipelines.flat();
|
|
564
|
+
if (
|
|
565
|
+
commands.some((words) =>
|
|
566
|
+
["exec", "nohup", "sudo"].includes(unwrapStaticCommand(words)[0]?.split("/").at(-1) ?? ""),
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
return undefined;
|
|
570
|
+
for (const words of commands) {
|
|
571
|
+
const argv = unwrapStaticCommand(words);
|
|
572
|
+
if (
|
|
573
|
+
!isShellWord(argv[0] ?? "") &&
|
|
574
|
+
argv.some(
|
|
575
|
+
(word, index) =>
|
|
576
|
+
index !== inlineProgramIndex(argv) &&
|
|
577
|
+
(isShellWord(word) ||
|
|
578
|
+
(/^--?[A-Za-z0-9-]+=/.test(word) && isShellWord(word.slice(word.indexOf("=") + 1)))),
|
|
579
|
+
) &&
|
|
580
|
+
!hasProvedShellDataOperands(argv) &&
|
|
581
|
+
!isStaticHerdrPrompt(command)
|
|
582
|
+
)
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
585
|
+
const shells = commands.filter((words) => isShellWord(unwrapStaticCommand(words)[0] ?? ""));
|
|
586
|
+
if (!shells.length) return [];
|
|
587
|
+
if (
|
|
588
|
+
!parsed ||
|
|
589
|
+
typeof parsed === "string" ||
|
|
590
|
+
!literalBindingText(command, "", "") ||
|
|
591
|
+
new TextEncoder().encode(command).byteLength > 65_536 ||
|
|
592
|
+
commands.some((words) => unwrapStaticCommand(words)[0]?.split("/").at(-1) === "cd")
|
|
593
|
+
)
|
|
594
|
+
return undefined;
|
|
595
|
+
const bodies: string[] = [];
|
|
596
|
+
for (const words of shells) {
|
|
597
|
+
const argv = unwrapProofCommand(words, "\0");
|
|
598
|
+
if (!argv || !SHELL_IDENTITIES.has(argv[0] ?? "")) return undefined;
|
|
599
|
+
const name = argv[0]?.split("/").at(-1);
|
|
600
|
+
if (argv.length === 2 && ["--version", "--help"].includes(argv[1] ?? "")) continue;
|
|
601
|
+
const index = name === "zsh" && argv[1] === "-f" ? 2 : 1;
|
|
602
|
+
if ((name === "zsh" && index !== 2) || argv[index] !== "-c" || argv.length !== index + 2)
|
|
603
|
+
return undefined;
|
|
604
|
+
bodies.push(argv[index + 1] ?? "");
|
|
605
|
+
}
|
|
606
|
+
return bodies;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function isProvedLiteralShellBody(body: string): boolean {
|
|
610
|
+
if (!body.trim()) return true;
|
|
611
|
+
const materialized = materializeLiteralShell(body)?.command ?? body;
|
|
612
|
+
const parsed = parseStaticShellCommands(materialized);
|
|
613
|
+
if (!parsed || typeof parsed === "string" || !hasProvedNavigationSequence(materialized))
|
|
614
|
+
return false;
|
|
615
|
+
if (isStaticHerdrPrompt(materialized)) return true;
|
|
616
|
+
return parsed.pipelines.flat().every((words) => {
|
|
617
|
+
const argv = unwrapProofCommand(words, "\0");
|
|
618
|
+
if (!argv) return false;
|
|
619
|
+
if (argv[0]?.split("/").at(-1) === "env") return false;
|
|
620
|
+
const name = unwrapStaticCommand(argv)[0]?.split("/").at(-1) ?? "";
|
|
621
|
+
if (isShellWord(name))
|
|
622
|
+
return (
|
|
623
|
+
literalShellBodies(
|
|
624
|
+
words.map((word) => "'" + word.replaceAll("'", "'\"'\"'") + "'").join(" "),
|
|
625
|
+
) !== undefined
|
|
626
|
+
);
|
|
627
|
+
return (
|
|
628
|
+
isProvedCwdSubprocessRole(unwrapStaticCommand(argv)) ||
|
|
629
|
+
(name === "bun" && isSafeLocalAssignmentCommand(argv) && !isUnsupportedInlineRuntime(argv)) ||
|
|
630
|
+
isStaticNavigationCommand(argv) ||
|
|
631
|
+
isLiteralHerdrRead(argv) ||
|
|
632
|
+
(argv.length === 1 && ["true", "false", "pwd"].includes(name))
|
|
633
|
+
);
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
533
637
|
/** A complete stdin-only JSON projection, with no suffix, imports or executable input. */
|
|
534
638
|
function isLiteralStdinJsonDisplay(argv: string[]): boolean {
|
|
535
639
|
if (argv.length !== 3 || !["python", "python3"].includes(argv[0] ?? "") || argv[1] !== "-c")
|
|
@@ -1836,6 +1940,13 @@ function isStaticHerdrPrompt(command: string): boolean {
|
|
|
1836
1940
|
);
|
|
1837
1941
|
if (isPassiveTextCommand(argv)) return true;
|
|
1838
1942
|
if (isLiteralHerdrRead(argv)) return index === 0;
|
|
1943
|
+
if (argv[0] === "sleep")
|
|
1944
|
+
return (
|
|
1945
|
+
index === 0 &&
|
|
1946
|
+
pipeline.length === 1 &&
|
|
1947
|
+
argv.length === 2 &&
|
|
1948
|
+
/^\d+(?:\.\d+)?$/.test(argv[1] ?? "")
|
|
1949
|
+
);
|
|
1839
1950
|
if (isStaticNavigationCommand(argv))
|
|
1840
1951
|
return index === 0 && (argv[0] !== "cd" || pipeline.length === 1);
|
|
1841
1952
|
return false;
|
|
@@ -1858,6 +1969,7 @@ function hasEnvironmentRead(
|
|
|
1858
1969
|
command: string,
|
|
1859
1970
|
allowedNames: Set<string>,
|
|
1860
1971
|
allowInventory = hasProvedPackageInventoryInvocation(command),
|
|
1972
|
+
replayedShellBodies: string[] = [],
|
|
1861
1973
|
): boolean {
|
|
1862
1974
|
if (isStaticHerdrPrompt(command)) return false;
|
|
1863
1975
|
if (isHerdrPrompt(command)) return true;
|
|
@@ -1926,7 +2038,13 @@ function hasEnvironmentRead(
|
|
|
1926
2038
|
) {
|
|
1927
2039
|
return true;
|
|
1928
2040
|
}
|
|
1929
|
-
if (
|
|
2041
|
+
if (
|
|
2042
|
+
unwrapped.some(
|
|
2043
|
+
(argv) =>
|
|
2044
|
+
!(SHELL_IDENTITIES.has(argv[0] ?? "") && replayedShellBodies.includes(argv.at(-1) ?? "")) &&
|
|
2045
|
+
hasStaticEnvironmentRead(argv, allowedNames, allowInventory),
|
|
2046
|
+
)
|
|
2047
|
+
) {
|
|
1930
2048
|
return true;
|
|
1931
2049
|
}
|
|
1932
2050
|
|
|
@@ -2743,7 +2861,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2743
2861
|
return getDangerousBashReason(command) !== null;
|
|
2744
2862
|
}
|
|
2745
2863
|
|
|
2746
|
-
function getDangerousBashReason(command: string): string | null {
|
|
2864
|
+
function getDangerousBashReason(command: string, shellDepth = 0): string | null {
|
|
2747
2865
|
const originalCommand = command;
|
|
2748
2866
|
const allowPackageInventory = hasProvedPackageInventoryInvocation(originalCommand);
|
|
2749
2867
|
const rawEnvironmentSource =
|
|
@@ -2757,20 +2875,30 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2757
2875
|
if (rawEnvironmentSource && hasEnvironmentMutationPrefix(originalCommand))
|
|
2758
2876
|
return "blocked by the environment/execution policy after an unproved environment mutation";
|
|
2759
2877
|
command = normalizeProgramHeredocs(command);
|
|
2878
|
+
const shellBodies = literalShellBodies(command);
|
|
2879
|
+
if (!shellBodies || (shellBodies.length && shellDepth >= 8))
|
|
2880
|
+
return "cannot prove a bounded literal shell invocation";
|
|
2881
|
+
for (const body of shellBodies) {
|
|
2882
|
+
const reason = getDangerousBashReason(body, shellDepth + 1);
|
|
2883
|
+
if (reason || getBlockedCliTool(body, true, shellDepth + 1))
|
|
2884
|
+
return reason ?? "invokes a blocked CLI from a shell body";
|
|
2885
|
+
if (!isProvedLiteralShellBody(body)) return "cannot prove a closed literal shell body";
|
|
2886
|
+
}
|
|
2760
2887
|
const proof = materializeLiteralShell(command);
|
|
2761
2888
|
if (!proof && /^\s*for\b/.test(command)) return "cannot prove a bounded literal shell loop";
|
|
2762
2889
|
if (proof) {
|
|
2763
2890
|
if (proof.paths.some((path) => hasSensitiveFileRead("cat " + path, isPathBlocked)))
|
|
2764
2891
|
return "accesses a sensitive file path";
|
|
2765
2892
|
command = proof.command;
|
|
2766
|
-
if (getBlockedCliTool(command))
|
|
2893
|
+
if (getBlockedCliTool(command, true, shellDepth))
|
|
2894
|
+
return "invokes a blocked CLI from a literal expansion";
|
|
2767
2895
|
}
|
|
2768
2896
|
for (const nested of [
|
|
2769
2897
|
...inlineSubprocessCommands(command),
|
|
2770
2898
|
...inlinePackageCommands(command, allowPackageInventory),
|
|
2771
2899
|
]) {
|
|
2772
|
-
const reason = getDangerousBashReason(nested);
|
|
2773
|
-
if (reason || getBlockedCliTool(nested))
|
|
2900
|
+
const reason = getDangerousBashReason(nested, shellDepth);
|
|
2901
|
+
if (reason || getBlockedCliTool(nested, true, shellDepth))
|
|
2774
2902
|
return reason ?? "invokes a blocked CLI from a script";
|
|
2775
2903
|
}
|
|
2776
2904
|
if (
|
|
@@ -2786,7 +2914,21 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2786
2914
|
}
|
|
2787
2915
|
if (
|
|
2788
2916
|
hasEnvironmentSourceAccess(command, rawEnvironmentSource, allowedEnvironmentVariables) ||
|
|
2789
|
-
hasEnvironmentRead(
|
|
2917
|
+
hasEnvironmentRead(
|
|
2918
|
+
command,
|
|
2919
|
+
allowedEnvironmentVariables,
|
|
2920
|
+
allowPackageInventory,
|
|
2921
|
+
shellBodies.filter((body) => {
|
|
2922
|
+
const parsed = parseStaticShellCommands(body);
|
|
2923
|
+
return (
|
|
2924
|
+
parsed &&
|
|
2925
|
+
typeof parsed !== "string" &&
|
|
2926
|
+
parsed.pipelines
|
|
2927
|
+
.flat()
|
|
2928
|
+
.every((words) => isPassiveTextCommand(unwrapStaticCommand(words)))
|
|
2929
|
+
);
|
|
2930
|
+
}),
|
|
2931
|
+
)
|
|
2790
2932
|
) {
|
|
2791
2933
|
return "blocked by the environment/execution policy; permitted access or non-execution could not be established for this command form";
|
|
2792
2934
|
}
|
|
@@ -2828,16 +2970,24 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
2828
2970
|
function getBlockedCliTool(
|
|
2829
2971
|
command: string,
|
|
2830
2972
|
expand = true,
|
|
2973
|
+
shellDepth = 0,
|
|
2831
2974
|
): { name: string; wrapper: string } | null {
|
|
2832
2975
|
command = normalizeProgramHeredocs(command);
|
|
2976
|
+
const shellBodies = literalShellBodies(command);
|
|
2977
|
+
if (shellBodies && shellDepth < 8) {
|
|
2978
|
+
for (const body of shellBodies) {
|
|
2979
|
+
const blocked = getBlockedCliTool(body, true, shellDepth + 1);
|
|
2980
|
+
if (blocked) return blocked;
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2833
2983
|
const proof = expand ? materializeLiteralShell(command) : undefined;
|
|
2834
2984
|
if (proof) {
|
|
2835
|
-
const originalBlocked = getBlockedCliTool(command, false);
|
|
2985
|
+
const originalBlocked = getBlockedCliTool(command, false, shellDepth);
|
|
2836
2986
|
if (originalBlocked) return originalBlocked;
|
|
2837
2987
|
command = proof.command;
|
|
2838
2988
|
}
|
|
2839
2989
|
for (const nested of inlineSubprocessCommands(command)) {
|
|
2840
|
-
const blocked = getBlockedCliTool(nested);
|
|
2990
|
+
const blocked = getBlockedCliTool(nested, true, shellDepth);
|
|
2841
2991
|
if (blocked) return blocked;
|
|
2842
2992
|
}
|
|
2843
2993
|
const staticCommands = parseStaticShellCommands(command);
|