@blogic-cz/agent-tools 1.6.12 → 1.6.13
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.
|
@@ -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;AA66FF,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,CA+XrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAt1GD,SAAS,UAAU,UAAU,KAAK,IAs1GM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAx1GC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAw1GjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aA11GE,MAAM,KAAK,OA01GgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aA51GE,MAAM,KAAK,OA41GgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YA91GC,MAAM,KAAK,OA81G0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YAh2GC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAg2Gf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YAl2GC,MAAM,KAAK,OAk2GsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YAp2GC,MAAM,KAAK,MAAM,GAAG,IAo2Ga,CAAC"}
|
package/package.json
CHANGED
|
@@ -422,6 +422,7 @@ function literalBindingText(
|
|
|
422
422
|
command: string,
|
|
423
423
|
name: string,
|
|
424
424
|
value: string,
|
|
425
|
+
allowBrackets = false,
|
|
425
426
|
): { text: string; syntax: string } | undefined {
|
|
426
427
|
let text = "";
|
|
427
428
|
let syntax = "";
|
|
@@ -453,7 +454,11 @@ function literalBindingText(
|
|
|
453
454
|
syntax += quote ? " ".repeat(value.length) : value;
|
|
454
455
|
i += match[0].length - 1;
|
|
455
456
|
} else {
|
|
456
|
-
if (
|
|
457
|
+
if (
|
|
458
|
+
char === "`" ||
|
|
459
|
+
(!quote && (/[(){}*?#]/.test(char) || (!allowBrackets && (char === "[" || char === "]"))))
|
|
460
|
+
)
|
|
461
|
+
return undefined;
|
|
457
462
|
text += char;
|
|
458
463
|
syntax += quote ? " " : char;
|
|
459
464
|
}
|
|
@@ -608,24 +613,45 @@ function literalShellBodies(command: string): string[] | undefined {
|
|
|
608
613
|
return bodies;
|
|
609
614
|
}
|
|
610
615
|
|
|
611
|
-
function isProvedLiteralShellBody(
|
|
616
|
+
function isProvedLiteralShellBody(
|
|
617
|
+
body: string,
|
|
618
|
+
allowedNames: Set<string>,
|
|
619
|
+
isPathBlocked: (path: string) => boolean,
|
|
620
|
+
onProvedPredicate: () => void,
|
|
621
|
+
): boolean {
|
|
612
622
|
if (!body.trim()) return true;
|
|
613
623
|
const materialized = materializeLiteralShell(body)?.command ?? body;
|
|
614
624
|
const parsed = parseStaticShellCommands(materialized);
|
|
615
|
-
if (!parsed || typeof parsed === "string"
|
|
616
|
-
|
|
625
|
+
if (!parsed || typeof parsed === "string") {
|
|
626
|
+
let proved = false;
|
|
627
|
+
if (
|
|
628
|
+
hasEnvironmentVariableExpansionRead(body, allowedNames, isPathBlocked, undefined, () => {
|
|
629
|
+
proved = true;
|
|
630
|
+
}) ||
|
|
631
|
+
!proved
|
|
632
|
+
)
|
|
633
|
+
return false;
|
|
634
|
+
onProvedPredicate();
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
if (!hasProvedNavigationSequence(materialized)) return false;
|
|
617
638
|
if (isStaticHerdrPrompt(materialized)) return true;
|
|
618
639
|
return parsed.pipelines.flat().every((words) => {
|
|
619
640
|
const argv = unwrapProofCommand(words, "\0");
|
|
620
641
|
if (!argv) return false;
|
|
621
642
|
if (argv[0]?.split("/").at(-1) === "env") return false;
|
|
622
643
|
const name = unwrapStaticCommand(argv)[0]?.split("/").at(-1) ?? "";
|
|
623
|
-
if (isShellWord(name))
|
|
644
|
+
if (isShellWord(name)) {
|
|
645
|
+
const nested = literalShellBodies(
|
|
646
|
+
words.map((word) => "'" + word.replaceAll("'", "'\"'\"'") + "'").join(" "),
|
|
647
|
+
);
|
|
624
648
|
return (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
649
|
+
nested !== undefined &&
|
|
650
|
+
nested.every((inner) =>
|
|
651
|
+
isProvedLiteralShellBody(inner, allowedNames, isPathBlocked, onProvedPredicate),
|
|
652
|
+
)
|
|
628
653
|
);
|
|
654
|
+
}
|
|
629
655
|
return (
|
|
630
656
|
isProvedCwdSubprocessRole(unwrapStaticCommand(argv)) ||
|
|
631
657
|
(name === "bun" && isSafeLocalAssignmentCommand(argv) && !isUnsupportedInlineRuntime(argv)) ||
|
|
@@ -2535,7 +2561,9 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2535
2561
|
allowedNames: Set<string>,
|
|
2536
2562
|
isPathBlocked: (path: string) => boolean,
|
|
2537
2563
|
onProvedStatus?: (command: string) => void,
|
|
2564
|
+
onProvedPredicate?: () => void,
|
|
2538
2565
|
): boolean {
|
|
2566
|
+
const originalCommand = command;
|
|
2539
2567
|
let heredoc = boundedHeredoc(command);
|
|
2540
2568
|
while (heredoc) {
|
|
2541
2569
|
// In an unquoted heredoc, # and quote characters are data, not shell syntax.
|
|
@@ -2544,6 +2572,7 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2544
2572
|
heredoc = boundedHeredoc(command);
|
|
2545
2573
|
}
|
|
2546
2574
|
if (!command.includes("$") && !command.includes("`")) return false;
|
|
2575
|
+
let predicateSyntaxIntact = command === originalCommand;
|
|
2547
2576
|
let invalidExpansion = false;
|
|
2548
2577
|
const assignments = getLeadingLiteralAssignments(command);
|
|
2549
2578
|
let localIndex = 0;
|
|
@@ -2562,6 +2591,7 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2562
2591
|
for (let i = 0; i < command.length; i++) {
|
|
2563
2592
|
const char = command[i] ?? "";
|
|
2564
2593
|
if (!quote && char === "#" && !wordStarted) {
|
|
2594
|
+
predicateSyntaxIntact = false;
|
|
2565
2595
|
while (i + 1 < command.length && command[i + 1] !== "\n") i++;
|
|
2566
2596
|
continue;
|
|
2567
2597
|
}
|
|
@@ -2810,6 +2840,21 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2810
2840
|
const materialized = normalized.replaceAll("ENVVALUE", "SAFEENVVALUE");
|
|
2811
2841
|
const blocked = hasSensitiveFileRead(materialized, isPathBlocked);
|
|
2812
2842
|
if (!blocked && statusCaptures.size) onProvedStatus?.(materialized);
|
|
2843
|
+
// This is a complete command proof, not merely a recursively safe shell body.
|
|
2844
|
+
if (
|
|
2845
|
+
!blocked &&
|
|
2846
|
+
parsed.pipelines.length === 1 &&
|
|
2847
|
+
parsed.pipelines[0]?.length === 1 &&
|
|
2848
|
+
!parsed.redirects.length
|
|
2849
|
+
) {
|
|
2850
|
+
const argv = unwrapProofCommand(parsed.pipelines[0]?.[0] ?? [], "ENVVALUE");
|
|
2851
|
+
if (argv && isApprovedEnvironmentPredicate(argv) && predicateSyntaxIntact) {
|
|
2852
|
+
// Roles alone cannot prove original shell boundaries or discarded controls.
|
|
2853
|
+
// Brackets are enabled only for the completely proved bracket builtin.
|
|
2854
|
+
const binding = literalBindingText(normalized, "", "", argv[0] === "[");
|
|
2855
|
+
if (binding && !/[;&|<>\r\n]/.test(binding.syntax.trim())) onProvedPredicate?.();
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2813
2858
|
return blocked;
|
|
2814
2859
|
}
|
|
2815
2860
|
|
|
@@ -3120,11 +3165,37 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
3120
3165
|
const shellBodies = literalShellBodies(command);
|
|
3121
3166
|
if (!shellBodies || (shellBodies.length && shellDepth >= 8))
|
|
3122
3167
|
return "cannot prove a bounded literal shell invocation";
|
|
3168
|
+
const provedPredicateShellBodies: string[] = [];
|
|
3123
3169
|
for (const body of shellBodies) {
|
|
3124
3170
|
const reason = getDangerousBashReason(body, shellDepth + 1);
|
|
3125
3171
|
if (reason || getBlockedCliTool(body, true, shellDepth + 1))
|
|
3126
3172
|
return reason ?? "invokes a blocked CLI from a shell body";
|
|
3127
|
-
if (
|
|
3173
|
+
if (
|
|
3174
|
+
!isProvedLiteralShellBody(body, allowedEnvironmentVariables, isPathBlocked, () => {
|
|
3175
|
+
provedPredicateShellBodies.push(body);
|
|
3176
|
+
})
|
|
3177
|
+
)
|
|
3178
|
+
return "cannot prove a closed literal shell body";
|
|
3179
|
+
}
|
|
3180
|
+
if (provedPredicateShellBodies.length) {
|
|
3181
|
+
const context = parseStaticShellCommands(command);
|
|
3182
|
+
const binding = literalBindingText(command, "", "");
|
|
3183
|
+
const argv =
|
|
3184
|
+
context && typeof context !== "string"
|
|
3185
|
+
? unwrapProofCommand(context.pipelines[0]?.[0] ?? [], "\0")
|
|
3186
|
+
: undefined;
|
|
3187
|
+
if (
|
|
3188
|
+
!context ||
|
|
3189
|
+
typeof context === "string" ||
|
|
3190
|
+
context.pipelines.length !== 1 ||
|
|
3191
|
+
context.pipelines[0]?.length !== 1 ||
|
|
3192
|
+
context.redirects.length !== 0 ||
|
|
3193
|
+
!binding ||
|
|
3194
|
+
/[;&|<>\r\n]/.test(binding.syntax.trim()) ||
|
|
3195
|
+
!argv ||
|
|
3196
|
+
!SHELL_IDENTITIES.has(argv[0] ?? "")
|
|
3197
|
+
)
|
|
3198
|
+
return "cannot prove a single literal shell predicate invocation";
|
|
3128
3199
|
}
|
|
3129
3200
|
const proof = materializeLiteralShell(command);
|
|
3130
3201
|
if (!proof && /^\s*for\b/.test(command)) return "cannot prove a bounded literal shell loop";
|
|
@@ -3169,6 +3240,7 @@ export function createCredentialGuard(config?: CredentialGuardConfig): Credentia
|
|
|
3169
3240
|
allowedEnvironmentVariables,
|
|
3170
3241
|
allowPackageInventory,
|
|
3171
3242
|
shellBodies.filter((body) => {
|
|
3243
|
+
if (provedPredicateShellBodies.includes(body)) return true;
|
|
3172
3244
|
const parsed = parseStaticShellCommands(body);
|
|
3173
3245
|
return (
|
|
3174
3246
|
parsed &&
|