@node9/proxy 2.33.1 → 2.34.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.
@@ -968,6 +968,95 @@ function expandPlainParam(p) {
968
968
  if (recorded !== void 0) return recorded;
969
969
  return HOME_VARIABLES.has(p.Param?.Value) ? "~" : void 0;
970
970
  }
971
+ function functionBodies(f) {
972
+ const out = /* @__PURE__ */ new Map();
973
+ syntax.Walk(f, (node) => {
974
+ const n = node;
975
+ if (n && syntax.NodeType(n) === "FuncDecl" && n.Name?.Value && n.Body) {
976
+ const list = out.get(n.Name.Value) ?? [];
977
+ list.push(n.Body);
978
+ out.set(n.Name.Value, list);
979
+ }
980
+ return true;
981
+ });
982
+ return out;
983
+ }
984
+ function rebindsPositionals(body) {
985
+ let found = false;
986
+ syntax.Walk(body, (node) => {
987
+ const n = node;
988
+ if (found || !n) return false;
989
+ if (syntax.NodeType(n) === "CallExpr") {
990
+ const words = (n.Args || []).map((a) => resolveWordLiteral(a));
991
+ if (words[0] === "shift") found = true;
992
+ else if (words[0] === "set" && words.slice(1).some((w) => w === null || w === "--" || !/^[-+]/.test(w)))
993
+ found = true;
994
+ }
995
+ return !found;
996
+ });
997
+ return found;
998
+ }
999
+ function tableBindings() {
1000
+ if (!assignmentTable) return "";
1001
+ const out = [];
1002
+ for (const [name, rec] of assignmentTable)
1003
+ if (rec.at < currentStmtOffset) out.push(`${name}=${rec.value ?? "\0"}`);
1004
+ return out.sort().join("");
1005
+ }
1006
+ function walkWithCalls(f, visit) {
1007
+ const bodies = functionBodies(f);
1008
+ if (bodies.size === 0) {
1009
+ syntax.Walk(f, visit);
1010
+ return;
1011
+ }
1012
+ const seenCalls = /* @__PURE__ */ new Set();
1013
+ const inline = (call) => {
1014
+ const words = (call.Args || []).map((a) => resolveWordLiteral(a));
1015
+ const list = words[0] ? bodies.get(words[0]) : void 0;
1016
+ if (!list || callDepth >= MAX_CALL_DEPTH) return;
1017
+ const args = words.slice(1);
1018
+ const key = `${words[0]}\0${JSON.stringify(args)}\0${tableBindings()}`;
1019
+ if (seenCalls.has(key)) return;
1020
+ seenCalls.add(key);
1021
+ const outerTable = assignmentTable;
1022
+ const outerOffset = currentStmtOffset;
1023
+ for (const body of list) {
1024
+ if (callWalks >= MAX_CALL_WALKS) return;
1025
+ callWalks++;
1026
+ const table = /* @__PURE__ */ new Map();
1027
+ for (const [name, rec] of outerTable ?? [])
1028
+ if (rec.at < outerOffset) table.set(name, { value: rec.value, at: -1 });
1029
+ for (const name of ["1", "2", "3", "4", "5", "6", "7", "8", "9", "@", "*", "#"])
1030
+ table.delete(name);
1031
+ if (!rebindsPositionals(body)) {
1032
+ args.slice(0, 9).forEach((value, i) => table.set(String(i + 1), { value, at: -1 }));
1033
+ if (args.length === 1) {
1034
+ table.set("@", { value: args[0], at: -1 });
1035
+ table.set("*", { value: args[0], at: -1 });
1036
+ }
1037
+ table.set("#", { value: String(args.length), at: -1 });
1038
+ }
1039
+ assignmentTable = table;
1040
+ currentStmtOffset = Number.MAX_SAFE_INTEGER;
1041
+ callDepth++;
1042
+ try {
1043
+ if (body.Cmd && syntax.NodeType(body.Cmd) === "Block") recordTopLevelAssignments(body.Cmd);
1044
+ syntax.Walk(body, step);
1045
+ } finally {
1046
+ callDepth--;
1047
+ assignmentTable = outerTable;
1048
+ currentStmtOffset = outerOffset;
1049
+ }
1050
+ }
1051
+ };
1052
+ const step = (node) => {
1053
+ if (!node) return false;
1054
+ const go = visit(node);
1055
+ if (go !== false && syntax.NodeType(node) === "CallExpr") inline(node);
1056
+ return go;
1057
+ };
1058
+ syntax.Walk(f, step);
1059
+ }
971
1060
  function resolveWordLiteral(w) {
972
1061
  const parts = w?.Parts || [];
973
1062
  let s = "";
@@ -1042,6 +1131,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1042
1131
  if (depth === 0) {
1043
1132
  seenPayloads = /* @__PURE__ */ new Set();
1044
1133
  payloadBudget = PAYLOAD_BUDGET;
1134
+ if (callDepth === 0) callWalks = 0;
1045
1135
  }
1046
1136
  assignmentTable = new Map(
1047
1137
  [...outerTable ?? []].map(([k, r]) => [k, { value: r.value, at: -1 }])
@@ -1049,7 +1139,7 @@ function analyzeFsOperationImpl(command, depth = 0) {
1049
1139
  currentStmtOffset = Number.MAX_SAFE_INTEGER;
1050
1140
  try {
1051
1141
  recordTopLevelAssignments(f);
1052
- syntax.Walk(f, (node) => {
1142
+ walkWithCalls(f, (node) => {
1053
1143
  if (!node || result?.verdict === "block") return false;
1054
1144
  const n = node;
1055
1145
  const nodeType = syntax.NodeType(n);
@@ -1924,16 +2014,21 @@ function looksGenerated(value, minLength, minEntropy, lettersOnlyAllowed = false
1924
2014
  if (EMAIL_VALUE_RE.test(value)) return false;
1925
2015
  if (looksLikeJoinedWords(value) || hasSequentialRun(value)) return false;
1926
2016
  if (PATH_VALUE_RE.test(value) || DOTTED_REFERENCE_RE.test(value)) return false;
2017
+ if (CONCATENATION_RE.test(value)) return false;
1927
2018
  if (rejectedByStructuredValidator(value)) return false;
1928
2019
  return shannonEntropy(value) >= minEntropy;
1929
2020
  }
1930
2021
  function nameHoldsCredential(name) {
1931
2022
  return !FIXTURE_NAME_RE.test(name) && !DERIVATIVE_NAME_RE.test(name);
1932
2023
  }
2024
+ function isOtpDocSecret(value) {
2025
+ return OTP_DOC_SECRETS.has(value.replace(/=+$/, "").toUpperCase());
2026
+ }
1933
2027
  function validateQueryCredential(raw) {
1934
2028
  const value = capturedValue(raw, QUERY_CREDENTIAL_VALUE_RE);
1935
2029
  if (value === null) return false;
1936
- if (OTP_SECRET_RE.test(value)) return looksGenerated(value, 16, 3, true);
2030
+ if (OTP_SECRET_RE.test(value))
2031
+ return !isOtpDocSecret(value) && looksGenerated(value, 16, 3, true);
1937
2032
  if (!/[0-9]/.test(value) || !/[A-Za-z]/.test(value)) return false;
1938
2033
  return looksGenerated(value, 12, 3);
1939
2034
  }
@@ -1947,13 +2042,40 @@ function keywordEndsAPart(name) {
1947
2042
  }
1948
2043
  function validateAssignedSecret(raw, before = "") {
1949
2044
  const value = capturedValue(raw, ASSIGNED_SECRET_VALUE_RE);
1950
- const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE);
2045
+ const name = capturedValue(raw, ASSIGNED_SECRET_NAME_RE)?.replace(/^_+/, "") ?? null;
1951
2046
  if (value === null || name === null) return false;
1952
- if (!keywordEndsAPart(name) || ARN_CONTEXT_RE.test(before)) return false;
1953
- const prefix = before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "";
2047
+ if (!keywordEndsAPart(name)) return false;
2048
+ if (ARN_CONTEXT_RE.test(before)) return false;
2049
+ const prefix = (before.match(/[A-Za-z0-9_]*$/)?.[0] ?? "") + (raw.startsWith("_") ? "_" : "");
1954
2050
  if (!nameHoldsCredential(prefix + name)) return false;
1955
2051
  return looksGenerated(value, 12, 3.5);
1956
2052
  }
2053
+ function validateBasicCredential(raw) {
2054
+ const b64 = capturedValue(raw, BASIC_VALUE_RE);
2055
+ if (b64 === null) return false;
2056
+ const bare = b64.replace(/=+$/, "");
2057
+ if (bare.length % 4 === 1) return false;
2058
+ const bytes = Buffer.from(b64, "base64");
2059
+ if (bytes.toString("base64").replace(/=+$/, "") !== bare) return false;
2060
+ const text = bytes.toString("utf8");
2061
+ if (!Buffer.from(text, "utf8").equals(bytes)) return false;
2062
+ for (let i = 0; i < text.length; i++) {
2063
+ const c = text.charCodeAt(i);
2064
+ if (c < 32 || c === 127) return false;
2065
+ }
2066
+ const pair = BASIC_PAIR_RE.exec(text);
2067
+ if (!pair) return false;
2068
+ const password = pair[2];
2069
+ if (!/[0-9]/.test(password) || !/[A-Za-z]/.test(password)) return false;
2070
+ const lower2 = password.toLowerCase();
2071
+ if (DLP_STOPWORDS.some((sw) => lower2.includes(sw)) || hasSequentialRun(password)) return false;
2072
+ return shannonEntropy(password) >= 3;
2073
+ }
2074
+ function validateOtpSecret(raw) {
2075
+ const secret = capturedValue(raw, OTP_CAPTURE_RE);
2076
+ if (secret === null || isOtpDocSecret(secret)) return false;
2077
+ return looksGenerated(secret.replace(/=+$/, ""), 16, 3, true);
2078
+ }
1957
2079
  function assertBuiltinPatternsAreSafe() {
1958
2080
  for (const p of DLP_PATTERNS) {
1959
2081
  if (!safeRegex3(p.regex.source)) {
@@ -2429,7 +2551,7 @@ function matchCanaryArgs(args, values) {
2429
2551
  return null;
2430
2552
  }
2431
2553
  }
2432
- var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, LEGACY_SYSTEM_PATH_RE, BIP39_ENGLISH, indices, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax3, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, SOURCE_COMMANDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, MASKED_KINDS, ENTRY_RE, syntax4, PUBLISH, OWN_HEADS, TERMINAL_ESCAPE_RE, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
2554
+ var aws_default, bash_safe_default, docker_default, filesystem_default, github_default, k8s_default, mongodb_default, postgres_default, project_jail_default, redis_default, BUILTIN_SHIELDS, SSRF_MAX_HOST, METADATA_ADDRESSES, METADATA_HOSTNAMES, v4Octets, CHECK_TEXT, VERDICTS, CHECK_VERDICT_RANK, ALL, BUILTIN_DANGEROUS_WORDS, PRODUCT_CHECKS, SHIELD_RULE_NAME, SHIELD_RULES_FOLDED_INTO_PRODUCT, CHECKS, CHECK_BY_ID, LEGACY_TO_ID, LEGACY_READERS, syntax, sharedParser, MESSAGE_FLAGS, SHELL_INTERPRETERS, DOWNLOAD_CMDS, NORMALIZE_CACHE_MAX, normalizeCache, AST_CACHE_MAX, astCache, PARSE_FAIL, defaultExpansionOps, VARIABLE_READERS, FS_READ_TOOLS, GREP_SHAPE, PATTERN_VERBS, PATTERN_VERB_NAMES, GREP_FILE_OPERANDS, READER_VALUE_LETTERS, FILE_OPERAND_FLAGS, SCP_VALUE_FLAGS, TAR_VALUE_LETTERS, ZIP_VALUE_LETTERS, RSYNC_VALUE_LETTERS, RSYNC_SKIP, CP_VALUE_LETTERS, INSTALL_VALUE_LETTERS, COPY_VERBS, TAR_MODE_WORD, COPY_VERB_HEADS, FS_OP_PRESCREEN_RE, HOME_CACHE_ALLOWLIST, SENSITIVE_PATH_RULES, AST_FS_REGEX_RULES, COMMAND_WRAPPERS, INLINE_INTERPRETER, RUNNER_WRAPPERS, _listOps, WRAPPER_TAKES_TARGET, FIND_EXEC_FLAGS, FS_MUTATION_TOOLS, HOME_VARIABLES, assignmentTable, currentStmtOffset, PAYLOAD_BUDGET, payloadBudget, seenPayloads, UNANALYSABLE_NESTING, ASSIGNMENT_HEADS, AND_OP, OR_OP, AND_OR_OPS, MAX_CALL_DEPTH, MAX_CALL_WALKS, callDepth, callWalks, FS_OP_CACHE_MAX, fsOpCache, REDIR_TRUNCATE_OPS, REDIR_FILE_IN_OPS, REDIR_HEREDOC_OPS, FIND_OPTIONS, COPY_RULE_OF, isReaderWord, positionalAfter, NONE, UNKNOWN, MAX_REGEX_LENGTH, REGEX_CACHE_MAX, regexCache, FORBIDDEN_PATH_SEGMENTS, BASH_TOOL_NAMES, MCP_EXECUTORS, BUILTIN_FIELDS, ARGV_FIELDS, BARE_WORD, MAX_ARGV_CHARACTERS, syntax2, WRITES, READS, DUP_OUT, HERE_STRING, AND, OR, DEFAULT_OPS, SYSTEM_DIRS, UPLOAD_SYSTEM_DIRS, LEGACY_SYSTEM_PATH_RE, BIP39_ENGLISH, indices, IBAN_LENGTH, B58, B58_INDEX, XPRV_VERSIONS, B62, CRC32_TABLE, GITHUB_TOKEN_RE, B64URL, B64URL_INDEX, CASK_TWO_ZERO_SUFFIX, CASK_FOUR_ZERO_SUFFIX, INVISIBLE_RE, INVISIBLE_ONE, HOMOGLYPHS, HOMOGLYPH_RE, ASSIGNMENT_CONTEXT_RE, DLP_STOPWORDS, REFERENCE_VALUE_RE, IDENTIFIER_VALUE_RE, PATH_VALUE_RE, DOTTED_REFERENCE_RE, CONCATENATION_RE, EMAIL_VALUE_RE, FIXTURE_NAME_RE, DERIVATIVE_NAME_RE, QUERY_CREDENTIAL_VALUE_RE, OTP_SECRET_RE, OTP_DOC_SECRETS, ASSIGNED_SECRET_VALUE_RE, ASSIGNED_SECRET_NAME_RE, NAME_KEYWORD_RE, ARN_CONTEXT_RE, BASIC_VALUE_RE, BASIC_PAIR_RE, OTP_CAPTURE_RE, DLP_PATTERNS, DLP_PATTERNS_GLOBAL, SENSITIVE_PATH_PATTERNS, SENSITIVE_PATH_REGEXES, MAX_DEPTH2, MAX_STRING_BYTES, MAX_JSON_PARSE_BYTES, syntax3, EDITOR_HEADS, MANAGER_HEADS, TUNING_FIELDS, MAP_GOVERNED, KNOB_GOVERNED, CONFIGURABLE_CHECK_IDS, SOURCE_COMMANDS, MAX_DESTINATIONS, MAX_SCAN_CHARS, EMAIL_RE, URL_RE, IBAN_RE, RECIPIENT_KEYS, MASKED_KINDS, ENTRY_RE, syntax4, PUBLISH, OWN_HEADS, TERMINAL_ESCAPE_RE, CANARY_MIN_LENGTH, MAX_TEXT, MAX_DEPTH4, MAX_JSON_PARSE, URL_DEPTH, B64_DEPTH, MIN_SEGMENT, SEPARATORS, stripSeparators, looksText, CANARY_DECODERS, VIEWS, LONG_OUTPUT_THRESHOLD_BYTES;
2433
2555
  var init_dist = __esm({
2434
2556
  "packages/policy-engine/dist/index.mjs"() {
2435
2557
  "use strict";
@@ -3221,6 +3343,11 @@ var init_dist = __esm({
3221
3343
  example: "cat /etc/shadow",
3222
3344
  advice: "Keep Review, or choose Block when agents must never read system password hashes."
3223
3345
  },
3346
+ "files.system-dirs": {
3347
+ plain: "The agent writes, creates or deletes a file in a system directory, or sends a system file in a request. /usr/local and a container working directory under /usr/src are ordinary project space and are not included.",
3348
+ example: "wget -O /usr/bin/tool https://example.com/tool",
3349
+ advice: "Keep Review on a workstation. A container image build that installs into /usr can set it to Log."
3350
+ },
3224
3351
  // ── Commands ──────────────────────────────────────────────────────────────
3225
3352
  "commands.inline-exec": {
3226
3353
  plain: "The agent runs a small program written inside the command itself.",
@@ -3417,7 +3544,7 @@ var init_dist = __esm({
3417
3544
  advice: "Fixed at Block. A person accepts the change on the machine (node9 mcp pin update)."
3418
3545
  },
3419
3546
  "loading.malicious-package": {
3420
- plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago.",
3547
+ plain: "The agent installs a package that the public malicious-package database (OSV) lists, or one published minutes ago. Covers npm, PyPI, RubyGems, crates.io, Go, NuGet, Maven, Packagist and VS Code extensions; the publish-age and install-script signals are npm and PyPI only.",
3421
3548
  example: "npm install of a package flagged as malware",
3422
3549
  advice: "Keep Block for known-malicious packages; brand-new packages already stop for Review."
3423
3550
  }
@@ -3455,6 +3582,15 @@ var init_dist = __esm({
3455
3582
  defaultValue: "review",
3456
3583
  legacy: ["review-password-hash-read"]
3457
3584
  },
3585
+ {
3586
+ id: "files.system-dirs",
3587
+ group: "files",
3588
+ title: "System directories",
3589
+ catches: "Writes, new files and removals under /usr, /etc, /boot, /bin, /sbin, /lib and /var/log, and uploads of system files",
3590
+ values: ALL,
3591
+ defaultValue: "review",
3592
+ legacy: ["review-system-dir-mutation", "review-system-file-upload"]
3593
+ },
3458
3594
  // ── Commands ────────────────────────────────────────────────────────────
3459
3595
  {
3460
3596
  id: "commands.inline-exec",
@@ -4580,6 +4716,10 @@ var init_dist = __esm({
4580
4716
  AND_OP = probeBinOp("a && b");
4581
4717
  OR_OP = probeBinOp("a || b");
4582
4718
  AND_OR_OPS = new Set([AND_OP, OR_OP].filter((o) => o !== null));
4719
+ MAX_CALL_DEPTH = 4;
4720
+ MAX_CALL_WALKS = 256;
4721
+ callDepth = 0;
4722
+ callWalks = 0;
4583
4723
  FS_OP_CACHE_MAX = 5e3;
4584
4724
  fsOpCache = /* @__PURE__ */ new Map();
4585
4725
  REDIR_TRUNCATE_OPS = /* @__PURE__ */ new Set([deriveRedirOp(">_f")]);
@@ -4641,6 +4781,17 @@ var init_dist = __esm({
4641
4781
  (s) => parseShared(`echo ${s}`).Stmts[0].Cmd.Args[1].Parts[0].Exp.Op
4642
4782
  )
4643
4783
  );
4784
+ SYSTEM_DIRS = [
4785
+ "/usr",
4786
+ "/etc",
4787
+ "/boot",
4788
+ "/bin",
4789
+ "/sbin",
4790
+ "/lib",
4791
+ "/lib64",
4792
+ "/var/log"
4793
+ ];
4794
+ UPLOAD_SYSTEM_DIRS = [...SYSTEM_DIRS, "/proc", "/sys", "/root"];
4644
4795
  LEGACY_SYSTEM_PATH_RE = /^(?:[a-zA-Z]:)?\/etc\/(?:passwd|shadow|sudoers)$/;
4645
4796
  BIP39_ENGLISH = `abandon
4646
4797
  ability
@@ -6897,15 +7048,20 @@ zoo`.split("\n");
6897
7048
  IDENTIFIER_VALUE_RE = /^[A-Za-z_.-]+$/;
6898
7049
  PATH_VALUE_RE = /^(?:\/|\.{1,2}\/|~\/)/;
6899
7050
  DOTTED_REFERENCE_RE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/;
7051
+ CONCATENATION_RE = /^\+[A-Za-z_$][\w$]*\./;
6900
7052
  EMAIL_VALUE_RE = /^[^@\s]+@[^@\s]+\.[A-Za-z]{2,}$/;
6901
7053
  FIXTURE_NAME_RE = /(?:fake|dummy|example|sample|mock|stub|placeholder|test)/i;
6902
- DERIVATIVE_NAME_RE = /(?:hash|digest|id|type|name|url|uri|path|file|len|length|count|size|ttl|expires?|expiry|age|date|time|format|field|header|param)$/i;
7054
+ DERIVATIVE_NAME_RE = /(?:hash|digest|fingerprint|thumbprint|checksum|id|type|name|url|uri|path|file|len|length|count|size|ttl|expires?|expiry|age|date|time|format|field|header|param)$/i;
6903
7055
  QUERY_CREDENTIAL_VALUE_RE = /=([A-Za-z0-9%+/_.~-]{8,512}={0,2})$/;
6904
7056
  OTP_SECRET_RE = /^[A-Z2-7]{16,}=*$/i;
6905
- ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
7057
+ OTP_DOC_SECRETS = /* @__PURE__ */ new Set(["JBSWY3DPEHPK3PXP", "GEZDGNBVGY3TQOJQ"]);
7058
+ ASSIGNED_SECRET_VALUE_RE = /(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})$/;
6906
7059
  ASSIGNED_SECRET_NAME_RE = /^([A-Za-z0-9_]+)/;
6907
- NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)/i;
7060
+ NAME_KEYWORD_RE = /^(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|consumer[_-]?key|app[_-]?key|signing[_-]?key|encryption[_-]?key|master[_-]?key|jsapi[_-]?ticket|pass)/i;
6908
7061
  ARN_CONTEXT_RE = /\barn:[A-Za-z0-9-]*:[^\s"'`]*$/i;
7062
+ BASIC_VALUE_RE = /([A-Za-z0-9+/]{8,512}={0,2})$/;
7063
+ BASIC_PAIR_RE = /^([^:\s]{1,128}):(\S{8,256})$/;
7064
+ OTP_CAPTURE_RE = /["']([A-Z2-7]{16,128}=*)["']$/i;
6909
7065
  DLP_PATTERNS = [
6910
7066
  // ── AWS ───────────────────────────────────────────────────────────────────
6911
7067
  {
@@ -7243,6 +7399,22 @@ zoo`.split("\n");
7243
7399
  contextBoost: true,
7244
7400
  minEntropy: 3
7245
7401
  },
7402
+ // ── HTTP Basic credential ─────────────────────────────────────────────────
7403
+ // `Authorization: Basic <base64 of user:password>`, or the bare scheme in a
7404
+ // config list. The validator decodes the value and accepts only a
7405
+ // `user:password` whose password looks generated (letters and digits, 8+
7406
+ // characters, the query-credential rule), so prose ("Basic authentication")
7407
+ // and documentation pairs (`admin:changeme`, `user:pass1234`) stay allowed.
7408
+ // review: the same weight as a bare Bearer token.
7409
+ {
7410
+ name: "HTTP Basic Credential",
7411
+ regex: /\bBasic[ \t]{1,8}([A-Za-z0-9+/]{8,512}={0,2})/i,
7412
+ severity: "review",
7413
+ keywords: ["basic"],
7414
+ validate: validateBasicCredential,
7415
+ redactGroup: 1,
7416
+ argumentsOnly: true
7417
+ },
7246
7418
  // ── Resend ────────────────────────────────────────────────────────────────
7247
7419
  {
7248
7420
  name: "Resend API Key",
@@ -7384,6 +7556,19 @@ zoo`.split("\n");
7384
7556
  validate: validateQueryCredential,
7385
7557
  argumentsOnly: true
7386
7558
  },
7559
+ // ── OTP secret in a constructor ───────────────────────────────────────────
7560
+ // `ROTP::TOTP.new('<base32>')`, `pyotp.TOTP("<base32>")`, `new HOTP('...')`:
7561
+ // the shared secret of a one-time-password generator written as a literal.
7562
+ // The otpauth URI form is `Credential in URL Query`. review.
7563
+ {
7564
+ name: "OTP Secret",
7565
+ regex: /\b(?:TOTP|HOTP|OTP)(?:\.new|\.create)?[ \t]{0,8}\(?[ \t]{0,8}["']([A-Z2-7]{16,128}=*)["']/i,
7566
+ severity: "review",
7567
+ keywords: ["otp"],
7568
+ validate: validateOtpSecret,
7569
+ redactGroup: 1,
7570
+ argumentsOnly: true
7571
+ },
7387
7572
  // ── Secret assigned to a telling name ─────────────────────────────────────
7388
7573
  // `password: ...`, `JWT_SECRET=...`, `api_key = "..."`. The name alone is
7389
7574
  // not a finding: most such lines hold a reference (`process.env.X`), a
@@ -7391,12 +7576,14 @@ zoo`.split("\n");
7391
7576
  // generated: 12+ characters, not an identifier, Shannon entropy of 3.5
7392
7577
  // bits per character or more. review, never block (node9Firewall#348,
7393
7578
  // item 4). Tuned on CredData's public split; its look-alike lines are the
7394
- // negative control.
7579
+ // negative control. A quote may be escaped (`\"access_token\":\"...\"`):
7580
+ // JSON written inside a string literal. The two optional quotes after the
7581
+ // name are written out, not as a counted group, to keep star height 1.
7395
7582
  {
7396
7583
  name: "Assigned Secret",
7397
- regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret)[A-Za-z0-9_]{0,40}["'\]]{0,2}[ \t]{0,8}(?::=|=>|[:=])[ \t]{0,8}["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})/i,
7584
+ regex: /(?:password|passphrase|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|private[_-]?key|access[_-]?key|client[_-]?secret|consumer[_-]?key|app[_-]?key|signing[_-]?key|encryption[_-]?key|master[_-]?key|jsapi[_-]?ticket|\bpass(?![A-Za-z0-9])|_pass(?![A-Za-z0-9]))[A-Za-z0-9_]{0,40}\\?["'\]]?\\?["'\]]?[ \t]{0,8}(?::=|=>|[:=])[ \t]{0,8}\\?["'`]?([A-Za-z0-9+/_.!@#$%^*~-]{12,512}={0,2})/i,
7398
7585
  severity: "review",
7399
- keywords: ["password", "passphrase", "passwd", "pwd", "secret", "token", "key"],
7586
+ keywords: ["pass", "pwd", "secret", "token", "key", "ticket"],
7400
7587
  validate: validateAssignedSecret,
7401
7588
  argumentsOnly: true
7402
7589
  }
@@ -7458,6 +7645,35 @@ zoo`.split("\n");
7458
7645
  MAX_STRING_BYTES = 1e5;
7459
7646
  MAX_JSON_PARSE_BYTES = 1e4;
7460
7647
  ({ syntax: syntax3 } = mvdanSh3);
7648
+ EDITOR_HEADS = ["code", "code-insiders", "codium", "vscodium", "cursor"];
7649
+ MANAGER_HEADS = /* @__PURE__ */ new Set([
7650
+ "gem",
7651
+ "bundle",
7652
+ "cargo",
7653
+ "go",
7654
+ "dotnet",
7655
+ "nuget",
7656
+ "mvn",
7657
+ "mvnw",
7658
+ "composer",
7659
+ ...EDITOR_HEADS,
7660
+ "npm",
7661
+ "npx",
7662
+ "pnpm",
7663
+ "yarn",
7664
+ "bun",
7665
+ "bunx",
7666
+ "pip",
7667
+ "pip3",
7668
+ "pipx",
7669
+ "python",
7670
+ "python3",
7671
+ "py",
7672
+ "uv",
7673
+ "uvx",
7674
+ "poetry",
7675
+ "pipenv"
7676
+ ]);
7461
7677
  TUNING_FIELDS = [
7462
7678
  { checkId: "network.unknown-host", name: "allow", legacy: ["egress", "allow"] },
7463
7679
  { checkId: "network.unknown-host", name: "deny", legacy: ["egress", "deny"] },
@@ -7516,6 +7732,7 @@ zoo`.split("\n");
7516
7732
  MAP_GOVERNED = /* @__PURE__ */ new Set([
7517
7733
  "files.shell-startup",
7518
7734
  "files.password-hashes",
7735
+ "files.system-dirs",
7519
7736
  "commands.inline-exec",
7520
7737
  "commands.eval-dynamic",
7521
7738
  "commands.curl-pipe-shell",