@blogic-cz/agent-tools 1.6.9 → 1.6.10
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 +1 -1
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential-guard/index.ts +113 -10
package/README.md
CHANGED
|
@@ -656,7 +656,7 @@ That's it. The hook reads tool input from stdin, runs the guard, and exits with
|
|
|
656
656
|
|
|
657
657
|
The guard permits static `printenv` reads and simple `echo`/`printf` expansions only for exact names in `credentialGuard.allowedEnvironmentVariables`. The default list is empty. Multiple names are allowed only when each name is approved. Approved environment values may be displayed, but redirecting their pipeline or passing them to an executor stays blocked.
|
|
658
658
|
|
|
659
|
-
Python inline programs and quoted stdin heredocs can also display approved values through `os.getenv("NAME")`, `os.environ.get("NAME", "default")`, or `os.environ["NAME"]`. The complete program must
|
|
659
|
+
Python inline programs and quoted stdin heredocs can also display approved values through `os.getenv("NAME")`, `os.environ.get("NAME", "default")`, or `os.environ["NAME"]`. The complete program must import unaliased `os`, plus `json` for JSON display, followed by `print(...)` statements. `json.dumps(...)` supports dictionaries and lists of approved reads, including comprehensions over a nonempty literal list of approved names. Every dictionary key and environment read must use an approved name. Each projection has at most 16 entries; the program has at most 65,536 characters. Computed or unbounded names, aliases, escaped or interpolated strings, additional statements, and whole-environment output remain unsupported. The same redirection and pipeline restrictions apply.
|
|
660
660
|
|
|
661
661
|
Literal assignments such as `F=README.md; cat "$F"` work in supported static commands. Bounded proofs also support a literal `for` loop over a fixed list, a single ASCII-letter path binding such as `cd PATH && A=docs/report.md && shasum -a 256 "$A"` or `F=README.md; cat "$F"`, including the existing `git add -- "$T/file"` form, and literal path braces such as `rg -n -i PATTERN src/{a,b} docs/{guide,api}.md`. Path braces work with supported passive text commands and `cat`, and support one unquoted comma list per path, at most 16 members per list and 32 generated paths per command, within 64 KiB. Nested braces, ranges, empty members, mixed quoting, command positions, and compound commands remain unsupported. Path bindings accept literal values and `;`, newline, or `&&` sequencing; a binding conditional on successful `cd` is proved only for uses in that same `&&` list. Their uses must be supported read-only file operands or explicit `git add --` paths. Rebinding, executable operands, startup-variable names, and uncertain scopes cannot establish this proof. Loop binders are a single lowercase letter or `T`, and their uses must occupy supported data operands. Every body command and expanded path is checked; unsupported control flow, mutations such as `printf -v`, and unknown command roles cannot establish the proof. Other variable assignments and brace expansions retain conservative refusal. Fully quoted literal braces remain usable in static searches.
|
|
662
662
|
|
|
@@ -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;AAsvFF,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,UA5nGD,SAAS,UAAU,UAAU,KAAK,IA4nGM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YA9nGC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IA8nGjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aAhoGE,MAAM,KAAK,OAgoGgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aAloGE,MAAM,KAAK,OAkoGgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YApoGC,MAAM,KAAK,OAooG0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YAtoGC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAsoGf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YAxoGC,MAAM,KAAK,OAwoGsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YA1oGC,MAAM,KAAK,MAAM,GAAG,IA0oGa,CAAC"}
|
package/package.json
CHANGED
|
@@ -1608,8 +1608,10 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1608
1608
|
return false;
|
|
1609
1609
|
|
|
1610
1610
|
const program = argv[index] ?? "";
|
|
1611
|
+
if (program.length > 65_536) return false;
|
|
1611
1612
|
let position = 0;
|
|
1612
1613
|
let foundRead = false;
|
|
1614
|
+
let importedJson = false;
|
|
1613
1615
|
const skipInlineWhitespace = () => {
|
|
1614
1616
|
while (/[\t ]/.test(program[position] ?? "")) position++;
|
|
1615
1617
|
};
|
|
@@ -1619,7 +1621,12 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1619
1621
|
return true;
|
|
1620
1622
|
};
|
|
1621
1623
|
const takeIdentifier = (value: string): boolean => {
|
|
1622
|
-
if (
|
|
1624
|
+
if (
|
|
1625
|
+
!program.startsWith(value, position) ||
|
|
1626
|
+
/[A-Za-z0-9_]/.test(program[position + value.length] ?? "")
|
|
1627
|
+
)
|
|
1628
|
+
return false;
|
|
1629
|
+
position += value.length;
|
|
1623
1630
|
return true;
|
|
1624
1631
|
};
|
|
1625
1632
|
const takeString = (): string | undefined => {
|
|
@@ -1638,8 +1645,14 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1638
1645
|
position++;
|
|
1639
1646
|
return value;
|
|
1640
1647
|
};
|
|
1641
|
-
const takeEnvironmentRead = (): boolean => {
|
|
1648
|
+
const takeEnvironmentRead = (variable?: string): boolean => {
|
|
1642
1649
|
skipInlineWhitespace();
|
|
1650
|
+
const takeName = (): boolean => {
|
|
1651
|
+
skipInlineWhitespace();
|
|
1652
|
+
if (variable !== undefined) return takeIdentifier(variable);
|
|
1653
|
+
const name = takeString();
|
|
1654
|
+
return name !== undefined && allowedNames.has(name);
|
|
1655
|
+
};
|
|
1643
1656
|
const start = position;
|
|
1644
1657
|
if (!takeIdentifier("os")) return false;
|
|
1645
1658
|
if (!take(".")) {
|
|
@@ -1648,8 +1661,7 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1648
1661
|
}
|
|
1649
1662
|
if (takeIdentifier("getenv")) {
|
|
1650
1663
|
if (!take("(")) return false;
|
|
1651
|
-
|
|
1652
|
-
if (name === undefined || !allowedNames.has(name)) return false;
|
|
1664
|
+
if (!takeName()) return false;
|
|
1653
1665
|
skipInlineWhitespace();
|
|
1654
1666
|
if (take(",")) {
|
|
1655
1667
|
if (takeString() === undefined) return false;
|
|
@@ -1662,8 +1674,7 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1662
1674
|
if (!takeIdentifier("environ")) return false;
|
|
1663
1675
|
if (take(".")) {
|
|
1664
1676
|
if (!takeIdentifier("get") || !take("(")) return false;
|
|
1665
|
-
|
|
1666
|
-
if (name === undefined || !allowedNames.has(name)) return false;
|
|
1677
|
+
if (!takeName()) return false;
|
|
1667
1678
|
skipInlineWhitespace();
|
|
1668
1679
|
if (take(",")) {
|
|
1669
1680
|
if (takeString() === undefined) return false;
|
|
@@ -1674,15 +1685,91 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1674
1685
|
return true;
|
|
1675
1686
|
}
|
|
1676
1687
|
if (!take("[")) return false;
|
|
1677
|
-
|
|
1678
|
-
if (name === undefined || !allowedNames.has(name)) return false;
|
|
1688
|
+
if (!takeName()) return false;
|
|
1679
1689
|
skipInlineWhitespace();
|
|
1680
1690
|
if (!take("]")) return false;
|
|
1681
1691
|
foundRead = true;
|
|
1682
1692
|
return true;
|
|
1683
1693
|
};
|
|
1694
|
+
const takeApprovedNames = (): boolean => {
|
|
1695
|
+
if (!take("[")) return false;
|
|
1696
|
+
for (let count = 0; count < 16; count++) {
|
|
1697
|
+
const name = takeString();
|
|
1698
|
+
if (name === undefined || !allowedNames.has(name)) return false;
|
|
1699
|
+
skipInlineWhitespace();
|
|
1700
|
+
if (take("]")) return true;
|
|
1701
|
+
if (!take(",")) return false;
|
|
1702
|
+
skipInlineWhitespace();
|
|
1703
|
+
if (take("]")) return true;
|
|
1704
|
+
}
|
|
1705
|
+
return false;
|
|
1706
|
+
};
|
|
1707
|
+
const takeComprehension = (): boolean => {
|
|
1708
|
+
const dictionary = take("{");
|
|
1709
|
+
if (!dictionary && !take("[")) return false;
|
|
1710
|
+
skipInlineWhitespace();
|
|
1711
|
+
const variable = dictionary
|
|
1712
|
+
? /^[A-Za-z_][A-Za-z0-9_]*/.exec(program.slice(position))?.[0]
|
|
1713
|
+
: /^os\.(?:getenv\(|environ(?:\.get\(|\[))[\t ]*([A-Za-z_][A-Za-z0-9_]*)/.exec(
|
|
1714
|
+
program.slice(position),
|
|
1715
|
+
)?.[1];
|
|
1716
|
+
if (!variable || ["os", "json", "print"].includes(variable)) return false;
|
|
1717
|
+
if (dictionary) {
|
|
1718
|
+
if (!takeIdentifier(variable)) return false;
|
|
1719
|
+
skipInlineWhitespace();
|
|
1720
|
+
if (!take(":")) return false;
|
|
1721
|
+
}
|
|
1722
|
+
if (!takeEnvironmentRead(variable)) return false;
|
|
1723
|
+
const loop = new RegExp(`[\\t ]+for[\\t ]+${variable}[\\t ]+in[\\t ]+`, "y");
|
|
1724
|
+
loop.lastIndex = position;
|
|
1725
|
+
const match = loop.exec(program);
|
|
1726
|
+
if (!match) return false;
|
|
1727
|
+
position = loop.lastIndex;
|
|
1728
|
+
if (!takeApprovedNames()) return false;
|
|
1729
|
+
skipInlineWhitespace();
|
|
1730
|
+
return take(dictionary ? "}" : "]");
|
|
1731
|
+
};
|
|
1732
|
+
const takeProjection = (): boolean => {
|
|
1733
|
+
const start = position;
|
|
1734
|
+
const previousRead = foundRead;
|
|
1735
|
+
if (takeComprehension()) return true;
|
|
1736
|
+
position = start;
|
|
1737
|
+
foundRead = previousRead;
|
|
1738
|
+
const dictionary = take("{");
|
|
1739
|
+
if (!dictionary && !take("[")) return false;
|
|
1740
|
+
const close = dictionary ? "}" : "]";
|
|
1741
|
+
for (let count = 0; count < 16; count++) {
|
|
1742
|
+
if (dictionary) {
|
|
1743
|
+
const key = takeString();
|
|
1744
|
+
if (key === undefined || !allowedNames.has(key)) return false;
|
|
1745
|
+
skipInlineWhitespace();
|
|
1746
|
+
if (!take(":")) return false;
|
|
1747
|
+
}
|
|
1748
|
+
if (!takeEnvironmentRead()) return false;
|
|
1749
|
+
skipInlineWhitespace();
|
|
1750
|
+
if (take(close)) return true;
|
|
1751
|
+
if (!take(",")) return false;
|
|
1752
|
+
skipInlineWhitespace();
|
|
1753
|
+
if (take(close)) return true;
|
|
1754
|
+
}
|
|
1755
|
+
return false;
|
|
1756
|
+
};
|
|
1757
|
+
const takeJsonDisplay = (): boolean => {
|
|
1758
|
+
if (!importedJson || !takeIdentifier("json") || !take(".dumps")) return false;
|
|
1759
|
+
skipInlineWhitespace();
|
|
1760
|
+
if (!take("(")) return false;
|
|
1761
|
+
skipInlineWhitespace();
|
|
1762
|
+
if (!takeProjection()) return false;
|
|
1763
|
+
skipInlineWhitespace();
|
|
1764
|
+
return take(")");
|
|
1765
|
+
};
|
|
1684
1766
|
const takeExpression = (): boolean => {
|
|
1685
|
-
const takeTerm = (): boolean =>
|
|
1767
|
+
const takeTerm = (): boolean => {
|
|
1768
|
+
skipInlineWhitespace();
|
|
1769
|
+
return program.startsWith("json", position)
|
|
1770
|
+
? takeJsonDisplay()
|
|
1771
|
+
: takeString() !== undefined || takeEnvironmentRead();
|
|
1772
|
+
};
|
|
1686
1773
|
if (!takeTerm()) return false;
|
|
1687
1774
|
skipInlineWhitespace();
|
|
1688
1775
|
while (take("+")) {
|
|
@@ -1718,10 +1805,26 @@ function isAllowedPythonEnvironmentRead(argv: string[], allowedNames: Set<string
|
|
|
1718
1805
|
skipInlineWhitespace();
|
|
1719
1806
|
if (!takeIdentifier("import")) return false;
|
|
1720
1807
|
skipInlineWhitespace();
|
|
1721
|
-
|
|
1808
|
+
const jsonFirst = takeIdentifier("json");
|
|
1809
|
+
if (!jsonFirst && !takeIdentifier("os")) return false;
|
|
1810
|
+
importedJson = jsonFirst;
|
|
1811
|
+
skipInlineWhitespace();
|
|
1812
|
+
if (take(",")) {
|
|
1813
|
+
skipInlineWhitespace();
|
|
1814
|
+
if (!takeIdentifier(jsonFirst ? "os" : "json")) return false;
|
|
1815
|
+
importedJson = true;
|
|
1816
|
+
} else if (jsonFirst) return false;
|
|
1722
1817
|
skipInlineWhitespace();
|
|
1723
1818
|
if (!(take(";") || take("\n") || take("\r\n"))) return false;
|
|
1724
1819
|
skipSeparators();
|
|
1820
|
+
if (!importedJson && takeIdentifier("import")) {
|
|
1821
|
+
skipInlineWhitespace();
|
|
1822
|
+
if (!takeIdentifier("json")) return false;
|
|
1823
|
+
importedJson = true;
|
|
1824
|
+
skipInlineWhitespace();
|
|
1825
|
+
if (!(take(";") || take("\n") || take("\r\n"))) return false;
|
|
1826
|
+
skipSeparators();
|
|
1827
|
+
}
|
|
1725
1828
|
while (position < program.length) {
|
|
1726
1829
|
if (!takePrint()) return false;
|
|
1727
1830
|
skipInlineWhitespace();
|