@node9/proxy 1.12.6 → 1.12.8

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.mjs CHANGED
@@ -1074,6 +1074,17 @@ import path4 from "path";
1074
1074
  function isAssignmentContext(text) {
1075
1075
  return ASSIGNMENT_CONTEXT_RE.test(text);
1076
1076
  }
1077
+ function shannonEntropy(s) {
1078
+ if (s.length === 0) return 0;
1079
+ const freq = /* @__PURE__ */ new Map();
1080
+ for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
1081
+ let h = 0;
1082
+ for (const count of freq.values()) {
1083
+ const p = count / s.length;
1084
+ h -= p * Math.log2(p);
1085
+ }
1086
+ return h;
1087
+ }
1077
1088
  function scanFilePath(filePath, cwd = process.cwd()) {
1078
1089
  if (!filePath) return null;
1079
1090
  let resolved;
@@ -1142,8 +1153,9 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1142
1153
  continue;
1143
1154
  }
1144
1155
  if (pattern.regex.test(text)) {
1145
- const matchedValue = (text.match(pattern.regex)?.[0] ?? "").toLowerCase();
1146
- if (DLP_STOPWORDS.some((sw) => matchedValue.includes(sw))) continue;
1156
+ const raw = text.match(pattern.regex)?.[0] ?? "";
1157
+ if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) continue;
1158
+ if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) continue;
1147
1159
  const severity = pattern.contextBoost && assignmentCtx ? "block" : pattern.severity;
1148
1160
  return {
1149
1161
  patternName: pattern.name,
@@ -1175,8 +1187,9 @@ function scanText(text) {
1175
1187
  continue;
1176
1188
  }
1177
1189
  if (pattern.regex.test(t)) {
1178
- const matchedValue = (t.match(pattern.regex)?.[0] ?? "").toLowerCase();
1179
- if (DLP_STOPWORDS.some((sw) => matchedValue.includes(sw))) continue;
1190
+ const raw = t.match(pattern.regex)?.[0] ?? "";
1191
+ if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) continue;
1192
+ if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) continue;
1180
1193
  return {
1181
1194
  patternName: pattern.name,
1182
1195
  fieldPath: "response-text",
@@ -1214,7 +1227,9 @@ var init_dlp = __esm({
1214
1227
  "%{",
1215
1228
  "<your",
1216
1229
  "test_key",
1217
- "test_token"
1230
+ "test_token",
1231
+ "your",
1232
+ "here"
1218
1233
  ];
1219
1234
  DLP_PATTERNS = [
1220
1235
  // ── AWS ───────────────────────────────────────────────────────────────────
@@ -1265,7 +1280,8 @@ var init_dlp = __esm({
1265
1280
  name: "OpenAI API Key",
1266
1281
  regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/,
1267
1282
  severity: "block",
1268
- keywords: ["sk-"]
1283
+ keywords: ["sk-"],
1284
+ minEntropy: 3.5
1269
1285
  },
1270
1286
  // ── Stripe ────────────────────────────────────────────────────────────────
1271
1287
  {
@@ -1336,7 +1352,13 @@ var init_dlp = __esm({
1336
1352
  keywords: ["hvb."]
1337
1353
  },
1338
1354
  // ── Hugging Face ──────────────────────────────────────────────────────────
1339
- { name: "HuggingFace Token", regex: /\bhf_[A-Za-z]{34}\b/, severity: "block", keywords: ["hf_"] },
1355
+ {
1356
+ name: "HuggingFace Token",
1357
+ regex: /\bhf_[A-Za-z]{34}\b/,
1358
+ severity: "block",
1359
+ keywords: ["hf_"],
1360
+ minEntropy: 3
1361
+ },
1340
1362
  // ── Postman ───────────────────────────────────────────────────────────────
1341
1363
  {
1342
1364
  name: "Postman API Token",
@@ -1488,7 +1510,8 @@ var init_dlp = __esm({
1488
1510
  name: "PyPI Upload Token",
1489
1511
  regex: /\bpypi-[A-Za-z0-9_-]{50,}\b/,
1490
1512
  severity: "block",
1491
- keywords: ["pypi-"]
1513
+ keywords: ["pypi-"],
1514
+ minEntropy: 3
1492
1515
  },
1493
1516
  // ── Bearer Token ─────────────────────────────────────────────────────────
1494
1517
  // contextBoost: promoted to block when assigned (e.g. AUTH_TOKEN=Bearer eyJ...)
@@ -1497,7 +1520,99 @@ var init_dlp = __esm({
1497
1520
  regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i,
1498
1521
  severity: "review",
1499
1522
  keywords: ["bearer"],
1500
- contextBoost: true
1523
+ contextBoost: true,
1524
+ minEntropy: 3
1525
+ },
1526
+ // ── Resend ────────────────────────────────────────────────────────────────
1527
+ {
1528
+ name: "Resend API Key",
1529
+ regex: /\bre_[a-zA-Z0-9]{24}\b/,
1530
+ severity: "block",
1531
+ keywords: ["re_"]
1532
+ },
1533
+ // ── Telegram ──────────────────────────────────────────────────────────────
1534
+ {
1535
+ name: "Telegram Bot Token",
1536
+ regex: /\b[0-9]{7,10}:AA[a-zA-Z0-9_-]{33}\b/,
1537
+ severity: "block",
1538
+ keywords: [":aa"]
1539
+ },
1540
+ // ── Mapbox ────────────────────────────────────────────────────────────────
1541
+ {
1542
+ name: "Mapbox Access Token",
1543
+ regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
1544
+ severity: "block",
1545
+ keywords: ["pk.eyj1"]
1546
+ },
1547
+ // ── Notion ────────────────────────────────────────────────────────────────
1548
+ {
1549
+ name: "Notion Integration Token",
1550
+ regex: /\bsecret_[a-zA-Z0-9]{43}\b/,
1551
+ severity: "block",
1552
+ keywords: ["secret_"]
1553
+ },
1554
+ // ── Square ────────────────────────────────────────────────────────────────
1555
+ {
1556
+ name: "Square Access Token",
1557
+ regex: /\bsq0atp-[0-9A-Za-z_-]{22}\b/,
1558
+ severity: "block",
1559
+ keywords: ["sq0atp-"]
1560
+ },
1561
+ {
1562
+ name: "Square OAuth Secret",
1563
+ regex: /\bsq0csp-[0-9A-Za-z_-]{43}\b/,
1564
+ severity: "block",
1565
+ keywords: ["sq0csp-"]
1566
+ },
1567
+ // ── Typeform ──────────────────────────────────────────────────────────────
1568
+ {
1569
+ name: "Typeform Token",
1570
+ regex: /\btfp_[a-zA-Z0-9_]{59}\b/,
1571
+ severity: "block",
1572
+ keywords: ["tfp_"]
1573
+ },
1574
+ // ── Cloudinary ────────────────────────────────────────────────────────────
1575
+ {
1576
+ name: "Cloudinary URL",
1577
+ regex: /\bcloudinary:\/\/[0-9]+:[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+/,
1578
+ severity: "block",
1579
+ keywords: ["cloudinary://"]
1580
+ },
1581
+ // ── Airtable ──────────────────────────────────────────────────────────────
1582
+ // New PAT format: pat + 14 alphanum + . + 64 alphanum
1583
+ {
1584
+ name: "Airtable PAT",
1585
+ regex: /\bpat[a-zA-Z0-9]{14}\.[a-zA-Z0-9]{64}\b/,
1586
+ severity: "block",
1587
+ keywords: ["pat"]
1588
+ },
1589
+ // ── RubyGems ──────────────────────────────────────────────────────────────
1590
+ {
1591
+ name: "RubyGems API Key",
1592
+ regex: /\brubygems_[a-f0-9]{48}\b/,
1593
+ severity: "block",
1594
+ keywords: ["rubygems_"]
1595
+ },
1596
+ // ── Shippo ────────────────────────────────────────────────────────────────
1597
+ {
1598
+ name: "Shippo Token",
1599
+ regex: /\bshippo_(?:live|test)_[a-f0-9]{40}\b/,
1600
+ severity: "block",
1601
+ keywords: ["shippo_"]
1602
+ },
1603
+ // ── Plaid ─────────────────────────────────────────────────────────────────
1604
+ {
1605
+ name: "Plaid Access Token",
1606
+ regex: /\baccess-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/,
1607
+ severity: "block",
1608
+ keywords: ["access-sandbox", "access-development", "access-production"]
1609
+ },
1610
+ // ── Age ───────────────────────────────────────────────────────────────────
1611
+ {
1612
+ name: "Age Identity Key",
1613
+ regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
1614
+ severity: "block",
1615
+ keywords: ["age-secret-key-"]
1501
1616
  }
1502
1617
  ];
1503
1618
  SENSITIVE_PATH_PATTERNS = [
@@ -7647,6 +7762,10 @@ var init_ui = __esm({
7647
7762
  btn.appendChild(badge);
7648
7763
  }
7649
7764
  }
7765
+ // Auto-open when browser was launched directly from \`node9 scan\`
7766
+ if (new URLSearchParams(location.search).get('openscan') === '1') {
7767
+ openScanModal();
7768
+ }
7650
7769
  } catch {}
7651
7770
  })();
7652
7771
 
@@ -9488,14 +9607,9 @@ function registerScanCommand(program2) {
9488
9607
  );
9489
9608
  console.log(heroLine);
9490
9609
  console.log("");
9491
- if (blockedCount > 0) {
9610
+ if (scan.totalCostUSD > 0) {
9492
9611
  console.log(
9493
- " " + chalk2.red("\u{1F6D1} Would have blocked") + " " + chalk2.red.bold(String(blockedCount).padStart(5)) + chalk2.dim(" operations stopped before execution")
9494
- );
9495
- }
9496
- if (reviewCount > 0) {
9497
- console.log(
9498
- " " + chalk2.yellow("\u{1F441} Would have flagged") + " " + chalk2.yellow.bold(String(reviewCount).padStart(5)) + chalk2.dim(" sent to you for approval")
9612
+ " " + chalk2.bold(fmtCost(scan.totalCostUSD)) + chalk2.dim(" AI spend \xB7 ") + chalk2.dim(`${totalRisky} risky operations`)
9499
9613
  );
9500
9614
  }
9501
9615
  if (scan.dlpFindings.length > 0) {
@@ -9503,47 +9617,27 @@ function registerScanCommand(program2) {
9503
9617
  " " + chalk2.red("\u{1F511} Credential leak") + " " + chalk2.red.bold(String(scan.dlpFindings.length).padStart(5)) + chalk2.dim(" secret detected in tool call")
9504
9618
  );
9505
9619
  }
9506
- if (scan.loopFindings.length > 0) {
9620
+ if (blockedCount > 0) {
9507
9621
  console.log(
9508
- " " + chalk2.yellow("\u{1F501} Loop detected") + " " + chalk2.yellow.bold(String(scan.loopFindings.length).padStart(5)) + chalk2.dim(" repeated tool call patterns found")
9622
+ " " + chalk2.red("\u{1F6D1} Would have blocked") + " " + chalk2.red.bold(String(blockedCount).padStart(5)) + chalk2.dim(" operations stopped before execution")
9509
9623
  );
9510
9624
  }
9511
- console.log("");
9512
- for (const section of summary.sections) {
9513
- const countParts = [];
9514
- if (section.blockedCount > 0)
9515
- countParts.push(chalk2.red(`${section.blockedCount} blocked`));
9516
- if (section.reviewCount > 0)
9517
- countParts.push(chalk2.yellow(`${section.reviewCount} review`));
9518
- const countStr = countParts.join(chalk2.dim(" \xB7 "));
9519
- const enableHint = section.shieldKey ? chalk2.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
9520
- console.log(" " + chalk2.dim("\u2500".repeat(70)));
9625
+ if (scan.loopFindings.length > 0) {
9521
9626
  console.log(
9522
- " " + chalk2.bold(section.label) + (section.subtitle ? chalk2.dim(` \xB7 ${section.subtitle}`) : "") + " " + countStr + enableHint
9627
+ " " + chalk2.yellow("\u{1F501} Loop detected") + " " + chalk2.yellow.bold(String(scan.loopFindings.length).padStart(5)) + chalk2.dim(" repeated tool call patterns found")
9523
9628
  );
9524
- for (const rule of section.rules) {
9525
- printRuleGroup(rule, topN, drillDown, previewWidth);
9526
- }
9527
- console.log("");
9528
9629
  }
9529
- const activeShieldIds = new Set(
9530
- summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
9531
- );
9532
- const emptyShields = Object.keys(SHIELDS).filter((n) => !activeShieldIds.has(n)).sort();
9533
- if (emptyShields.length > 0) {
9534
- console.log(" " + chalk2.dim("\u2500".repeat(70)));
9630
+ if (reviewCount > 0) {
9535
9631
  console.log(
9536
- " " + chalk2.bold("Shields") + chalk2.dim(" \xB7 no findings in your history") + " " + chalk2.green("\u2713")
9632
+ " " + chalk2.yellow("\u{1F441} Would have flagged") + " " + chalk2.yellow.bold(String(reviewCount).padStart(5)) + chalk2.dim(" sent to you for approval")
9537
9633
  );
9538
- console.log(" " + chalk2.dim(emptyShields.join(" \xB7 ")));
9539
- console.log(" " + chalk2.dim("\u2192 node9 shield enable <name> to activate any shield"));
9540
- console.log("");
9541
9634
  }
9635
+ console.log("");
9542
9636
  if (scan.dlpFindings.length > 0) {
9543
9637
  console.log(" " + chalk2.dim("\u2500".repeat(70)));
9544
9638
  console.log(
9545
9639
  " " + chalk2.red.bold("\u{1F511} Credential Leaks") + chalk2.dim(" \xB7 ") + chalk2.red(
9546
- `${num(scan.dlpFindings.length)} potential secret leak${scan.dlpFindings.length !== 1 ? "s" : ""}`
9640
+ `${num(scan.dlpFindings.length)} secret${scan.dlpFindings.length !== 1 ? "s" : ""} found in plain text`
9547
9641
  )
9548
9642
  );
9549
9643
  const shownDlp = drillDown ? scan.dlpFindings : scan.dlpFindings.slice(0, topN);
@@ -9553,7 +9647,7 @@ function registerScanCommand(program2) {
9553
9647
  const agentBadge = f.agent === "gemini" ? chalk2.blue("[Gemini] ") : f.agent === "codex" ? chalk2.magenta("[Codex] ") : chalk2.cyan("[Claude] ");
9554
9648
  const sessionSuffix = f.sessionId ? chalk2.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
9555
9649
  console.log(
9556
- ` ${ts}${proj}${agentBadge}` + chalk2.yellow(f.patternName) + chalk2.dim(" ") + chalk2.gray(f.redactedSample) + sessionSuffix
9650
+ ` \u{1F6A8} ${ts}${proj}${agentBadge}` + chalk2.yellow(f.patternName) + chalk2.dim(" ") + chalk2.gray(f.redactedSample) + sessionSuffix
9557
9651
  );
9558
9652
  }
9559
9653
  if (!drillDown && scan.dlpFindings.length > topN) {
@@ -9565,6 +9659,21 @@ function registerScanCommand(program2) {
9565
9659
  }
9566
9660
  console.log("");
9567
9661
  }
9662
+ const blockedRuleSections = summary.sections.map((s) => ({ ...s, rules: s.rules.filter((r) => r.verdict === "block") })).filter((s) => s.rules.length > 0);
9663
+ if (blockedRuleSections.length > 0) {
9664
+ console.log(" " + chalk2.dim("\u2500".repeat(70)));
9665
+ console.log(
9666
+ " " + chalk2.red.bold("\u{1F6D1} Blocked") + chalk2.dim(" \xB7 ") + chalk2.red(
9667
+ `${blockedCount} operation${blockedCount !== 1 ? "s" : ""} node9 would have stopped`
9668
+ )
9669
+ );
9670
+ for (const section of blockedRuleSections) {
9671
+ for (const rule of section.rules) {
9672
+ printRuleGroup(rule, topN, drillDown, previewWidth);
9673
+ }
9674
+ }
9675
+ console.log("");
9676
+ }
9568
9677
  if (scan.loopFindings.length > 0) {
9569
9678
  console.log(" " + chalk2.dim("\u2500".repeat(70)));
9570
9679
  console.log(
@@ -9591,12 +9700,32 @@ function registerScanCommand(program2) {
9591
9700
  }
9592
9701
  console.log("");
9593
9702
  }
9594
- }
9595
- if (scan.totalCostUSD > 0) {
9596
- console.log(
9597
- " " + chalk2.bold("Agent spend:") + " " + chalk2.yellow(fmtCost(scan.totalCostUSD)) + chalk2.dim(" (for per-period breakdown: node9 report)")
9703
+ for (const section of summary.sections) {
9704
+ const reviewRules = section.rules.filter((r) => r.verdict !== "block");
9705
+ if (reviewRules.length === 0) continue;
9706
+ const enableHint = section.shieldKey ? chalk2.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
9707
+ console.log(" " + chalk2.dim("\u2500".repeat(70)));
9708
+ console.log(
9709
+ " " + chalk2.bold(section.label) + (section.subtitle ? chalk2.dim(` \xB7 ${section.subtitle}`) : "") + " " + chalk2.yellow(`${section.reviewCount} review`) + enableHint
9710
+ );
9711
+ for (const rule of reviewRules) {
9712
+ printRuleGroup(rule, topN, drillDown, previewWidth);
9713
+ }
9714
+ console.log("");
9715
+ }
9716
+ const activeShieldIds = new Set(
9717
+ summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
9598
9718
  );
9599
- console.log("");
9719
+ const emptyShields = Object.keys(SHIELDS).filter((n) => !activeShieldIds.has(n)).sort();
9720
+ if (emptyShields.length > 0) {
9721
+ console.log(" " + chalk2.dim("\u2500".repeat(70)));
9722
+ console.log(
9723
+ " " + chalk2.bold("\u{1F6E1} Inactive Shields") + chalk2.dim(" \xB7 enable for more coverage")
9724
+ );
9725
+ console.log(" " + chalk2.dim(emptyShields.join(" \xB7 ")));
9726
+ console.log(" " + chalk2.dim("\u2192 node9 shield enable <name> to activate"));
9727
+ console.log("");
9728
+ }
9600
9729
  }
9601
9730
  if (isInstalled) {
9602
9731
  console.log(chalk2.green(" \u2705 node9 is active \u2014 your future sessions are protected."));
@@ -9637,7 +9766,7 @@ function registerScanCommand(program2) {
9637
9766
  console.log("");
9638
9767
  if (!isTestingMode()) {
9639
9768
  if (isInstalled) {
9640
- const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
9769
+ const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/?openscan=1`;
9641
9770
  if (isDaemonRunning()) {
9642
9771
  const internalToken = getInternalToken();
9643
9772
  if (internalToken) {
package/dist/index.js CHANGED
@@ -995,6 +995,17 @@ var ASSIGNMENT_CONTEXT_RE = /\b(?:password|passwd|secret|token|api[_-]?key|auth(
995
995
  function isAssignmentContext(text) {
996
996
  return ASSIGNMENT_CONTEXT_RE.test(text);
997
997
  }
998
+ function shannonEntropy(s) {
999
+ if (s.length === 0) return 0;
1000
+ const freq = /* @__PURE__ */ new Map();
1001
+ for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
1002
+ let h = 0;
1003
+ for (const count of freq.values()) {
1004
+ const p = count / s.length;
1005
+ h -= p * Math.log2(p);
1006
+ }
1007
+ return h;
1008
+ }
998
1009
  var DLP_STOPWORDS = [
999
1010
  "example",
1000
1011
  "placeholder",
@@ -1017,7 +1028,9 @@ var DLP_STOPWORDS = [
1017
1028
  "%{",
1018
1029
  "<your",
1019
1030
  "test_key",
1020
- "test_token"
1031
+ "test_token",
1032
+ "your",
1033
+ "here"
1021
1034
  ];
1022
1035
  var DLP_PATTERNS = [
1023
1036
  // ── AWS ───────────────────────────────────────────────────────────────────
@@ -1068,7 +1081,8 @@ var DLP_PATTERNS = [
1068
1081
  name: "OpenAI API Key",
1069
1082
  regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/,
1070
1083
  severity: "block",
1071
- keywords: ["sk-"]
1084
+ keywords: ["sk-"],
1085
+ minEntropy: 3.5
1072
1086
  },
1073
1087
  // ── Stripe ────────────────────────────────────────────────────────────────
1074
1088
  {
@@ -1139,7 +1153,13 @@ var DLP_PATTERNS = [
1139
1153
  keywords: ["hvb."]
1140
1154
  },
1141
1155
  // ── Hugging Face ──────────────────────────────────────────────────────────
1142
- { name: "HuggingFace Token", regex: /\bhf_[A-Za-z]{34}\b/, severity: "block", keywords: ["hf_"] },
1156
+ {
1157
+ name: "HuggingFace Token",
1158
+ regex: /\bhf_[A-Za-z]{34}\b/,
1159
+ severity: "block",
1160
+ keywords: ["hf_"],
1161
+ minEntropy: 3
1162
+ },
1143
1163
  // ── Postman ───────────────────────────────────────────────────────────────
1144
1164
  {
1145
1165
  name: "Postman API Token",
@@ -1291,7 +1311,8 @@ var DLP_PATTERNS = [
1291
1311
  name: "PyPI Upload Token",
1292
1312
  regex: /\bpypi-[A-Za-z0-9_-]{50,}\b/,
1293
1313
  severity: "block",
1294
- keywords: ["pypi-"]
1314
+ keywords: ["pypi-"],
1315
+ minEntropy: 3
1295
1316
  },
1296
1317
  // ── Bearer Token ─────────────────────────────────────────────────────────
1297
1318
  // contextBoost: promoted to block when assigned (e.g. AUTH_TOKEN=Bearer eyJ...)
@@ -1300,7 +1321,99 @@ var DLP_PATTERNS = [
1300
1321
  regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i,
1301
1322
  severity: "review",
1302
1323
  keywords: ["bearer"],
1303
- contextBoost: true
1324
+ contextBoost: true,
1325
+ minEntropy: 3
1326
+ },
1327
+ // ── Resend ────────────────────────────────────────────────────────────────
1328
+ {
1329
+ name: "Resend API Key",
1330
+ regex: /\bre_[a-zA-Z0-9]{24}\b/,
1331
+ severity: "block",
1332
+ keywords: ["re_"]
1333
+ },
1334
+ // ── Telegram ──────────────────────────────────────────────────────────────
1335
+ {
1336
+ name: "Telegram Bot Token",
1337
+ regex: /\b[0-9]{7,10}:AA[a-zA-Z0-9_-]{33}\b/,
1338
+ severity: "block",
1339
+ keywords: [":aa"]
1340
+ },
1341
+ // ── Mapbox ────────────────────────────────────────────────────────────────
1342
+ {
1343
+ name: "Mapbox Access Token",
1344
+ regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
1345
+ severity: "block",
1346
+ keywords: ["pk.eyj1"]
1347
+ },
1348
+ // ── Notion ────────────────────────────────────────────────────────────────
1349
+ {
1350
+ name: "Notion Integration Token",
1351
+ regex: /\bsecret_[a-zA-Z0-9]{43}\b/,
1352
+ severity: "block",
1353
+ keywords: ["secret_"]
1354
+ },
1355
+ // ── Square ────────────────────────────────────────────────────────────────
1356
+ {
1357
+ name: "Square Access Token",
1358
+ regex: /\bsq0atp-[0-9A-Za-z_-]{22}\b/,
1359
+ severity: "block",
1360
+ keywords: ["sq0atp-"]
1361
+ },
1362
+ {
1363
+ name: "Square OAuth Secret",
1364
+ regex: /\bsq0csp-[0-9A-Za-z_-]{43}\b/,
1365
+ severity: "block",
1366
+ keywords: ["sq0csp-"]
1367
+ },
1368
+ // ── Typeform ──────────────────────────────────────────────────────────────
1369
+ {
1370
+ name: "Typeform Token",
1371
+ regex: /\btfp_[a-zA-Z0-9_]{59}\b/,
1372
+ severity: "block",
1373
+ keywords: ["tfp_"]
1374
+ },
1375
+ // ── Cloudinary ────────────────────────────────────────────────────────────
1376
+ {
1377
+ name: "Cloudinary URL",
1378
+ regex: /\bcloudinary:\/\/[0-9]+:[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+/,
1379
+ severity: "block",
1380
+ keywords: ["cloudinary://"]
1381
+ },
1382
+ // ── Airtable ──────────────────────────────────────────────────────────────
1383
+ // New PAT format: pat + 14 alphanum + . + 64 alphanum
1384
+ {
1385
+ name: "Airtable PAT",
1386
+ regex: /\bpat[a-zA-Z0-9]{14}\.[a-zA-Z0-9]{64}\b/,
1387
+ severity: "block",
1388
+ keywords: ["pat"]
1389
+ },
1390
+ // ── RubyGems ──────────────────────────────────────────────────────────────
1391
+ {
1392
+ name: "RubyGems API Key",
1393
+ regex: /\brubygems_[a-f0-9]{48}\b/,
1394
+ severity: "block",
1395
+ keywords: ["rubygems_"]
1396
+ },
1397
+ // ── Shippo ────────────────────────────────────────────────────────────────
1398
+ {
1399
+ name: "Shippo Token",
1400
+ regex: /\bshippo_(?:live|test)_[a-f0-9]{40}\b/,
1401
+ severity: "block",
1402
+ keywords: ["shippo_"]
1403
+ },
1404
+ // ── Plaid ─────────────────────────────────────────────────────────────────
1405
+ {
1406
+ name: "Plaid Access Token",
1407
+ regex: /\baccess-(?:sandbox|development|production)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/,
1408
+ severity: "block",
1409
+ keywords: ["access-sandbox", "access-development", "access-production"]
1410
+ },
1411
+ // ── Age ───────────────────────────────────────────────────────────────────
1412
+ {
1413
+ name: "Age Identity Key",
1414
+ regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
1415
+ severity: "block",
1416
+ keywords: ["age-secret-key-"]
1304
1417
  }
1305
1418
  ];
1306
1419
  var SENSITIVE_PATH_PATTERNS = [
@@ -1397,8 +1510,9 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1397
1510
  continue;
1398
1511
  }
1399
1512
  if (pattern.regex.test(text)) {
1400
- const matchedValue = (text.match(pattern.regex)?.[0] ?? "").toLowerCase();
1401
- if (DLP_STOPWORDS.some((sw) => matchedValue.includes(sw))) continue;
1513
+ const raw = text.match(pattern.regex)?.[0] ?? "";
1514
+ if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) continue;
1515
+ if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) continue;
1402
1516
  const severity = pattern.contextBoost && assignmentCtx ? "block" : pattern.severity;
1403
1517
  return {
1404
1518
  patternName: pattern.name,