@blogic-cz/agent-tools 1.6.9 → 1.6.11
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 +2 -2
- package/dist/credential-guard/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/credential-guard/index.ts +119 -27
package/README.md
CHANGED
|
@@ -654,9 +654,9 @@ Claude Code uses shell command hooks. The package ships a ready-made wrapper scr
|
|
|
654
654
|
|
|
655
655
|
That's it. The hook reads tool input from stdin, runs the guard, and exits with code 2 (blocked + reason on stderr) or 0 (allowed).
|
|
656
656
|
|
|
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.
|
|
657
|
+
The guard permits static `printenv` reads and simple `echo`/`printf` expansions only for exact names in `credentialGuard.allowedEnvironmentVariables`. Quoted empty defaults such as `"${NAME:-}"` follow the same display rules, including `printf` data arguments with a literal format; unquoted defaults, nonempty defaults, and other parameter operators stay blocked. 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;AA2uFF,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,UAjnGD,SAAS,UAAU,UAAU,KAAK,IAinGM,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,aAAa,YAnnGC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAmnGjB,CAAC;AAExD,yEAAyE;AACzE,eAAO,MAAM,aAAa,aArnGE,MAAM,KAAK,OAqnGgB,CAAC;AAExD,yDAAyD;AACzD,eAAO,MAAM,aAAa,aAvnGE,MAAM,KAAK,OAunGgB,CAAC;AAExD,oEAAoE;AACpE,eAAO,MAAM,sBAAsB,YAznGC,MAAM,KAAK,OAynG0B,CAAC;AAE1E,iDAAiD;AACjD,eAAO,MAAM,iBAAiB,YA3nGC,MAAM,KAAK;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IA2nGf,CAAC;AAEhE,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,YA7nGC,MAAM,KAAK,OA6nGsB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,YA/nGC,MAAM,KAAK,MAAM,GAAG,IA+nGa,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();
|
|
@@ -2449,7 +2552,7 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2449
2552
|
const name = variable[1] ?? variable[3] ?? "";
|
|
2450
2553
|
if (variable[2]) {
|
|
2451
2554
|
if (quote !== '"' || !allowedNames.has(name)) invalidExpansion = true;
|
|
2452
|
-
normalized += "
|
|
2555
|
+
normalized += "ENVVALUE";
|
|
2453
2556
|
i += variable[0].length - 1;
|
|
2454
2557
|
continue;
|
|
2455
2558
|
}
|
|
@@ -2503,13 +2606,6 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2503
2606
|
)
|
|
2504
2607
|
return true;
|
|
2505
2608
|
}
|
|
2506
|
-
for (const words of parsed.pipelines.flat()) {
|
|
2507
|
-
if (
|
|
2508
|
-
words.some((arg) => arg.includes("ENVTESTVALUE")) &&
|
|
2509
|
-
!isApprovedEnvironmentPredicate(unwrapStaticCommand(words))
|
|
2510
|
-
)
|
|
2511
|
-
return true;
|
|
2512
|
-
}
|
|
2513
2609
|
for (const words of parsed.pipelines.flat()) {
|
|
2514
2610
|
if (!words.some((arg) => arg.includes("UNKNOWNVALUE"))) continue;
|
|
2515
2611
|
if (words.some((arg) => arg.includes("UNKNOWNVALUEUNQUOTED"))) return true;
|
|
@@ -2520,7 +2616,7 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2520
2616
|
)
|
|
2521
2617
|
return true;
|
|
2522
2618
|
}
|
|
2523
|
-
if (!localMaterialized.includes("ENVVALUE")
|
|
2619
|
+
if (!localMaterialized.includes("ENVVALUE")) {
|
|
2524
2620
|
return (
|
|
2525
2621
|
hasSensitiveFileRead(localMaterialized, isPathBlocked) ||
|
|
2526
2622
|
hasSensitivePathRedirect(localMaterialized, isPathBlocked) ||
|
|
@@ -2530,7 +2626,7 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2530
2626
|
|
|
2531
2627
|
for (const [index, pipeline] of parsed.pipelines.entries()) {
|
|
2532
2628
|
const hasEnvironmentValue = pipeline.some((argv) =>
|
|
2533
|
-
argv.some((arg) => arg.includes("ENVVALUE")
|
|
2629
|
+
argv.some((arg) => arg.includes("ENVVALUE")),
|
|
2534
2630
|
);
|
|
2535
2631
|
if (!hasEnvironmentValue) continue;
|
|
2536
2632
|
if (parsed.redirects.some((redirect) => redirect.pipeline === index)) return true;
|
|
@@ -2538,12 +2634,8 @@ function hasEnvironmentVariableExpansionRead(
|
|
|
2538
2634
|
pipeline.some((argv) => {
|
|
2539
2635
|
const unwrapped = unwrapStaticCommand(argv);
|
|
2540
2636
|
const name = unwrapped[0]?.split("/").at(-1) ?? "";
|
|
2541
|
-
if (argv.some((arg) => arg.includes("ENVVALUE")
|
|
2542
|
-
if (
|
|
2543
|
-
argv
|
|
2544
|
-
.slice(0, argv.length - unwrapped.length)
|
|
2545
|
-
.some((arg) => arg.includes("ENVVALUE") || arg.includes("ENVTESTVALUE"))
|
|
2546
|
-
)
|
|
2637
|
+
if (argv.some((arg) => arg.includes("ENVVALUE"))) {
|
|
2638
|
+
if (argv.slice(0, argv.length - unwrapped.length).some((arg) => arg.includes("ENVVALUE")))
|
|
2547
2639
|
return true;
|
|
2548
2640
|
if (isApprovedEnvironmentPredicate(unwrapped)) return false;
|
|
2549
2641
|
if (isNavigationOperand(unwrapped, "ENVVALUE")) {
|
|
@@ -2579,7 +2671,7 @@ function isApprovedEnvironmentPredicate(argv: string[]): boolean {
|
|
|
2579
2671
|
if (args.at(-1) !== "]") return false;
|
|
2580
2672
|
args = args.slice(0, -1);
|
|
2581
2673
|
} else if (argv[0] !== "test") return false;
|
|
2582
|
-
const value = (arg: string | undefined) => arg === "ENVVALUE"
|
|
2674
|
+
const value = (arg: string | undefined) => arg === "ENVVALUE";
|
|
2583
2675
|
const literal = (arg: string | undefined) =>
|
|
2584
2676
|
/^[A-Za-z0-9_.:-]*$/.test(arg ?? "") && arg !== undefined;
|
|
2585
2677
|
return (
|