@blogic-cz/agent-tools 1.6.1 → 1.6.3
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 +4 -0
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential-guard/index.ts +147 -16
package/README.md
CHANGED
|
@@ -654,6 +654,10 @@ Literal assignments such as `F=README.md; cat "$F"` work in supported static com
|
|
|
654
654
|
|
|
655
655
|
Ordinary navigation operands such as `cd "$HOME"` and `git -C "$WORKTREE_ROOT" status --short` do not need display permission. This does not authorize printing those values, using them as code/options, or reading unknown file paths. In `printf`, approved values belong in data arguments after a fixed nonmutating format, for example `printf -- '%s' "$WORKSPACE_LABEL"`.
|
|
656
656
|
|
|
657
|
+
Literal `herdr agent prompt` commands can be composed with independent `cat` readers and passive text pipelines. Prompt text stays data; command substitutions, environment mutation, unknown consumers, and executable pipelines cannot establish the proof. All reader operands and redirects still receive the ordinary sensitive-path checks. Literal loops also support `herdr pane read ID --source recent-unwrapped --lines N`, with the binder restricted to `ID`, and `2>&1` file-descriptor duplication. A bounded Python stdin processor supports only `import json,sys; d=json.load(sys.stdin); print(d["result"].get("text","") if isinstance(d.get("result"),dict) else d)`, including insignificant spacing and single lowercase-letter local renaming. Extra imports, statements, calls, runtime options, arguments, and other Python programs cannot establish this loop proof. The existing loop size and command limits apply.
|
|
658
|
+
|
|
659
|
+
Prompt composition also supports one `cd [--] PATH` with a literal relative or absolute directory at the start of the final `&&` chain. All subsequent inspection must remain in that chain, so a failed `cd` cannot reach its readers. Directory names may contain quoted spaces. Multiple `cd` commands, later `;` or newline boundaries, tilde or variable expansion, other `cd` options, background jobs, `||`, and navigation in pipelines remain unproved. Supported Git inspection can use literal `-C PATH` prefixes with `grep`, `status`, `rev-parse`, or `rev-list`; execution and startup options remain unsupported. Navigation composition refuses relative file redirects, including redirects on `cd` and Git commands, because those use the shell directory before the command runs. Absolute redirects still receive ordinary path checks. Named directories receive sensitive-path checks with directory-prefix semantics. Only a proved `cd` chain qualifies relative reader operands with that directory; Git `-C` qualifies that command's file operands without changing shell state. Git grep uses the shared grep pattern-versus-file checks. This checks textual operands and does not resolve aliases, symlinks, ambient directories, or command lookup.
|
|
660
|
+
|
|
657
661
|
The guard recognizes `rtk`, `rtk proxy`, `command`, and supported `env` prefixes and checks commands in pipelines and chains. Quoted filenames, comments, and redirects retain sensitive-path checks. Jq object selectors and inline awk programs qualify only when their actual program and options establish passive behavior. Literal environment-command text in `rg`, `grep`, `git grep`, `echo`, and `printf` is allowed only with supported command roles and passive pipeline consumers. Environment-source evidence and startup identity checks also apply to the complete original invocation. Custom dangerous-command patterns apply to both the original input and proved expansions.
|
|
658
662
|
|
|
659
663
|
Paths are checked against sensitive filename patterns and configured path rules. Supported proofs apply those checks to their expanded operands. Ordinary parent-directory and absolute paths may be allowed; the guard does not resolve filesystem paths or symlinks, or enforce repository confinement.
|
|
@@ -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;AAgzEF,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,CA4TrF;AAQD,kEAAkE;AAClE,eAAO,MAAM,uBAAuB,UAtpFD,SAAS,UAAU,UAAU,KAAK,IAspFM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAxpFC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAwpFjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aA1pFE,MAAM,KAAK,OA0pFgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aA5pFE,MAAM,KAAK,OA4pFgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YA9pFC,MAAM,KAAK,OA8pF0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YAhqFC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAgqFf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YAlqFC,MAAM,KAAK,OAkqFsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YApqFC,MAAM,KAAK,MAAM,GAAG,IAoqFa,CAAC"}
|
package/package.json
CHANGED
|
@@ -504,9 +504,8 @@ function isLiteralLoopCommand(words: string[], marker: string, value: string): b
|
|
|
504
504
|
}
|
|
505
505
|
if (name === "herdr")
|
|
506
506
|
return (
|
|
507
|
-
argv.length === 4 &&
|
|
508
|
-
|
|
509
|
-
argv[2] === "get" &&
|
|
507
|
+
((argv.length === 4 && argv[1] === "agent" && argv[2] === "get") ||
|
|
508
|
+
isLiteralHerdrPaneRead(argv)) &&
|
|
510
509
|
/^[A-Za-z0-9_.:-]+$/.test(argv[3] ?? "") &&
|
|
511
510
|
uses.every((index) => index === 3)
|
|
512
511
|
);
|
|
@@ -520,7 +519,36 @@ function isLiteralLoopCommand(words: string[], marker: string, value: string): b
|
|
|
520
519
|
uses.every((index) => index === 8)
|
|
521
520
|
);
|
|
522
521
|
return (
|
|
523
|
-
|
|
522
|
+
uses.length === 0 &&
|
|
523
|
+
((["grep", "head", "tail"].includes(name ?? "") && isPassiveTextCommand(argv)) ||
|
|
524
|
+
isLiteralStdinJsonDisplay(argv))
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function isLiteralHerdrPaneRead(argv: string[]): boolean {
|
|
529
|
+
return (
|
|
530
|
+
argv.length === 8 &&
|
|
531
|
+
argv.slice(0, 3).join(" ") === "herdr pane read" &&
|
|
532
|
+
/^[A-Za-z0-9_.:-]+$/.test(argv[3] ?? "") &&
|
|
533
|
+
argv[4] === "--source" &&
|
|
534
|
+
argv[5] === "recent-unwrapped" &&
|
|
535
|
+
argv[6] === "--lines" &&
|
|
536
|
+
/^[1-9]\d*$/.test(argv[7] ?? "")
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** A complete stdin-only JSON projection, with no suffix, imports or executable input. */
|
|
541
|
+
function isLiteralStdinJsonDisplay(argv: string[]): boolean {
|
|
542
|
+
if (argv.length !== 3 || !["python", "python3"].includes(argv[0] ?? "") || argv[1] !== "-c")
|
|
543
|
+
return false;
|
|
544
|
+
const program = argv[2] ?? "";
|
|
545
|
+
if (program.length > 65_536) return false;
|
|
546
|
+
const parts = programParts(program, true);
|
|
547
|
+
if (!parts || parts.literals.join("\0") !== "result\0text\0\0result") return false;
|
|
548
|
+
// ponytail: one JSON projection with a single-letter local; extend only with another complete grammar.
|
|
549
|
+
const code = parts.code.replace(/[ \t]+/g, " ").trim();
|
|
550
|
+
return /^import json\s*,\s*sys\s*(?:;|\r?\n)\s*([a-z])\s*=\s*json\s*\.\s*load\s*\(\s*sys\s*\.\s*stdin\s*\)\s*(?:;|\r?\n)\s*print\s*\(\s*\1\s*\[\s*STRING\s*\]\s*\.\s*get\s*\(\s*STRING\s*,\s*STRING\s*\)\s+if isinstance\s*\(\s*\1\s*\.\s*get\s*\(\s*STRING\s*\)\s*,\s*dict\s*\)\s+else \1\s*\)\s*;?$/.test(
|
|
551
|
+
code,
|
|
524
552
|
);
|
|
525
553
|
}
|
|
526
554
|
|
|
@@ -567,7 +595,8 @@ function materializeLiteralShell(command: string): LiteralShellProof | undefined
|
|
|
567
595
|
const values = rawValues.map((item) => item.replace(/^['"]|['"]$/g, ""));
|
|
568
596
|
if (!values.length || values.length > 16) return undefined;
|
|
569
597
|
const marked = literalBindingText(loop[3] ?? "", name, marker);
|
|
570
|
-
if (!marked || /&&|\|\||[<>]|&/.test(marked.syntax.replace(/\b2>&1\
|
|
598
|
+
if (!marked || /&&|\|\||[<>]|&/.test(marked.syntax.replace(/\b2>&1(?=[ \t\n;|]|$)/g, "")))
|
|
599
|
+
return undefined;
|
|
571
600
|
const parsed = parseStaticShellCommands(marked.text);
|
|
572
601
|
if (
|
|
573
602
|
!parsed ||
|
|
@@ -1519,6 +1548,56 @@ function isNavigationOperand(argv: string[], marker: string): boolean {
|
|
|
1519
1548
|
);
|
|
1520
1549
|
}
|
|
1521
1550
|
|
|
1551
|
+
/** Literal directory operands only; navigation is never a pipeline consumer. */
|
|
1552
|
+
function isStaticNavigationCommand(argv: string[]): boolean {
|
|
1553
|
+
const path = (value: string | undefined) => /^[A-Za-z0-9_./][A-Za-z0-9_./ -]*$/.test(value ?? "");
|
|
1554
|
+
if (argv[0] === "cd")
|
|
1555
|
+
return isNavigationOperand(argv, "\0") && path(argv[argv[1] === "--" ? 2 : 1]);
|
|
1556
|
+
if (argv[0] !== "git") return false;
|
|
1557
|
+
let index = 1;
|
|
1558
|
+
while (argv[index] === "-C") {
|
|
1559
|
+
if (!path(argv[index + 1])) return false;
|
|
1560
|
+
index += 2;
|
|
1561
|
+
}
|
|
1562
|
+
return (
|
|
1563
|
+
isNavigationOperand(argv, "\0") ||
|
|
1564
|
+
(index > 1 && isPassiveTextCommand(["git", ...argv.slice(index)]))
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/** Prove a single cd whose uses are gated on success, without simulating shell state. */
|
|
1569
|
+
function hasProvedNavigationSequence(command: string): boolean {
|
|
1570
|
+
const literal = literalBindingText(command, "", "");
|
|
1571
|
+
const parsed = parseStaticShellCommands(command);
|
|
1572
|
+
if (!literal || !parsed || typeof parsed === "string") return false;
|
|
1573
|
+
const commands = parsed.pipelines.flat().map((words) => unwrapProofCommand(words, "\0"));
|
|
1574
|
+
const changesDirectory = commands.filter((argv) => argv?.[0] === "cd");
|
|
1575
|
+
if (
|
|
1576
|
+
!commands.some((argv) => argv && (argv[0] === "cd" || (argv[0] === "git" && argv[1] === "-C")))
|
|
1577
|
+
)
|
|
1578
|
+
return true;
|
|
1579
|
+
if (
|
|
1580
|
+
/\|\||(^|[^&])&(?!&)/.test(literal.syntax.replace(/[<>]&[0-9-]+/g, "")) ||
|
|
1581
|
+
parsed.redirects.some(({ target }) => !target.startsWith("/")) ||
|
|
1582
|
+
changesDirectory.length > 1
|
|
1583
|
+
)
|
|
1584
|
+
return false;
|
|
1585
|
+
if (!changesDirectory.length) return true;
|
|
1586
|
+
// Only the final AND list can use this directory. A failing cd never reaches its readers.
|
|
1587
|
+
const syntax = literal.syntax.replace(/[ \t\n]+$/g, "");
|
|
1588
|
+
const boundary = Math.max(syntax.lastIndexOf(";"), syntax.lastIndexOf("\n"));
|
|
1589
|
+
const tail = parseStaticShellCommands(command.slice(boundary + 1, syntax.length));
|
|
1590
|
+
if (!tail || typeof tail === "string" || tail.pipelines.length < 2) return false;
|
|
1591
|
+
const first = tail.pipelines[0];
|
|
1592
|
+
const argv = unwrapProofCommand(first?.[0] ?? [], "\0");
|
|
1593
|
+
return (
|
|
1594
|
+
first?.length === 1 &&
|
|
1595
|
+
argv?.[0] === "cd" &&
|
|
1596
|
+
isStaticNavigationCommand(argv) &&
|
|
1597
|
+
syntax.slice(boundary + 1).match(/&&|[;&|\n]/)?.[0] === "&&"
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1522
1601
|
function isPassiveTextCommand(argv: string[]): boolean {
|
|
1523
1602
|
const name = argv[0]?.split("/").at(-1) ?? "";
|
|
1524
1603
|
const args = argv.slice(1);
|
|
@@ -1597,24 +1676,48 @@ function hasStaticEnvironmentRead(
|
|
|
1597
1676
|
}
|
|
1598
1677
|
|
|
1599
1678
|
function isStaticHerdrPrompt(command: string): boolean {
|
|
1679
|
+
// Preserve shell word boundaries and executable identity before proving composition.
|
|
1680
|
+
const literal = literalBindingText(command, "", "");
|
|
1681
|
+
if (!literal) return false;
|
|
1600
1682
|
const parsed = parseStaticShellCommands(command);
|
|
1601
1683
|
if (!parsed || typeof parsed === "string") return false;
|
|
1602
|
-
const commands = parsed.pipelines.flat().map(
|
|
1684
|
+
const commands = parsed.pipelines.flat().map((words) => unwrapProofCommand(words, "\0"));
|
|
1603
1685
|
const prompts = commands.filter(
|
|
1604
|
-
(argv) => argv[0] === "herdr" && argv[1] === "agent" && argv[2] === "prompt",
|
|
1686
|
+
(argv) => argv?.[0] === "herdr" && argv[1] === "agent" && argv[2] === "prompt",
|
|
1605
1687
|
);
|
|
1688
|
+
if (!hasProvedNavigationSequence(command)) return false;
|
|
1606
1689
|
return (
|
|
1607
1690
|
prompts.length > 0 &&
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1691
|
+
parsed.pipelines.every((pipeline) =>
|
|
1692
|
+
pipeline.every((words, index) => {
|
|
1693
|
+
const argv = unwrapProofCommand(words, "\0");
|
|
1694
|
+
if (!argv) return false;
|
|
1695
|
+
if (argv[0] === "herdr" && argv[1] === "agent" && argv[2] === "prompt")
|
|
1696
|
+
return index === 0 && argv.length >= 5;
|
|
1697
|
+
// cat is a reader at the start of an independent pipeline, never a consumer.
|
|
1698
|
+
if (argv[0] === "cat")
|
|
1699
|
+
return (
|
|
1700
|
+
index === 0 &&
|
|
1701
|
+
argv.length > 1 &&
|
|
1702
|
+
argv.slice(1).every((arg) => arg === "--" || !arg.startsWith("-"))
|
|
1703
|
+
);
|
|
1704
|
+
if (isPassiveTextCommand(argv)) return true;
|
|
1705
|
+
if (isStaticNavigationCommand(argv))
|
|
1706
|
+
return index === 0 && (argv[0] !== "cd" || pipeline.length === 1);
|
|
1707
|
+
return false;
|
|
1708
|
+
}),
|
|
1612
1709
|
)
|
|
1613
1710
|
);
|
|
1614
1711
|
}
|
|
1615
1712
|
|
|
1616
1713
|
function isHerdrPrompt(command: string): boolean {
|
|
1617
|
-
|
|
1714
|
+
if (/^herdr\s+agent\s+prompt\b/.test(command.trim())) return true;
|
|
1715
|
+
const parsed = parseStaticShellCommands(command, true);
|
|
1716
|
+
if (!parsed || typeof parsed === "string") return false;
|
|
1717
|
+
return parsed.pipelines.flat().some((words) => {
|
|
1718
|
+
const argv = unwrapStaticCommand(words);
|
|
1719
|
+
return argv[0] === "herdr" && argv[1] === "agent" && argv[2] === "prompt";
|
|
1720
|
+
});
|
|
1618
1721
|
}
|
|
1619
1722
|
|
|
1620
1723
|
function hasEnvironmentRead(
|
|
@@ -1788,13 +1891,41 @@ function hasSensitiveFileRead(command: string, isPathBlocked: (path: string) =>
|
|
|
1788
1891
|
const readers =
|
|
1789
1892
|
/\b(?:cat|cp|grep|rg|sed|awk|jq|head|tail|less|more|source|ls|sort|uniq|cut|wc|mv|find|shasum|sha1sum|sha256sum|sha512sum|md5sum|cksum)\b/i;
|
|
1790
1893
|
if (parsed && parsed !== "brace-expansion") {
|
|
1894
|
+
let directory = "";
|
|
1895
|
+
const trackDirectory = hasProvedNavigationSequence(command);
|
|
1791
1896
|
return parsed.pipelines.flat().some((words) => {
|
|
1792
1897
|
const argv = unwrapStaticCommand(words);
|
|
1793
1898
|
const inspectedArgv =
|
|
1794
1899
|
argv[0]?.split("/").at(-1) === "env" ? unwrapEnvironmentCommand(argv) : argv;
|
|
1795
1900
|
if (inspectedArgv === null) return false;
|
|
1796
|
-
|
|
1797
|
-
|
|
1901
|
+
let inspected = unwrapStaticCommand(inspectedArgv);
|
|
1902
|
+
let name = inspected[0]?.split("/").at(-1) ?? "";
|
|
1903
|
+
let commandDirectory = directory;
|
|
1904
|
+
const inDirectory = (path: string) =>
|
|
1905
|
+
path.startsWith("/") ? posix.normalize(path) : posix.join(commandDirectory, path);
|
|
1906
|
+
const blockedOperand = (path: string) =>
|
|
1907
|
+
[path, inDirectory(path)].some((value) => isPathBlocked(value) || isSensitivePath(value));
|
|
1908
|
+
const blockedDirectory = (path: string) => blockedOperand(path) || blockedOperand(path + "/");
|
|
1909
|
+
if (name === "cd") {
|
|
1910
|
+
const path = inspected[inspected[1] === "--" ? 2 : 1];
|
|
1911
|
+
if (path === undefined) return false;
|
|
1912
|
+
if (blockedDirectory(path)) return true;
|
|
1913
|
+
if (trackDirectory && isStaticNavigationCommand(inspected)) directory = inDirectory(path);
|
|
1914
|
+
return false;
|
|
1915
|
+
}
|
|
1916
|
+
if (name === "git") {
|
|
1917
|
+
let index = 1;
|
|
1918
|
+
while (inspected[index] === "-C" && inspected[index + 1]) {
|
|
1919
|
+
if (blockedDirectory(inspected[index + 1] ?? "")) return true;
|
|
1920
|
+
commandDirectory = inDirectory(inspected[index + 1] ?? "");
|
|
1921
|
+
index += 2;
|
|
1922
|
+
}
|
|
1923
|
+
// Git grep has the same pattern-versus-file roles as grep below.
|
|
1924
|
+
if (inspected[index] === "grep") {
|
|
1925
|
+
inspected = ["grep", ...inspected.slice(index + 1)];
|
|
1926
|
+
name = "grep";
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1798
1929
|
if (name === "bun" && inspected[1] === "run" && inspected[2] === "gh-tool") {
|
|
1799
1930
|
return inspected.some((arg, index) => {
|
|
1800
1931
|
const path =
|
|
@@ -1934,10 +2065,10 @@ function hasSensitiveFileRead(command: string, isPathBlocked: (path: string) =>
|
|
|
1934
2065
|
}
|
|
1935
2066
|
paths.push(arg);
|
|
1936
2067
|
}
|
|
1937
|
-
return paths.some(
|
|
2068
|
+
return paths.some(blockedOperand);
|
|
1938
2069
|
}
|
|
1939
2070
|
const paths = name === "jq" ? jqFileArguments(inspected) : inspected.slice(1);
|
|
1940
|
-
return paths.some(
|
|
2071
|
+
return paths.some(blockedOperand);
|
|
1941
2072
|
});
|
|
1942
2073
|
}
|
|
1943
2074
|
const operands = parseStaticShellCommands(command, true);
|