@node9/proxy 2.13.1 → 2.14.0
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/dist/cli.js +262 -26
- package/dist/cli.mjs +262 -26
- package/dist/dashboard.mjs +246 -24
- package/dist/index.js +219 -21
- package/dist/index.mjs +219 -21
- package/dist/scan-ink.mjs +44 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1302,20 +1302,32 @@ function isProtectedHomePath(rawPath) {
|
|
|
1302
1302
|
}
|
|
1303
1303
|
return true;
|
|
1304
1304
|
}
|
|
1305
|
-
function
|
|
1306
|
-
const
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
const name = (words[0] ?? "").toLowerCase();
|
|
1310
|
-
const flags = [];
|
|
1311
|
-
const paths = [];
|
|
1312
|
-
for (let i = 1; i < words.length; i++) {
|
|
1305
|
+
function positionedArgs(words, from = 1, to = words.length) {
|
|
1306
|
+
const out = [];
|
|
1307
|
+
let afterFlag = null;
|
|
1308
|
+
for (let i = from; i < to; i++) {
|
|
1313
1309
|
const v = words[i];
|
|
1314
|
-
if (v === null)
|
|
1315
|
-
|
|
1316
|
-
|
|
1310
|
+
if (v === null) {
|
|
1311
|
+
afterFlag = null;
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (v.startsWith("-")) {
|
|
1315
|
+
afterFlag = v;
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
out.push({ value: v, index: out.length, argv: i, afterFlag });
|
|
1319
|
+
afterFlag = null;
|
|
1317
1320
|
}
|
|
1318
|
-
return
|
|
1321
|
+
return out;
|
|
1322
|
+
}
|
|
1323
|
+
function extractLiteralArgs(callExpr) {
|
|
1324
|
+
const rawArgs = callExpr.Args || [];
|
|
1325
|
+
if (rawArgs.length === 0) return { name: "", flags: [], paths: [], words: [], args: [] };
|
|
1326
|
+
const words = rawArgs.map((a) => resolveWordLiteral(a));
|
|
1327
|
+
const name = baseWord(words[0]);
|
|
1328
|
+
const flags = words.slice(1).filter((w) => w !== null && w.startsWith("-"));
|
|
1329
|
+
const args = positionedArgs(words);
|
|
1330
|
+
return { name, flags, paths: args.map((a) => a.value), words, args };
|
|
1319
1331
|
}
|
|
1320
1332
|
function resolveWordLiteral(w) {
|
|
1321
1333
|
const parts = w?.Parts || [];
|
|
@@ -1634,6 +1646,9 @@ function analyzeFsOperationImpl(command, depth = 0) {
|
|
|
1634
1646
|
if (result?.verdict === "block") return false;
|
|
1635
1647
|
}
|
|
1636
1648
|
}
|
|
1649
|
+
for (const p of copySourcePaths(words)) {
|
|
1650
|
+
result = stricter(result, copyVerdictOf(matchSensitivePath2(p)));
|
|
1651
|
+
}
|
|
1637
1652
|
return true;
|
|
1638
1653
|
});
|
|
1639
1654
|
return result;
|
|
@@ -1646,6 +1661,133 @@ function stricter(a, b) {
|
|
|
1646
1661
|
if (!b) return a;
|
|
1647
1662
|
return b.verdict === "block" && a.verdict !== "block" ? b : a;
|
|
1648
1663
|
}
|
|
1664
|
+
function flagInfo(w) {
|
|
1665
|
+
if (w.startsWith("--")) {
|
|
1666
|
+
const eq = w.indexOf("=");
|
|
1667
|
+
return eq < 0 ? { letter: null, long: w, attached: null } : { letter: null, long: w.slice(0, eq), attached: w.slice(eq + 1) };
|
|
1668
|
+
}
|
|
1669
|
+
const m = /^-([a-zA-Z]+)(.*)$/.exec(w);
|
|
1670
|
+
if (!m) return { letter: null, long: null, attached: null };
|
|
1671
|
+
return { letter: m[1][m[1].length - 1], long: null, attached: m[2] === "" ? null : m[2] };
|
|
1672
|
+
}
|
|
1673
|
+
function flagIs(w, names) {
|
|
1674
|
+
if (w === null) return false;
|
|
1675
|
+
const f = flagInfo(w);
|
|
1676
|
+
return names.some((n) => n.startsWith("--") ? f.long === n : f.letter === n);
|
|
1677
|
+
}
|
|
1678
|
+
function operandOf(a, names) {
|
|
1679
|
+
if (!names || a.afterFlag === null) return false;
|
|
1680
|
+
return flagIs(a.afterFlag, names) && flagInfo(a.afterFlag).attached === null;
|
|
1681
|
+
}
|
|
1682
|
+
function resolveCopyShape(words, h) {
|
|
1683
|
+
const verb = baseWord(words[h]);
|
|
1684
|
+
if (!verb) return null;
|
|
1685
|
+
const direct = COPY_VERBS[verb];
|
|
1686
|
+
if (direct) return { shape: direct, last: h };
|
|
1687
|
+
const slots = positionedArgs(words, h + 1);
|
|
1688
|
+
for (let i = 0; i < slots.length; i++) {
|
|
1689
|
+
for (let n = 3; n >= 1; n--) {
|
|
1690
|
+
const part = slots.slice(i, i + n);
|
|
1691
|
+
if (part.length < n) continue;
|
|
1692
|
+
const key = [verb, ...part.map((a) => a.value.toLowerCase())].join(" ");
|
|
1693
|
+
const shape = COPY_VERBS[key];
|
|
1694
|
+
if (shape) return { shape, last: part[n - 1].argv };
|
|
1695
|
+
}
|
|
1696
|
+
if (slots[i].afterFlag === null) return null;
|
|
1697
|
+
}
|
|
1698
|
+
return null;
|
|
1699
|
+
}
|
|
1700
|
+
function findStartPoints(words, h) {
|
|
1701
|
+
const k = words.findIndex((w, i) => i > h && w !== null && FIND_EXEC_FLAGS.has(w));
|
|
1702
|
+
if (k < 0) return { k, starts: [] };
|
|
1703
|
+
const firstPredicate = words.findIndex(
|
|
1704
|
+
(w, i) => i > h && w !== null && w.startsWith("-") && !FIND_OPTIONS.has(w)
|
|
1705
|
+
);
|
|
1706
|
+
const end = firstPredicate > h ? firstPredicate : k;
|
|
1707
|
+
return { k, starts: positionalAfter(words, h + 1, end) };
|
|
1708
|
+
}
|
|
1709
|
+
function copySourcePaths(words) {
|
|
1710
|
+
const h = unwrapCommandHead(words);
|
|
1711
|
+
const fi = words.findIndex((w, i) => i <= h && baseWord(w) === "find");
|
|
1712
|
+
if (fi >= 0) {
|
|
1713
|
+
const { k, starts } = findStartPoints(words, fi);
|
|
1714
|
+
if (k < 0) return [];
|
|
1715
|
+
const action = unwrapCommandHead(words.slice(k + 1));
|
|
1716
|
+
return resolveCopyShape(words.slice(k + 1), action) ? starts : [];
|
|
1717
|
+
}
|
|
1718
|
+
if (!COPY_VERB_HEADS.has(baseWord(words[h]))) return [];
|
|
1719
|
+
const r = resolveCopyShape(words, h);
|
|
1720
|
+
if (!r) return [];
|
|
1721
|
+
const { shape, last } = r;
|
|
1722
|
+
const args = positionedArgs(words, last + 1);
|
|
1723
|
+
const tail = words.slice(last + 1);
|
|
1724
|
+
const skipped = (a) => operandOf(a, shape.skipFlags);
|
|
1725
|
+
const targetDir = shape.targetDirFlag === true && tail.some((w) => w !== null && w.startsWith("-") && flagIs(w, ["t", "--target-directory"]));
|
|
1726
|
+
const targetOperand = (a) => targetDir && operandOf(a, ["t", "--target-directory"]);
|
|
1727
|
+
const lastOperand = [...tail].reverse().find((w) => w === null || !w.startsWith("-"));
|
|
1728
|
+
const dynamicDest = lastOperand === null;
|
|
1729
|
+
const destIsLastOperand = (shape.source === "allButLast" || shape.source === "first") && !targetDir && !dynamicDest;
|
|
1730
|
+
if (destIsLastOperand && typeof lastOperand === "string" && matchSensitivePath2(lastOperand))
|
|
1731
|
+
return [];
|
|
1732
|
+
let src;
|
|
1733
|
+
switch (shape.source) {
|
|
1734
|
+
case "all":
|
|
1735
|
+
src = args;
|
|
1736
|
+
break;
|
|
1737
|
+
case "first":
|
|
1738
|
+
src = targetDir ? args : args.slice(0, 1);
|
|
1739
|
+
break;
|
|
1740
|
+
case "flagOperand": {
|
|
1741
|
+
const inline = tail.filter((w) => w !== null && w.startsWith("--")).map((w) => flagInfo(w)).filter((f) => f.attached !== null && (shape.sourceFlags ?? []).includes(f.long ?? "")).map((f) => f.attached);
|
|
1742
|
+
return [
|
|
1743
|
+
...args.filter(
|
|
1744
|
+
(a) => flagIs(a.afterFlag, shape.sourceFlags ?? []) && flagInfo(a.afterFlag).attached === null
|
|
1745
|
+
).map((a) => a.value),
|
|
1746
|
+
...inline
|
|
1747
|
+
];
|
|
1748
|
+
}
|
|
1749
|
+
case "archive":
|
|
1750
|
+
src = archiveInputs(shape.archive, args, tail);
|
|
1751
|
+
break;
|
|
1752
|
+
case "allButLast":
|
|
1753
|
+
src = targetDir || dynamicDest ? args : args.slice(0, -1);
|
|
1754
|
+
break;
|
|
1755
|
+
}
|
|
1756
|
+
return src.filter((a) => !skipped(a) && !targetOperand(a)).map((a) => a.value);
|
|
1757
|
+
}
|
|
1758
|
+
function archiveInputs(kind, args, tail) {
|
|
1759
|
+
const first = args[0];
|
|
1760
|
+
const bareKey = first && first.afterFlag === null && TAR_MODE_WORD.test(first.value);
|
|
1761
|
+
if (kind === "tar") {
|
|
1762
|
+
const flagsText = tail.filter((w) => w !== null && w.startsWith("-")).join(" ");
|
|
1763
|
+
const mode = (bareKey ? first.value : "") + flagsText;
|
|
1764
|
+
const extracting = /x|t/.test(bareKey ? first.value.replace(/f/g, "") : "") || /(^|\s)-[a-zA-Z]*[xt]|--extract|--list|--get/.test(flagsText);
|
|
1765
|
+
const writing = /[cruA]/.test(bareKey ? first.value : "") || /(^|\s)-[a-zA-Z]*[cruA]|--create|--append|--update|--concatenate/.test(flagsText);
|
|
1766
|
+
if (extracting && !writing) return [];
|
|
1767
|
+
void mode;
|
|
1768
|
+
let i = 0;
|
|
1769
|
+
if (bareKey) {
|
|
1770
|
+
i = 1;
|
|
1771
|
+
const next = args[1];
|
|
1772
|
+
if (first.value.includes("f") && next && next.afterFlag === null) i = 2;
|
|
1773
|
+
}
|
|
1774
|
+
return args.slice(i);
|
|
1775
|
+
}
|
|
1776
|
+
if (kind === "zip") return first && first.afterFlag === "-" ? args : args.slice(1);
|
|
1777
|
+
if (kind === "ar") return bareKey ? args.slice(2) : args.slice(1);
|
|
1778
|
+
return args.slice(2);
|
|
1779
|
+
}
|
|
1780
|
+
function copyVerdictOf(hit) {
|
|
1781
|
+
if (!hit) return null;
|
|
1782
|
+
const ruleName = COPY_RULE_OF[hit.ruleName];
|
|
1783
|
+
if (!ruleName) return null;
|
|
1784
|
+
return {
|
|
1785
|
+
ruleName,
|
|
1786
|
+
verdict: "review",
|
|
1787
|
+
reason: `Copying ${hit.path} moves a credential out of its jail (project-jail shield)`,
|
|
1788
|
+
path: hit.path
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1649
1791
|
function matchSensitivePath2(p) {
|
|
1650
1792
|
for (const sp of SENSITIVE_PATH_RULES) {
|
|
1651
1793
|
if (sp.match(p))
|
|
@@ -1653,12 +1795,13 @@ function matchSensitivePath2(p) {
|
|
|
1653
1795
|
}
|
|
1654
1796
|
return null;
|
|
1655
1797
|
}
|
|
1798
|
+
function baseWord(w) {
|
|
1799
|
+
return (w ?? "").split("/").pop()?.toLowerCase() ?? "";
|
|
1800
|
+
}
|
|
1656
1801
|
function wrappedReadPaths(words, name) {
|
|
1657
1802
|
if (name === "find") {
|
|
1658
|
-
const k = words
|
|
1659
|
-
|
|
1660
|
-
const firstPredicate = words.findIndex((w, i) => i > 0 && w !== null && w.startsWith("-"));
|
|
1661
|
-
return positionalAfter(words, 1, firstPredicate > 0 ? firstPredicate : k);
|
|
1803
|
+
const { k, starts } = findStartPoints(words, 0);
|
|
1804
|
+
return k > 0 && isReaderWord(words[k + 1] ?? null) ? starts : null;
|
|
1662
1805
|
}
|
|
1663
1806
|
if (!COMMAND_WRAPPERS.has(name) && !RUNNER_WRAPPERS.has(name)) return null;
|
|
1664
1807
|
const h = unwrapCommandHead(words);
|
|
@@ -2330,6 +2473,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2330
2473
|
const shellShaped = isBashTool(toolName) || inspectsShellCommand(toolName, config.policy.toolInspection);
|
|
2331
2474
|
const shellShapedCommand = shellShaped ? isBashTool(toolName) && args && typeof args === "object" ? typeof args.command === "string" ? args.command : null : extractShellCommand(toolName, args, config.policy.toolInspection) : null;
|
|
2332
2475
|
const bashCommand = agent !== "Terminal" ? shellShapedCommand : null;
|
|
2476
|
+
let pendingAstReview;
|
|
2333
2477
|
if (bashCommand !== null) {
|
|
2334
2478
|
const pipeVerdict = pipeChainVerdict(
|
|
2335
2479
|
bashCommand,
|
|
@@ -2341,7 +2485,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2341
2485
|
if (fsVerdict) {
|
|
2342
2486
|
const isShieldRule = fsVerdict.ruleName.startsWith("shield:");
|
|
2343
2487
|
const labelPrefix = isShieldRule ? "project-jail (AST)" : "Node9 (AST)";
|
|
2344
|
-
|
|
2488
|
+
const astVerdict = {
|
|
2345
2489
|
decision: fsVerdict.verdict,
|
|
2346
2490
|
blockedByLabel: `${labelPrefix}: ${fsVerdict.ruleName}`,
|
|
2347
2491
|
reason: fsVerdict.reason,
|
|
@@ -2349,6 +2493,8 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2349
2493
|
ruleName: fsVerdict.ruleName,
|
|
2350
2494
|
ruleDescription: fsVerdict.reason
|
|
2351
2495
|
};
|
|
2496
|
+
if (fsVerdict.verdict === "block") return astVerdict;
|
|
2497
|
+
pendingAstReview = astVerdict;
|
|
2352
2498
|
}
|
|
2353
2499
|
const sqlAction = resolveCheck(config.policy.commandChecks?.sqlDdl);
|
|
2354
2500
|
const sqlVerdict = sqlAction === "off" ? null : analyzeSqlDestructive(bashCommand);
|
|
@@ -2391,7 +2537,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2391
2537
|
const matchedRule = resolvePinned(matches);
|
|
2392
2538
|
if (matchedRule) {
|
|
2393
2539
|
if (matchedRule.verdict === "allow")
|
|
2394
|
-
return { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2540
|
+
return pendingAstReview ?? { decision: "allow", ruleName: matchedRule.name ?? matchedRule.tool };
|
|
2395
2541
|
return {
|
|
2396
2542
|
decision: matchedRule.verdict,
|
|
2397
2543
|
blockedByLabel: `Smart Rule: ${matchedRule.name ?? matchedRule.tool}`,
|
|
@@ -2418,6 +2564,7 @@ async function evaluatePolicy(config, toolName, args, context = {}, hooks = {})
|
|
|
2418
2564
|
allTokens = analyzed.allTokens;
|
|
2419
2565
|
pathTokens = analyzed.paths;
|
|
2420
2566
|
const candidates2 = [];
|
|
2567
|
+
if (pendingAstReview) candidates2.push(pendingAstReview);
|
|
2421
2568
|
const evalVerdict = detectDangerousShellExec(shellCommand);
|
|
2422
2569
|
if (evalVerdict === "block") {
|
|
2423
2570
|
return {
|
|
@@ -2714,6 +2861,12 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2714
2861
|
"read-ssh",
|
|
2715
2862
|
"read-gcp",
|
|
2716
2863
|
"read-cred",
|
|
2864
|
+
// Stage 4 (2026-09-11): a copy of a credential file scores like a READ of it --
|
|
2865
|
+
// read-ssh/aws/cred are critical, so copy-ssh/aws/cred are; read-env is high
|
|
2866
|
+
// (below), so copy-env joins the high list, not this one (/code-review).
|
|
2867
|
+
"copy-ssh",
|
|
2868
|
+
"copy-aws",
|
|
2869
|
+
"copy-cred",
|
|
2717
2870
|
"delete-repo",
|
|
2718
2871
|
"helm-uninstall",
|
|
2719
2872
|
"drop-table",
|
|
@@ -2726,6 +2879,7 @@ function classifyRuleSeverity(name, verdict) {
|
|
|
2726
2879
|
];
|
|
2727
2880
|
if (criticalPatterns.some((p) => n.includes(p))) return "critical";
|
|
2728
2881
|
const highPatterns = [
|
|
2882
|
+
"copy-env",
|
|
2729
2883
|
"force-push",
|
|
2730
2884
|
"force_push",
|
|
2731
2885
|
"git-destructive",
|
|
@@ -2743,6 +2897,11 @@ function narrativeRuleLabel(name) {
|
|
|
2743
2897
|
const map = {
|
|
2744
2898
|
"read-aws": "AWS credentials read",
|
|
2745
2899
|
"read-ssh": "SSH private key read",
|
|
2900
|
+
// Stage 4 copy twins, so `scan --narrative` prints a label, not a raw slug.
|
|
2901
|
+
"copy-ssh": "SSH private key copied out",
|
|
2902
|
+
"copy-aws": "AWS credentials copied out",
|
|
2903
|
+
"copy-env": ".env file copied out",
|
|
2904
|
+
"copy-cred": "credential file copied out",
|
|
2746
2905
|
"read-gcp": "GCP credentials read",
|
|
2747
2906
|
"read-cred": "credential file read",
|
|
2748
2907
|
"delete-repo": "GitHub repository deletion",
|
|
@@ -3332,7 +3491,7 @@ function* stringValues(obj, depth = 0) {
|
|
|
3332
3491
|
}
|
|
3333
3492
|
for (const v of Object.values(obj)) yield* stringValues(v, depth + 1);
|
|
3334
3493
|
}
|
|
3335
|
-
var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, isReaderWord, positionalAfter, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
|
|
3494
|
+
var IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, MAX, UNTRUSTED_TOOLS, SIGNALS, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, MAX_DEPTH, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, DLP_SCAN_LIMITS, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, FS_READ_TOOLS, SCP_VALUE_FLAGS, RSYNC_SKIP, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, BASH_TOOL_NAMES, AST_FS_REGEX_RULES, SQL_DB_CLIS, SQL_DDL_RE, CHMOD_OPEN_PERM_TOKENS, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _redirStdinOps, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, INTERP_LEADING_TARGET, NET_BINARIES, VALUE_FLAGS, FS_OP_CACHE_MAX, fsOpCache, stripDotSlash, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, DEFAULT_EGRESS_ALLOWLIST, SOURCE_COMMANDS, SINK_COMMANDS, OBFUSCATORS, SENSITIVE_PATTERNS, FLAGS_WITH_VALUES, SSRF_MAX_HOST, METADATA_ADDRESSES, STRICT_TIERS, METADATA_HOSTNAMES, v4Octets, TIER_REASON, DESTINATION_ARGS, VERDICT_RANK, SQL_DML_KEYWORDS, aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, LOOP_MAX_RECORDS, FINDING_TO_SIGNAL, SCAN_SIGNAL_WEIGHTS, LOOP_THRESHOLD_FOR_WASTE, DESTRUCTIVE_OP_RE, SENSITIVE_PATH_RE, FILE_TOOLS, PII_EMAIL_RE, PII_SSN_RE, PII_PHONE_RE, PII_CC16_RE, PII_CC15_RE, PII_IBAN_RE, REALTIME_PII_PATTERNS, MAX_PII_SCAN_BYTES, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH2, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES, CANONICAL_EXTRACTOR_VERSION, DEDUPE_PREVIEW_LEN, TERMINAL_ESCAPE_RE, ENGINE_VERSION;
|
|
3336
3495
|
var init_dist = __esm({
|
|
3337
3496
|
"packages/policy-engine/dist/index.mjs"() {
|
|
3338
3497
|
"use strict";
|
|
@@ -4068,12 +4227,56 @@ var init_dist = __esm({
|
|
|
4068
4227
|
"nl",
|
|
4069
4228
|
"dd"
|
|
4070
4229
|
]);
|
|
4230
|
+
SCP_VALUE_FLAGS = ["i", "F", "o", "c", "S", "P", "J", "D", "W", "l"];
|
|
4231
|
+
RSYNC_SKIP = [
|
|
4232
|
+
"e",
|
|
4233
|
+
"--rsh",
|
|
4234
|
+
"--exclude",
|
|
4235
|
+
"--exclude-from",
|
|
4236
|
+
"--include",
|
|
4237
|
+
"--include-from",
|
|
4238
|
+
"--files-from",
|
|
4239
|
+
"f",
|
|
4240
|
+
"--filter"
|
|
4241
|
+
];
|
|
4242
|
+
COPY_VERBS = {
|
|
4243
|
+
cp: { source: "allButLast", targetDirFlag: true },
|
|
4244
|
+
mv: { source: "allButLast", targetDirFlag: true },
|
|
4245
|
+
install: { source: "allButLast", targetDirFlag: true },
|
|
4246
|
+
ln: { source: "first", targetDirFlag: true },
|
|
4247
|
+
scp: { source: "allButLast", skipFlags: SCP_VALUE_FLAGS },
|
|
4248
|
+
rsync: { source: "allButLast", skipFlags: RSYNC_SKIP },
|
|
4249
|
+
tar: {
|
|
4250
|
+
source: "archive",
|
|
4251
|
+
archive: "tar",
|
|
4252
|
+
skipFlags: ["f", "X", "T", "--file", "--exclude", "--exclude-from", "--files-from"]
|
|
4253
|
+
},
|
|
4254
|
+
zip: { source: "archive", archive: "zip", skipFlags: ["x", "i", "--exclude", "--include"] },
|
|
4255
|
+
ar: { source: "archive", archive: "ar" },
|
|
4256
|
+
"7z": { source: "archive", archive: "7z", skipFlags: ["x", "--exclude"] },
|
|
4257
|
+
gzip: { source: "all" },
|
|
4258
|
+
bzip2: { source: "all" },
|
|
4259
|
+
xz: { source: "all" },
|
|
4260
|
+
"docker cp": { source: "allButLast" },
|
|
4261
|
+
"kubectl cp": { source: "allButLast" },
|
|
4262
|
+
"gsutil cp": { source: "allButLast" },
|
|
4263
|
+
"gsutil rsync": { source: "allButLast" },
|
|
4264
|
+
"rclone copy": { source: "allButLast" },
|
|
4265
|
+
"rclone sync": { source: "allButLast" },
|
|
4266
|
+
"aws s3 cp": { source: "allButLast" },
|
|
4267
|
+
"aws s3 mv": { source: "allButLast" },
|
|
4268
|
+
"aws s3 sync": { source: "allButLast" },
|
|
4269
|
+
"gcloud storage cp": { source: "allButLast" },
|
|
4270
|
+
"az storage blob upload": { source: "flagOperand", sourceFlags: ["f", "--file"] }
|
|
4271
|
+
};
|
|
4272
|
+
TAR_MODE_WORD = /^[a-zA-Z]+$/;
|
|
4273
|
+
COPY_VERB_HEADS = new Set(Object.keys(COPY_VERBS).map((k) => k.split(" ")[0]));
|
|
4071
4274
|
FS_OP_PRESCREEN_RE = new RegExp(
|
|
4072
4275
|
// A quote is a separator too: `eval "cat X"` and `sh -c 'cat X'` put the
|
|
4073
4276
|
// reader right after `"` / `'`, and without these two characters the
|
|
4074
4277
|
// prescreen rejected every string-wrapped read before the parser ran.
|
|
4075
4278
|
// Found 2026-09-11 by instrumenting the walk -- no CallExpr was ever visited.
|
|
4076
|
-
`(?:^|[\\s|;&("'\`\\n])(?:rm|${[...FS_READ_TOOLS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4279
|
+
`(?:^|[\\s|;&("'\`\\n/])(?:rm|${[...FS_READ_TOOLS, ...COPY_VERB_HEADS].map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})\\b|(?<!<)<(?!<)`
|
|
4077
4280
|
);
|
|
4078
4281
|
HOME_CACHE_ALLOWLIST = [
|
|
4079
4282
|
".cache",
|
|
@@ -4363,8 +4566,15 @@ var init_dist = __esm({
|
|
|
4363
4566
|
deriveRedirOp("cat <<X\nX"),
|
|
4364
4567
|
deriveRedirOp("cat <<-X\nX")
|
|
4365
4568
|
]);
|
|
4366
|
-
|
|
4367
|
-
|
|
4569
|
+
FIND_OPTIONS = /* @__PURE__ */ new Set(["-H", "-L", "-P"]);
|
|
4570
|
+
COPY_RULE_OF = {
|
|
4571
|
+
"shield:project-jail:block-read-ssh": "shield:project-jail:review-copy-ssh",
|
|
4572
|
+
"shield:project-jail:block-read-aws": "shield:project-jail:review-copy-aws",
|
|
4573
|
+
"shield:project-jail:block-read-env": "shield:project-jail:review-copy-env",
|
|
4574
|
+
"shield:project-jail:review-read-credentials": "shield:project-jail:review-copy-credentials"
|
|
4575
|
+
};
|
|
4576
|
+
isReaderWord = (w) => w !== null && FS_READ_TOOLS.has(baseWord(w));
|
|
4577
|
+
positionalAfter = (words, from, to = words.length) => positionedArgs(words, from, to).map((a) => a.value);
|
|
4368
4578
|
DEFAULT_EGRESS_ALLOWLIST = [
|
|
4369
4579
|
// node9's own control plane (api, app, dev-api, staging and the apex).
|
|
4370
4580
|
// Without it, turning egress on asks the user to approve node9 itself.
|
|
@@ -5409,7 +5619,7 @@ var init_dist = __esm({
|
|
|
5409
5619
|
{ view: "separators-stripped", decoder: "separators", stripped: true }
|
|
5410
5620
|
];
|
|
5411
5621
|
LONG_OUTPUT_THRESHOLD_BYTES = 100 * 1024;
|
|
5412
|
-
CANONICAL_EXTRACTOR_VERSION = "canonical-
|
|
5622
|
+
CANONICAL_EXTRACTOR_VERSION = "canonical-v14";
|
|
5413
5623
|
DEDUPE_PREVIEW_LEN = 120;
|
|
5414
5624
|
TERMINAL_ESCAPE_RE = // eslint-disable-next-line no-control-regex
|
|
5415
5625
|
/\x1b\[[0-9;?]*[A-Za-z]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_]|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
|
|
@@ -12979,6 +13189,28 @@ function codexSessionCost(model, tokens, request2) {
|
|
|
12979
13189
|
const tierMultiplier = request2?.serviceTier === "flex" ? 0.5 : codexModel(model) === "gpt-6-astra" && (request2?.serviceTier === "fast" || request2?.serviceTier === "priority") ? 2 : 1;
|
|
12980
13190
|
return (((input - cached - written) * pin + cached * pcr + written * (pcw || pin)) * inputMultiplier + tokenNumber(tokens.output) * pout * outputMultiplier) * tierMultiplier;
|
|
12981
13191
|
}
|
|
13192
|
+
function statAndFirstLine(file) {
|
|
13193
|
+
const CAP = 4 * 1024 * 1024;
|
|
13194
|
+
const CHUNK = 64 * 1024;
|
|
13195
|
+
const fd = fs21.openSync(file, "r");
|
|
13196
|
+
try {
|
|
13197
|
+
const stat = fs21.fstatSync(fd);
|
|
13198
|
+
const limit = Math.min(stat.size, CAP);
|
|
13199
|
+
const parts = [];
|
|
13200
|
+
for (let pos = 0; pos < limit; pos += CHUNK) {
|
|
13201
|
+
const buf = Buffer.alloc(Math.min(CHUNK, limit - pos));
|
|
13202
|
+
const read2 = fs21.readSync(fd, buf, 0, buf.length, pos);
|
|
13203
|
+
if (read2 <= 0) break;
|
|
13204
|
+
const slice = buf.subarray(0, read2);
|
|
13205
|
+
const nl = slice.indexOf(10);
|
|
13206
|
+
parts.push(nl >= 0 ? slice.subarray(0, nl) : slice);
|
|
13207
|
+
if (nl >= 0) break;
|
|
13208
|
+
}
|
|
13209
|
+
return { stat, first: Buffer.concat(parts).toString("utf8") };
|
|
13210
|
+
} finally {
|
|
13211
|
+
fs21.closeSync(fd);
|
|
13212
|
+
}
|
|
13213
|
+
}
|
|
12982
13214
|
function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
12983
13215
|
const files = [];
|
|
12984
13216
|
const walk = (dir) => {
|
|
@@ -12996,10 +13228,10 @@ function listCodexSessionFiles(base = codexSessionsDir()) {
|
|
|
12996
13228
|
const sessions = /* @__PURE__ */ new Map();
|
|
12997
13229
|
for (const file of files.sort()) {
|
|
12998
13230
|
try {
|
|
12999
|
-
const stat =
|
|
13231
|
+
const { stat, first: head } = statAndFirstLine(file);
|
|
13000
13232
|
let id = "";
|
|
13001
13233
|
try {
|
|
13002
|
-
const first = JSON.parse(
|
|
13234
|
+
const first = JSON.parse(head);
|
|
13003
13235
|
if (first?.type === "session_meta" && typeof first.payload?.id === "string")
|
|
13004
13236
|
id = first.payload.id;
|
|
13005
13237
|
} catch {
|
|
@@ -59452,7 +59684,11 @@ var BUILTIN_JAIL = [
|
|
|
59452
59684
|
"~/.ssh \u2014 SSH private keys",
|
|
59453
59685
|
"~/.aws \u2014 AWS credentials",
|
|
59454
59686
|
".env files",
|
|
59455
|
-
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud"
|
|
59687
|
+
"credential files \u2014 credentials.json, .netrc, .npmrc, .docker, .kube, gcloud",
|
|
59688
|
+
// Stage 4 (2026-09-11): reads are blocked; a COPY out of the jail (cp, tar,
|
|
59689
|
+
// scp, rsync, aws s3 cp, ...) is reviewed, because a backup and a theft are
|
|
59690
|
+
// the same command shape.
|
|
59691
|
+
"copies out of the jail (cp, tar, scp, rsync, cloud upload) \u2014 review"
|
|
59456
59692
|
];
|
|
59457
59693
|
function registerJailCommand(program2) {
|
|
59458
59694
|
const jail = program2.command("jail").description("Grow the credential jail \u2014 block/review reads of your sensitive paths");
|