@blogic-cz/agent-tools 1.6.2 → 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 CHANGED
@@ -656,6 +656,8 @@ Ordinary navigation operands such as `cd "$HOME"` and `git -C "$WORKTREE_ROOT" s
656
656
 
657
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
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
+
659
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.
660
662
 
661
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;AA6tEF,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,UAnkFD,SAAS,UAAU,UAAU,KAAK,IAmkFM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YArkFC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAqkFjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aAvkFE,MAAM,KAAK,OAukFgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aAzkFE,MAAM,KAAK,OAykFgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YA3kFC,MAAM,KAAK,OA2kF0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YA7kFC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IA6kFf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YA/kFC,MAAM,KAAK,OA+kFsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YAjlFC,MAAM,KAAK,MAAM,GAAG,IAilFa,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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blogic-cz/agent-tools",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "CLI tools for AI coding agent workflows — GitHub, database, Kubernetes, Azure platform, Azure DevOps, logs, sessions, and audit",
5
5
  "keywords": [
6
6
  "agent",
@@ -1548,6 +1548,56 @@ function isNavigationOperand(argv: string[], marker: string): boolean {
1548
1548
  );
1549
1549
  }
1550
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
+
1551
1601
  function isPassiveTextCommand(argv: string[]): boolean {
1552
1602
  const name = argv[0]?.split("/").at(-1) ?? "";
1553
1603
  const args = argv.slice(1);
@@ -1627,13 +1677,15 @@ function hasStaticEnvironmentRead(
1627
1677
 
1628
1678
  function isStaticHerdrPrompt(command: string): boolean {
1629
1679
  // Preserve shell word boundaries and executable identity before proving composition.
1630
- if (!literalBindingText(command, "", "")) return false;
1680
+ const literal = literalBindingText(command, "", "");
1681
+ if (!literal) return false;
1631
1682
  const parsed = parseStaticShellCommands(command);
1632
1683
  if (!parsed || typeof parsed === "string") return false;
1633
1684
  const commands = parsed.pipelines.flat().map((words) => unwrapProofCommand(words, "\0"));
1634
1685
  const prompts = commands.filter(
1635
1686
  (argv) => argv?.[0] === "herdr" && argv[1] === "agent" && argv[2] === "prompt",
1636
1687
  );
1688
+ if (!hasProvedNavigationSequence(command)) return false;
1637
1689
  return (
1638
1690
  prompts.length > 0 &&
1639
1691
  parsed.pipelines.every((pipeline) =>
@@ -1649,7 +1701,10 @@ function isStaticHerdrPrompt(command: string): boolean {
1649
1701
  argv.length > 1 &&
1650
1702
  argv.slice(1).every((arg) => arg === "--" || !arg.startsWith("-"))
1651
1703
  );
1652
- return isPassiveTextCommand(argv);
1704
+ if (isPassiveTextCommand(argv)) return true;
1705
+ if (isStaticNavigationCommand(argv))
1706
+ return index === 0 && (argv[0] !== "cd" || pipeline.length === 1);
1707
+ return false;
1653
1708
  }),
1654
1709
  )
1655
1710
  );
@@ -1836,13 +1891,41 @@ function hasSensitiveFileRead(command: string, isPathBlocked: (path: string) =>
1836
1891
  const readers =
1837
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;
1838
1893
  if (parsed && parsed !== "brace-expansion") {
1894
+ let directory = "";
1895
+ const trackDirectory = hasProvedNavigationSequence(command);
1839
1896
  return parsed.pipelines.flat().some((words) => {
1840
1897
  const argv = unwrapStaticCommand(words);
1841
1898
  const inspectedArgv =
1842
1899
  argv[0]?.split("/").at(-1) === "env" ? unwrapEnvironmentCommand(argv) : argv;
1843
1900
  if (inspectedArgv === null) return false;
1844
- const inspected = unwrapStaticCommand(inspectedArgv);
1845
- const name = inspected[0]?.split("/").at(-1) ?? "";
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
+ }
1846
1929
  if (name === "bun" && inspected[1] === "run" && inspected[2] === "gh-tool") {
1847
1930
  return inspected.some((arg, index) => {
1848
1931
  const path =
@@ -1982,10 +2065,10 @@ function hasSensitiveFileRead(command: string, isPathBlocked: (path: string) =>
1982
2065
  }
1983
2066
  paths.push(arg);
1984
2067
  }
1985
- return paths.some((path) => isPathBlocked(path) || isSensitivePath(path));
2068
+ return paths.some(blockedOperand);
1986
2069
  }
1987
2070
  const paths = name === "jq" ? jqFileArguments(inspected) : inspected.slice(1);
1988
- return paths.some((path) => isPathBlocked(path) || isSensitivePath(path));
2071
+ return paths.some(blockedOperand);
1989
2072
  });
1990
2073
  }
1991
2074
  const operands = parseStaticShellCommands(command, true);