@node9/proxy 2.12.0 → 2.13.1
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/cli.js +543 -514
- package/dist/cli.mjs +543 -514
- package/dist/dashboard.mjs +2580 -2327
- package/dist/index.js +169 -84
- package/dist/index.mjs +169 -84
- package/dist/scan-ink.mjs +43 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1162,13 +1162,32 @@ var DLP_PATTERNS_GLOBAL = DLP_PATTERNS.map(
|
|
|
1162
1162
|
})
|
|
1163
1163
|
);
|
|
1164
1164
|
var SENSITIVE_PATH_PATTERNS = [
|
|
1165
|
-
/[/\\]\.ssh[/\\]/i,
|
|
1166
|
-
/[/\\]\.aws[/\\]/i,
|
|
1165
|
+
/[/\\]\.ssh([/\\]|$)/i,
|
|
1166
|
+
/[/\\]\.aws([/\\]|$)/i,
|
|
1167
1167
|
/[/\\]\.config[/\\]gcloud[/\\]/i,
|
|
1168
1168
|
/[/\\]\.azure[/\\]/i,
|
|
1169
1169
|
/[/\\]\.kube[/\\]config$/i,
|
|
1170
|
-
|
|
1171
|
-
// .
|
|
1170
|
+
// ⚠️ ONE SEMANTIC, FOUR COPIES. This is the AST tier's `.env` rule verbatim
|
|
1171
|
+
// (shell/index.ts SENSITIVE_PATH_RULES), whose reasoning is documented there:
|
|
1172
|
+
// structural suffix chain rather than a hand-written list, `example|sample|
|
|
1173
|
+
// template` exempt because a fixture stays a fixture whatever follows, and
|
|
1174
|
+
// `.test` anchored because `test` names an ENVIRONMENT -- `.env.test` is the
|
|
1175
|
+
// committed template, `.env.test.local` is gitignored and holds real values.
|
|
1176
|
+
//
|
|
1177
|
+
// It was previously `[/\\]\.env($|\.)` with NO exemptions, so `Read .env.example`
|
|
1178
|
+
// blocked while `cat .env.example` allowed: the same file, opposite verdicts,
|
|
1179
|
+
// decided only by which tool asked. See src/__tests__/jail-both-doors.test.ts,
|
|
1180
|
+
// which is the contract that now holds these copies in step, and stage 5 of
|
|
1181
|
+
// doc/credential-jail-architecture.md, which replaces them with one generated
|
|
1182
|
+
// source.
|
|
1183
|
+
// ⚠️ The `.local` branch comes FIRST and takes no exemption. A fixture stays a
|
|
1184
|
+
// fixture whatever follows it -- `.env.example.md` is documentation -- but
|
|
1185
|
+
// `.env.example.local` is gitignored by the `.env*.local` convention and holds
|
|
1186
|
+
// real values, exactly the reasoning that anchors `(?!\.test$)` rather than
|
|
1187
|
+
// using `\b`. Without this branch the fixture exemption also bought a two-step
|
|
1188
|
+
// bypass: `cp .env .env.sample`, then read the copy.
|
|
1189
|
+
/[/\\]\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
1190
|
+
// .env + any suffix chain; fixtures exempt unless .local
|
|
1172
1191
|
/[/\\]\.git-credentials$/i,
|
|
1173
1192
|
/[/\\]\.npmrc$/i,
|
|
1174
1193
|
/[/\\]\.docker[/\\]config\.json$/i,
|
|
@@ -1670,6 +1689,10 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1670
1689
|
"od",
|
|
1671
1690
|
"xxd",
|
|
1672
1691
|
"hexdump",
|
|
1692
|
+
// Emits the file's bytes, re-encoded, so it is a read by the set's own test
|
|
1693
|
+
// ("does it emit file contents"). Absent until 2026-09-10, which is why
|
|
1694
|
+
// `base64 ~/.ssh/id_rsa` printed a private key with no verdict.
|
|
1695
|
+
"base64",
|
|
1673
1696
|
"strings",
|
|
1674
1697
|
"sort",
|
|
1675
1698
|
"uniq",
|
|
@@ -1678,7 +1701,11 @@ var FS_READ_TOOLS = /* @__PURE__ */ new Set([
|
|
|
1678
1701
|
"dd"
|
|
1679
1702
|
]);
|
|
1680
1703
|
var FS_OP_PRESCREEN_RE = new RegExp(
|
|
1681
|
-
|
|
1704
|
+
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
1705
|
+
// reader right after `"` / `'`, and without these two characters the
|
|
1706
|
+
// prescreen rejected every string-wrapped read before the parser ran.
|
|
1707
|
+
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
1708
|
+
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
1682
1709
|
);
|
|
1683
1710
|
var HOME_CACHE_ALLOWLIST = [
|
|
1684
1711
|
".cache",
|
|
@@ -1699,12 +1726,12 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1699
1726
|
{
|
|
1700
1727
|
rule: "shield:project-jail:block-read-ssh",
|
|
1701
1728
|
reason: "Reading SSH private keys is blocked by project-jail shield",
|
|
1702
|
-
match: (p) => /(
|
|
1729
|
+
match: (p) => /([\\/]\.ssh[\\/]|^\.ssh[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.ssh$)/i.test(p)
|
|
1703
1730
|
},
|
|
1704
1731
|
{
|
|
1705
1732
|
rule: "shield:project-jail:block-read-aws",
|
|
1706
1733
|
reason: "Reading AWS credentials is blocked by project-jail shield",
|
|
1707
|
-
match: (p) => /(
|
|
1734
|
+
match: (p) => /([\\/]\.aws[\\/]|^\.aws[\\/]|^(?:[~/]|[A-Za-z]:).*[\\/]\.aws$)/i.test(p)
|
|
1708
1735
|
},
|
|
1709
1736
|
{
|
|
1710
1737
|
// Mirrors the JSON shield's `.env` pattern (project-jail.json's
|
|
@@ -1748,7 +1775,9 @@ var SENSITIVE_PATH_RULES = [
|
|
|
1748
1775
|
// symmetry — silently exempts every `.env.test.*` file.
|
|
1749
1776
|
//
|
|
1750
1777
|
// shields.test.ts:983-995 is the canonical contract; keep both in step.
|
|
1751
|
-
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]
|
|
1778
|
+
match: (p) => /(?:^|[\\/])\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i.test(
|
|
1779
|
+
p
|
|
1780
|
+
)
|
|
1752
1781
|
},
|
|
1753
1782
|
{
|
|
1754
1783
|
// verdict: 'review' (not 'block') is a deliberate design choice
|
|
@@ -1959,15 +1988,14 @@ function listOps() {
|
|
|
1959
1988
|
return _listOps;
|
|
1960
1989
|
}
|
|
1961
1990
|
var WRAPPER_TAKES_TARGET = /* @__PURE__ */ new Set(["chroot"]);
|
|
1991
|
+
var FIND_EXEC_FLAGS = /* @__PURE__ */ new Set(["-exec", "-execdir", "-ok", "-okdir"]);
|
|
1962
1992
|
var INTERP_LEADING_TARGET = /* @__PURE__ */ new Set(["su"]);
|
|
1963
1993
|
function unwrapCommandHead(words) {
|
|
1964
1994
|
let i = 0;
|
|
1965
1995
|
while (i < words.length) {
|
|
1966
1996
|
const head = (words[i] ?? "").toLowerCase().split("/").pop() ?? "";
|
|
1967
1997
|
if (head === "find") {
|
|
1968
|
-
const x = words.findIndex(
|
|
1969
|
-
(w, k) => k > i && (w === "-exec" || w === "-execdir" || w === "-ok")
|
|
1970
|
-
);
|
|
1998
|
+
const x = words.findIndex((w, k) => k > i && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1971
1999
|
if (x < 0) break;
|
|
1972
2000
|
i = x + 1;
|
|
1973
2001
|
continue;
|
|
@@ -1989,7 +2017,11 @@ function unwrapCommandHead(words) {
|
|
|
1989
2017
|
if (t.startsWith("-")) {
|
|
1990
2018
|
i++;
|
|
1991
2019
|
const nxt = words[i];
|
|
1992
|
-
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase())
|
|
2020
|
+
if (nxt != null && !nxt.startsWith("-") && !INLINE_INTERPRETER.test(nxt.split("/").pop() ?? "") && !COMMAND_WRAPPERS.has(nxt.toLowerCase()) && !RUNNER_WRAPPERS.has(nxt.toLowerCase()) && // A reader is a command, never a flag's operand: `env - cat X`,
|
|
2021
|
+
// `stdbuf -o0 cat X`, `ionice -c3 cat X`. Without this the head was
|
|
2022
|
+
// swallowed and the jail needed a looser fallback whose cost was a
|
|
2023
|
+
// false positive on `sudo echo cat X`.
|
|
2024
|
+
!FS_READ_TOOLS.has(nxt.split("/").pop()?.toLowerCase() ?? ""))
|
|
1993
2025
|
i++;
|
|
1994
2026
|
continue;
|
|
1995
2027
|
}
|
|
@@ -2110,36 +2142,29 @@ function isProtectedHomePath(rawPath) {
|
|
|
2110
2142
|
}
|
|
2111
2143
|
function extractLiteralArgs(callExpr) {
|
|
2112
2144
|
const args = callExpr.Args || [];
|
|
2113
|
-
if (args.length === 0) return { name: "", flags: [], paths: [] };
|
|
2114
|
-
const
|
|
2115
|
-
|
|
2116
|
-
let s = "";
|
|
2117
|
-
for (const p of parts) {
|
|
2118
|
-
const t = syntax.NodeType(p);
|
|
2119
|
-
if (t === "Lit") s += (p.Value ?? "").replace(/\\(.)/g, "$1");
|
|
2120
|
-
else if (t === "SglQuoted") s += p.Value ?? "";
|
|
2121
|
-
else if (t === "DblQuoted") {
|
|
2122
|
-
const inner = p.Parts || [];
|
|
2123
|
-
if (!inner.every((ip) => syntax.NodeType(ip) === "Lit")) return null;
|
|
2124
|
-
s += inner.map((ip) => ip.Value ?? "").join("");
|
|
2125
|
-
} else {
|
|
2126
|
-
return null;
|
|
2127
|
-
}
|
|
2128
|
-
}
|
|
2129
|
-
return s;
|
|
2130
|
-
};
|
|
2131
|
-
const name = (litFromWord(args[0]) || "").toLowerCase();
|
|
2145
|
+
if (args.length === 0) return { name: "", flags: [], paths: [], words: [] };
|
|
2146
|
+
const words = args.map((a) => resolveWordLiteral(a));
|
|
2147
|
+
const name = (words[0] ?? "").toLowerCase();
|
|
2132
2148
|
const flags = [];
|
|
2133
2149
|
const paths = [];
|
|
2134
|
-
for (let i = 1; i <
|
|
2135
|
-
const v =
|
|
2150
|
+
for (let i = 1; i < words.length; i++) {
|
|
2151
|
+
const v = words[i];
|
|
2136
2152
|
if (v === null) continue;
|
|
2137
2153
|
if (v.startsWith("-")) flags.push(v);
|
|
2138
2154
|
else paths.push(v);
|
|
2139
2155
|
}
|
|
2140
|
-
return { name, flags, paths };
|
|
2156
|
+
return { name, flags, paths, words };
|
|
2141
2157
|
}
|
|
2142
|
-
var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
2158
|
+
var NET_BINARIES = /* @__PURE__ */ new Set([
|
|
2159
|
+
"curl",
|
|
2160
|
+
"wget",
|
|
2161
|
+
"scp",
|
|
2162
|
+
"ssh",
|
|
2163
|
+
"nc",
|
|
2164
|
+
"ncat",
|
|
2165
|
+
"netcat",
|
|
2166
|
+
"rsync"
|
|
2167
|
+
]);
|
|
2143
2168
|
var VALUE_FLAGS = {
|
|
2144
2169
|
curl: /* @__PURE__ */ new Set([
|
|
2145
2170
|
"-d",
|
|
@@ -2424,6 +2449,9 @@ function deriveRedirOp(sample) {
|
|
|
2424
2449
|
}
|
|
2425
2450
|
}
|
|
2426
2451
|
var REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
|
|
2452
|
+
var REDIR_FILE_IN_OPS = new Set(
|
|
2453
|
+
[deriveRedirOp("cat < f"), deriveRedirOp("cat <> f")].filter((op) => op >= 0)
|
|
2454
|
+
);
|
|
2427
2455
|
var REDIR_HEREDOC_OPS = /* @__PURE__ */ new Set([
|
|
2428
2456
|
deriveRedirOp("cat <<X\nX"),
|
|
2429
2457
|
deriveRedirOp("cat <<-X\nX")
|
|
@@ -2488,16 +2516,21 @@ function isRmCreatedInCommandCleanup(command) {
|
|
|
2488
2516
|
}
|
|
2489
2517
|
return sawRm && ok;
|
|
2490
2518
|
}
|
|
2491
|
-
function analyzeFsOperationImpl(command) {
|
|
2519
|
+
function analyzeFsOperationImpl(command, depth = 0) {
|
|
2492
2520
|
const f = parseShared(command);
|
|
2493
2521
|
if (f === PARSE_FAIL) return null;
|
|
2494
2522
|
let result = null;
|
|
2495
2523
|
try {
|
|
2496
2524
|
syntax.Walk(f, (node) => {
|
|
2497
|
-
if (!node || result) return false;
|
|
2525
|
+
if (!node || result?.verdict === "block") return false;
|
|
2498
2526
|
const n = node;
|
|
2499
|
-
|
|
2500
|
-
|
|
2527
|
+
const nodeType = syntax.NodeType(n);
|
|
2528
|
+
if (nodeType === "Stmt") {
|
|
2529
|
+
result = stricter(result, jailedRedirectRead(n));
|
|
2530
|
+
return result?.verdict !== "block";
|
|
2531
|
+
}
|
|
2532
|
+
if (nodeType !== "CallExpr") return true;
|
|
2533
|
+
const { name, flags, paths, words } = extractLiteralArgs(n);
|
|
2501
2534
|
if (!name) return true;
|
|
2502
2535
|
if (name === "rm") {
|
|
2503
2536
|
const flagStr = flags.join("").toLowerCase();
|
|
@@ -2526,19 +2559,22 @@ function analyzeFsOperationImpl(command) {
|
|
|
2526
2559
|
}
|
|
2527
2560
|
}
|
|
2528
2561
|
}
|
|
2529
|
-
if (
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
reason: sp.reason,
|
|
2537
|
-
path: p
|
|
2538
|
-
};
|
|
2539
|
-
return false;
|
|
2540
|
-
}
|
|
2562
|
+
if (depth < 1) {
|
|
2563
|
+
const payload = literalShellPayload(words, name);
|
|
2564
|
+
if (payload !== null) {
|
|
2565
|
+
const inner = analyzeFsOperationImpl(payload, depth + 1);
|
|
2566
|
+
if (inner) {
|
|
2567
|
+
result = inner;
|
|
2568
|
+
return false;
|
|
2541
2569
|
}
|
|
2570
|
+
return true;
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
const readPaths = FS_READ_TOOLS.has(name) ? paths : wrappedReadPaths(words, name);
|
|
2574
|
+
if (readPaths) {
|
|
2575
|
+
for (const p of readPaths) {
|
|
2576
|
+
result = stricter(result, matchSensitivePath2(p));
|
|
2577
|
+
if (result?.verdict === "block") return false;
|
|
2542
2578
|
}
|
|
2543
2579
|
}
|
|
2544
2580
|
return true;
|
|
@@ -2548,6 +2584,57 @@ function analyzeFsOperationImpl(command) {
|
|
|
2548
2584
|
return null;
|
|
2549
2585
|
}
|
|
2550
2586
|
}
|
|
2587
|
+
function stricter(a, b) {
|
|
2588
|
+
if (!a) return b;
|
|
2589
|
+
if (!b) return a;
|
|
2590
|
+
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
2591
|
+
}
|
|
2592
|
+
function matchSensitivePath2(p) {
|
|
2593
|
+
for (const sp of SENSITIVE_PATH_RULES) {
|
|
2594
|
+
if (sp.match(p))
|
|
2595
|
+
return { ruleName: sp.rule, verdict: sp.verdict ?? "block", reason: sp.reason, path: p };
|
|
2596
|
+
}
|
|
2597
|
+
return null;
|
|
2598
|
+
}
|
|
2599
|
+
var isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(w.split("/").pop()?.toLowerCase() ?? "");
|
|
2600
|
+
var positionalAfter = (words, from, to = words.length) => words.slice(from, to).filter((w) => w !== null && !w.startsWith("-"));
|
|
2601
|
+
function wrappedReadPaths(words, name) {
|
|
2602
|
+
if (name === "find") {
|
|
2603
|
+
const k = words.findIndex((w) => w !== null && FIND_EXEC_FLAGS.has(w));
|
|
2604
|
+
if (k < 1 || !isReaderWord(words[k + 1] ?? null)) return null;
|
|
2605
|
+
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
2606
|
+
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
2607
|
+
}
|
|
2608
|
+
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
2609
|
+
const h = unwrapCommandHead(words);
|
|
2610
|
+
return h > 0 && isReaderWord(words[h] ?? null) ? positionalAfter(words, h + 1) : null;
|
|
2611
|
+
}
|
|
2612
|
+
function literalShellPayload(words, name) {
|
|
2613
|
+
const h = COMMAND_WRAPPERS.has(name) || RUNNER_WRAPPERS.has(name) ? unwrapCommandHead(words) : 0;
|
|
2614
|
+
const head = (words[h] ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
2615
|
+
if (head === "eval") {
|
|
2616
|
+
const rest = words.slice(h + 1);
|
|
2617
|
+
if (rest.length === 0 || rest.some((w) => w === null)) return null;
|
|
2618
|
+
return rest.join(" ");
|
|
2619
|
+
}
|
|
2620
|
+
if (SHELL_INTERPRETERS.has(head)) {
|
|
2621
|
+
const c = words.findIndex((w, i) => i > h && w !== null && isInlineCodeFlag(head, w));
|
|
2622
|
+
if (c < 0) return null;
|
|
2623
|
+
return words[c + 1] ?? null;
|
|
2624
|
+
}
|
|
2625
|
+
return null;
|
|
2626
|
+
}
|
|
2627
|
+
function jailedRedirectRead(stmt) {
|
|
2628
|
+
const redirs = stmt.Redirs || [];
|
|
2629
|
+
for (const r of redirs) {
|
|
2630
|
+
if (!r || !REDIR_FILE_IN_OPS.has(r.Op)) continue;
|
|
2631
|
+
const p = resolveWordLiteral(r.Word);
|
|
2632
|
+
if (p === null || p === "") continue;
|
|
2633
|
+
const hit = matchSensitivePath2(p);
|
|
2634
|
+
if (hit) return hit;
|
|
2635
|
+
}
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2551
2638
|
function analyzeShellCommand(command) {
|
|
2552
2639
|
const actions = [];
|
|
2553
2640
|
const paths = [];
|
|
@@ -2680,21 +2767,7 @@ function evaluateEgress(dests, policy) {
|
|
|
2680
2767
|
}
|
|
2681
2768
|
return review;
|
|
2682
2769
|
}
|
|
2683
|
-
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([
|
|
2684
|
-
"cat",
|
|
2685
|
-
"head",
|
|
2686
|
-
"tail",
|
|
2687
|
-
"grep",
|
|
2688
|
-
"awk",
|
|
2689
|
-
"sed",
|
|
2690
|
-
"cut",
|
|
2691
|
-
"sort",
|
|
2692
|
-
"tee",
|
|
2693
|
-
"less",
|
|
2694
|
-
"more",
|
|
2695
|
-
"strings",
|
|
2696
|
-
"xxd"
|
|
2697
|
-
]);
|
|
2770
|
+
var SOURCE_COMMANDS = /* @__PURE__ */ new Set([...FS_READ_TOOLS, "tee"]);
|
|
2698
2771
|
var SINK_COMMANDS = /* @__PURE__ */ new Set([
|
|
2699
2772
|
"curl",
|
|
2700
2773
|
"wget",
|
|
@@ -2725,16 +2798,25 @@ var OBFUSCATORS = /* @__PURE__ */ new Set([
|
|
|
2725
2798
|
"node"
|
|
2726
2799
|
]);
|
|
2727
2800
|
var SENSITIVE_PATTERNS = [
|
|
2728
|
-
/
|
|
2729
|
-
|
|
2801
|
+
// Kept in step with the AST tier and dlp/ -- see jail-both-doors.test.ts.
|
|
2802
|
+
/(?:^|\/)\.env(?![\w-])(?:[\w.-]*\.local$|(?!\.(?:example|sample|template)\b)(?!\.test$)[\w.-]*$)/i,
|
|
2803
|
+
// .env chain; fixtures exempt unless .local
|
|
2730
2804
|
/id_rsa|id_ed25519|id_ecdsa|id_dsa/i,
|
|
2731
2805
|
// SSH private keys
|
|
2732
2806
|
/\.pem$|\.key$|\.p12$|\.pfx$/i,
|
|
2733
2807
|
// certificate files
|
|
2734
|
-
/
|
|
2735
|
-
//
|
|
2736
|
-
/
|
|
2737
|
-
//
|
|
2808
|
+
// The `$` half mirrors shell/index.ts's SENSITIVE_PATH_RULES: a file INSIDE
|
|
2809
|
+
// the directory counts wherever it appears, while the directory ITSELF counts
|
|
2810
|
+
// only when the path is ROOTED (`~/.ssh`, `/home/u/.ssh`) -- an unrooted
|
|
2811
|
+
// `config/.ssh` is more likely a search pattern than a read. These are
|
|
2812
|
+
// extracted TOKENS (see `args.some(isSensitivePath)` below), the same input
|
|
2813
|
+
// contract as the shell tier, so the same boundary is the right one.
|
|
2814
|
+
// Without it `grep -r x ~/.ssh | curl -d @-` scored one tier BELOW the
|
|
2815
|
+
// identical pipeline naming a file inside that directory.
|
|
2816
|
+
/(?:^|\/)\.ssh\/|^(?:[~/]|[A-Za-z]:).*\/\.ssh$/i,
|
|
2817
|
+
// ~/.ssh/ and ~/.ssh
|
|
2818
|
+
/(?:^|\/)\.aws\/credentials|^(?:[~/]|[A-Za-z]:).*\/\.aws$/i,
|
|
2819
|
+
// AWS creds + dir
|
|
2738
2820
|
/(?:^|\/)\.netrc$/i,
|
|
2739
2821
|
// netrc (stores HTTP credentials)
|
|
2740
2822
|
/(?:^|\/)(passwd|shadow|sudoers)$/i,
|
|
@@ -2768,8 +2850,8 @@ function splitOnPipe(cmd) {
|
|
|
2768
2850
|
if (current.trim()) segments2.push(current.trim());
|
|
2769
2851
|
return segments2.filter(Boolean);
|
|
2770
2852
|
}
|
|
2771
|
-
function positionalTokens(
|
|
2772
|
-
return
|
|
2853
|
+
function positionalTokens(tokens) {
|
|
2854
|
+
return tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("@") && t.length > 0);
|
|
2773
2855
|
}
|
|
2774
2856
|
function analyzePipeChain(command) {
|
|
2775
2857
|
const segments2 = splitOnPipe(command);
|
|
@@ -2792,8 +2874,10 @@ function analyzePipeChain(command) {
|
|
|
2792
2874
|
for (const segment of segments2) {
|
|
2793
2875
|
const tokens = segment.split(/\s+/).filter(Boolean);
|
|
2794
2876
|
if (tokens.length === 0) continue;
|
|
2795
|
-
const
|
|
2796
|
-
const
|
|
2877
|
+
const h = unwrapCommandHead(tokens);
|
|
2878
|
+
const head = h < tokens.length ? h : 0;
|
|
2879
|
+
const binary = tokens[head].toLowerCase();
|
|
2880
|
+
const args = positionalTokens(tokens.slice(head));
|
|
2797
2881
|
if (SOURCE_COMMANDS.has(binary)) {
|
|
2798
2882
|
sourceFiles.push(...args);
|
|
2799
2883
|
if (args.some(isSensitivePath)) hasSensitiveSource = true;
|
|
@@ -4192,7 +4276,7 @@ var project_jail_default = {
|
|
|
4192
4276
|
{
|
|
4193
4277
|
field: "command",
|
|
4194
4278
|
op: "matches",
|
|
4195
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
4279
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.ssh[\\/\\\\]",
|
|
4196
4280
|
flags: "i"
|
|
4197
4281
|
}
|
|
4198
4282
|
],
|
|
@@ -4206,7 +4290,7 @@ var project_jail_default = {
|
|
|
4206
4290
|
{
|
|
4207
4291
|
field: "command",
|
|
4208
4292
|
op: "matches",
|
|
4209
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
4293
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.aws[\\/\\\\]",
|
|
4210
4294
|
flags: "i"
|
|
4211
4295
|
}
|
|
4212
4296
|
],
|
|
@@ -4220,7 +4304,7 @@ var project_jail_default = {
|
|
|
4220
4304
|
{
|
|
4221
4305
|
field: "command",
|
|
4222
4306
|
op: "matches",
|
|
4223
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
4307
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*?\\.env(\\.(local|production|staging|development|production\\.local|staging\\.local|development\\.local))?(?=\\s|$|[;&|>)<])",
|
|
4224
4308
|
flags: "i"
|
|
4225
4309
|
}
|
|
4226
4310
|
],
|
|
@@ -4234,7 +4318,7 @@ var project_jail_default = {
|
|
|
4234
4318
|
{
|
|
4235
4319
|
field: "command",
|
|
4236
4320
|
op: "matches",
|
|
4237
|
-
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
4321
|
+
value: "(cat|less|head|tail|bat|more|open|print|nano|vim|vi|emacs|code|type|grep|egrep|fgrep|rg|ag|ack|awk|gawk|sed|cut|tr|jq|yq|od|xxd|hexdump|strings|base64|sort|uniq|tac|nl|dd)\\s+.*(credentials\\.json|\\.netrc|\\.npmrc|\\.docker[\\/\\\\]config\\.json|gcloud[\\/\\\\]credentials)",
|
|
4238
4322
|
flags: "i"
|
|
4239
4323
|
}
|
|
4240
4324
|
],
|
|
@@ -4248,7 +4332,7 @@ var project_jail_default = {
|
|
|
4248
4332
|
{
|
|
4249
4333
|
field: "file_path",
|
|
4250
4334
|
op: "matches",
|
|
4251
|
-
value: "(
|
|
4335
|
+
value: "([\\/\\\\]\\.ssh([\\/\\\\]|$)|^\\.ssh[\\/\\\\])",
|
|
4252
4336
|
flags: "i"
|
|
4253
4337
|
}
|
|
4254
4338
|
],
|
|
@@ -4262,7 +4346,7 @@ var project_jail_default = {
|
|
|
4262
4346
|
{
|
|
4263
4347
|
field: "file_path",
|
|
4264
4348
|
op: "matches",
|
|
4265
|
-
value: "(
|
|
4349
|
+
value: "([\\/\\\\]\\.aws([\\/\\\\]|$)|^\\.aws[\\/\\\\])",
|
|
4266
4350
|
flags: "i"
|
|
4267
4351
|
}
|
|
4268
4352
|
],
|
|
@@ -4276,7 +4360,7 @@ var project_jail_default = {
|
|
|
4276
4360
|
{
|
|
4277
4361
|
field: "file_path",
|
|
4278
4362
|
op: "matches",
|
|
4279
|
-
value: "(^|[\\/\\\\])\\.env(
|
|
4363
|
+
value: "(^|[\\/\\\\])\\.env(?![\\w-])(?:[\\w.-]*\\.local$|(?!\\.(example|sample|template)\\b)(?!\\.test$)[\\w.-]*$)",
|
|
4280
4364
|
flags: "i"
|
|
4281
4365
|
}
|
|
4282
4366
|
],
|
|
@@ -7053,12 +7137,13 @@ function extractFilePaths(toolName, args) {
|
|
|
7053
7137
|
}
|
|
7054
7138
|
return paths.filter(Boolean);
|
|
7055
7139
|
}
|
|
7140
|
+
var NETWORK_COMMAND_RE = new RegExp(`(?<![.\\w-])(${[...NET_BINARIES].join("|")})\\b`);
|
|
7056
7141
|
function isNetworkTool(toolName, args) {
|
|
7057
7142
|
const t = toolName.toLowerCase();
|
|
7058
7143
|
if (t === "bash" || t === "shell" || t === "run_shell_command" || t === "terminal.execute") {
|
|
7059
7144
|
const a = args;
|
|
7060
7145
|
const cmd = typeof a?.command === "string" ? a.command : typeof a?.cmd === "string" ? a.cmd : "";
|
|
7061
|
-
return
|
|
7146
|
+
return NETWORK_COMMAND_RE.test(cmd);
|
|
7062
7147
|
}
|
|
7063
7148
|
return false;
|
|
7064
7149
|
}
|