@node9/proxy 1.12.5 → 1.12.7

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 CHANGED
@@ -1094,6 +1094,17 @@ var init_regex = __esm({
1094
1094
  function isAssignmentContext(text) {
1095
1095
  return ASSIGNMENT_CONTEXT_RE.test(text);
1096
1096
  }
1097
+ function shannonEntropy(s) {
1098
+ if (s.length === 0) return 0;
1099
+ const freq = /* @__PURE__ */ new Map();
1100
+ for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
1101
+ let h = 0;
1102
+ for (const count of freq.values()) {
1103
+ const p = count / s.length;
1104
+ h -= p * Math.log2(p);
1105
+ }
1106
+ return h;
1107
+ }
1097
1108
  function scanFilePath(filePath, cwd = process.cwd()) {
1098
1109
  if (!filePath) return null;
1099
1110
  let resolved;
@@ -1162,8 +1173,9 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1162
1173
  continue;
1163
1174
  }
1164
1175
  if (pattern.regex.test(text)) {
1165
- const matchedValue = (text.match(pattern.regex)?.[0] ?? "").toLowerCase();
1166
- if (DLP_STOPWORDS.some((sw) => matchedValue.includes(sw))) continue;
1176
+ const raw = text.match(pattern.regex)?.[0] ?? "";
1177
+ if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) continue;
1178
+ if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) continue;
1167
1179
  const severity = pattern.contextBoost && assignmentCtx ? "block" : pattern.severity;
1168
1180
  return {
1169
1181
  patternName: pattern.name,
@@ -1195,8 +1207,9 @@ function scanText(text) {
1195
1207
  continue;
1196
1208
  }
1197
1209
  if (pattern.regex.test(t)) {
1198
- const matchedValue = (t.match(pattern.regex)?.[0] ?? "").toLowerCase();
1199
- if (DLP_STOPWORDS.some((sw) => matchedValue.includes(sw))) continue;
1210
+ const raw = t.match(pattern.regex)?.[0] ?? "";
1211
+ if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) continue;
1212
+ if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) continue;
1200
1213
  return {
1201
1214
  patternName: pattern.name,
1202
1215
  fieldPath: "response-text",
@@ -1236,7 +1249,9 @@ var init_dlp = __esm({
1236
1249
  "%{",
1237
1250
  "<your",
1238
1251
  "test_key",
1239
- "test_token"
1252
+ "test_token",
1253
+ "your",
1254
+ "here"
1240
1255
  ];
1241
1256
  DLP_PATTERNS = [
1242
1257
  // ── AWS ───────────────────────────────────────────────────────────────────
@@ -1287,7 +1302,8 @@ var init_dlp = __esm({
1287
1302
  name: "OpenAI API Key",
1288
1303
  regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/,
1289
1304
  severity: "block",
1290
- keywords: ["sk-"]
1305
+ keywords: ["sk-"],
1306
+ minEntropy: 3.5
1291
1307
  },
1292
1308
  // ── Stripe ────────────────────────────────────────────────────────────────
1293
1309
  {
@@ -1358,7 +1374,13 @@ var init_dlp = __esm({
1358
1374
  keywords: ["hvb."]
1359
1375
  },
1360
1376
  // ── Hugging Face ──────────────────────────────────────────────────────────
1361
- { name: "HuggingFace Token", regex: /\bhf_[A-Za-z]{34}\b/, severity: "block", keywords: ["hf_"] },
1377
+ {
1378
+ name: "HuggingFace Token",
1379
+ regex: /\bhf_[A-Za-z]{34}\b/,
1380
+ severity: "block",
1381
+ keywords: ["hf_"],
1382
+ minEntropy: 3
1383
+ },
1362
1384
  // ── Postman ───────────────────────────────────────────────────────────────
1363
1385
  {
1364
1386
  name: "Postman API Token",
@@ -1510,7 +1532,8 @@ var init_dlp = __esm({
1510
1532
  name: "PyPI Upload Token",
1511
1533
  regex: /\bpypi-[A-Za-z0-9_-]{50,}\b/,
1512
1534
  severity: "block",
1513
- keywords: ["pypi-"]
1535
+ keywords: ["pypi-"],
1536
+ minEntropy: 3
1514
1537
  },
1515
1538
  // ── Bearer Token ─────────────────────────────────────────────────────────
1516
1539
  // contextBoost: promoted to block when assigned (e.g. AUTH_TOKEN=Bearer eyJ...)
@@ -1519,7 +1542,99 @@ var init_dlp = __esm({
1519
1542
  regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i,
1520
1543
  severity: "review",
1521
1544
  keywords: ["bearer"],
1522
- contextBoost: true
1545
+ contextBoost: true,
1546
+ minEntropy: 3
1547
+ },
1548
+ // ── Resend ────────────────────────────────────────────────────────────────
1549
+ {
1550
+ name: "Resend API Key",
1551
+ regex: /\bre_[a-zA-Z0-9]{24}\b/,
1552
+ severity: "block",
1553
+ keywords: ["re_"]
1554
+ },
1555
+ // ── Telegram ──────────────────────────────────────────────────────────────
1556
+ {
1557
+ name: "Telegram Bot Token",
1558
+ regex: /\b[0-9]{7,10}:AA[a-zA-Z0-9_-]{33}\b/,
1559
+ severity: "block",
1560
+ keywords: [":aa"]
1561
+ },
1562
+ // ── Mapbox ────────────────────────────────────────────────────────────────
1563
+ {
1564
+ name: "Mapbox Access Token",
1565
+ regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
1566
+ severity: "block",
1567
+ keywords: ["pk.eyj1"]
1568
+ },
1569
+ // ── Notion ────────────────────────────────────────────────────────────────
1570
+ {
1571
+ name: "Notion Integration Token",
1572
+ regex: /\bsecret_[a-zA-Z0-9]{43}\b/,
1573
+ severity: "block",
1574
+ keywords: ["secret_"]
1575
+ },
1576
+ // ── Square ────────────────────────────────────────────────────────────────
1577
+ {
1578
+ name: "Square Access Token",
1579
+ regex: /\bsq0atp-[0-9A-Za-z_-]{22}\b/,
1580
+ severity: "block",
1581
+ keywords: ["sq0atp-"]
1582
+ },
1583
+ {
1584
+ name: "Square OAuth Secret",
1585
+ regex: /\bsq0csp-[0-9A-Za-z_-]{43}\b/,
1586
+ severity: "block",
1587
+ keywords: ["sq0csp-"]
1588
+ },
1589
+ // ── Typeform ──────────────────────────────────────────────────────────────
1590
+ {
1591
+ name: "Typeform Token",
1592
+ regex: /\btfp_[a-zA-Z0-9_]{59}\b/,
1593
+ severity: "block",
1594
+ keywords: ["tfp_"]
1595
+ },
1596
+ // ── Cloudinary ────────────────────────────────────────────────────────────
1597
+ {
1598
+ name: "Cloudinary URL",
1599
+ regex: /\bcloudinary:\/\/[0-9]+:[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+/,
1600
+ severity: "block",
1601
+ keywords: ["cloudinary://"]
1602
+ },
1603
+ // ── Airtable ──────────────────────────────────────────────────────────────
1604
+ // New PAT format: pat + 14 alphanum + . + 64 alphanum
1605
+ {
1606
+ name: "Airtable PAT",
1607
+ regex: /\bpat[a-zA-Z0-9]{14}\.[a-zA-Z0-9]{64}\b/,
1608
+ severity: "block",
1609
+ keywords: ["pat"]
1610
+ },
1611
+ // ── RubyGems ──────────────────────────────────────────────────────────────
1612
+ {
1613
+ name: "RubyGems API Key",
1614
+ regex: /\brubygems_[a-f0-9]{48}\b/,
1615
+ severity: "block",
1616
+ keywords: ["rubygems_"]
1617
+ },
1618
+ // ── Shippo ────────────────────────────────────────────────────────────────
1619
+ {
1620
+ name: "Shippo Token",
1621
+ regex: /\bshippo_(?:live|test)_[a-f0-9]{40}\b/,
1622
+ severity: "block",
1623
+ keywords: ["shippo_"]
1624
+ },
1625
+ // ── Plaid ─────────────────────────────────────────────────────────────────
1626
+ {
1627
+ name: "Plaid Access Token",
1628
+ 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/,
1629
+ severity: "block",
1630
+ keywords: ["access-sandbox", "access-development", "access-production"]
1631
+ },
1632
+ // ── Age ───────────────────────────────────────────────────────────────────
1633
+ {
1634
+ name: "Age Identity Key",
1635
+ regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
1636
+ severity: "block",
1637
+ keywords: ["age-secret-key-"]
1523
1638
  }
1524
1639
  ];
1525
1640
  SENSITIVE_PATH_PATTERNS = [
@@ -7670,6 +7785,10 @@ var init_ui = __esm({
7670
7785
  btn.appendChild(badge);
7671
7786
  }
7672
7787
  }
7788
+ // Auto-open when browser was launched directly from \`node9 scan\`
7789
+ if (new URLSearchParams(location.search).get('openscan') === '1') {
7790
+ openScanModal();
7791
+ }
7673
7792
  } catch {}
7674
7793
  })();
7675
7794
 
@@ -9426,7 +9545,7 @@ function registerScanCommand(program2) {
9426
9545
  import_chalk2.default.cyan.bold("\u{1F50D} Scanning your AI history") + import_chalk2.default.dim(" \u2014 what would node9 have caught?")
9427
9546
  );
9428
9547
  console.log("");
9429
- const useTTY = process.stdout.isTTY === true;
9548
+ const useTTY = process.stdout.isTTY === true && process.env.NODE9_WRAPPER !== "1";
9430
9549
  if (!useTTY) {
9431
9550
  process.stdout.write(
9432
9551
  " " + import_chalk2.default.dim("Scanning your history \u2014 this may take a moment...\n")
@@ -9508,14 +9627,9 @@ function registerScanCommand(program2) {
9508
9627
  );
9509
9628
  console.log(heroLine);
9510
9629
  console.log("");
9511
- if (blockedCount > 0) {
9630
+ if (scan.totalCostUSD > 0) {
9512
9631
  console.log(
9513
- " " + import_chalk2.default.red("\u{1F6D1} Would have blocked") + " " + import_chalk2.default.red.bold(String(blockedCount).padStart(5)) + import_chalk2.default.dim(" operations stopped before execution")
9514
- );
9515
- }
9516
- if (reviewCount > 0) {
9517
- console.log(
9518
- " " + import_chalk2.default.yellow("\u{1F441} Would have flagged") + " " + import_chalk2.default.yellow.bold(String(reviewCount).padStart(5)) + import_chalk2.default.dim(" sent to you for approval")
9632
+ " " + import_chalk2.default.bold(fmtCost(scan.totalCostUSD)) + import_chalk2.default.dim(" AI spend \xB7 ") + import_chalk2.default.dim(`${totalRisky} risky operations`)
9519
9633
  );
9520
9634
  }
9521
9635
  if (scan.dlpFindings.length > 0) {
@@ -9523,47 +9637,27 @@ function registerScanCommand(program2) {
9523
9637
  " " + import_chalk2.default.red("\u{1F511} Credential leak") + " " + import_chalk2.default.red.bold(String(scan.dlpFindings.length).padStart(5)) + import_chalk2.default.dim(" secret detected in tool call")
9524
9638
  );
9525
9639
  }
9526
- if (scan.loopFindings.length > 0) {
9640
+ if (blockedCount > 0) {
9527
9641
  console.log(
9528
- " " + import_chalk2.default.yellow("\u{1F501} Loop detected") + " " + import_chalk2.default.yellow.bold(String(scan.loopFindings.length).padStart(5)) + import_chalk2.default.dim(" repeated tool call patterns found")
9642
+ " " + import_chalk2.default.red("\u{1F6D1} Would have blocked") + " " + import_chalk2.default.red.bold(String(blockedCount).padStart(5)) + import_chalk2.default.dim(" operations stopped before execution")
9529
9643
  );
9530
9644
  }
9531
- console.log("");
9532
- for (const section of summary.sections) {
9533
- const countParts = [];
9534
- if (section.blockedCount > 0)
9535
- countParts.push(import_chalk2.default.red(`${section.blockedCount} blocked`));
9536
- if (section.reviewCount > 0)
9537
- countParts.push(import_chalk2.default.yellow(`${section.reviewCount} review`));
9538
- const countStr = countParts.join(import_chalk2.default.dim(" \xB7 "));
9539
- const enableHint = section.shieldKey ? import_chalk2.default.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
9540
- console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9645
+ if (scan.loopFindings.length > 0) {
9541
9646
  console.log(
9542
- " " + import_chalk2.default.bold(section.label) + (section.subtitle ? import_chalk2.default.dim(` \xB7 ${section.subtitle}`) : "") + " " + countStr + enableHint
9647
+ " " + import_chalk2.default.yellow("\u{1F501} Loop detected") + " " + import_chalk2.default.yellow.bold(String(scan.loopFindings.length).padStart(5)) + import_chalk2.default.dim(" repeated tool call patterns found")
9543
9648
  );
9544
- for (const rule of section.rules) {
9545
- printRuleGroup(rule, topN, drillDown, previewWidth);
9546
- }
9547
- console.log("");
9548
9649
  }
9549
- const activeShieldIds = new Set(
9550
- summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
9551
- );
9552
- const emptyShields = Object.keys(SHIELDS).filter((n) => !activeShieldIds.has(n)).sort();
9553
- if (emptyShields.length > 0) {
9554
- console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9650
+ if (reviewCount > 0) {
9555
9651
  console.log(
9556
- " " + import_chalk2.default.bold("Shields") + import_chalk2.default.dim(" \xB7 no findings in your history") + " " + import_chalk2.default.green("\u2713")
9652
+ " " + import_chalk2.default.yellow("\u{1F441} Would have flagged") + " " + import_chalk2.default.yellow.bold(String(reviewCount).padStart(5)) + import_chalk2.default.dim(" sent to you for approval")
9557
9653
  );
9558
- console.log(" " + import_chalk2.default.dim(emptyShields.join(" \xB7 ")));
9559
- console.log(" " + import_chalk2.default.dim("\u2192 node9 shield enable <name> to activate any shield"));
9560
- console.log("");
9561
9654
  }
9655
+ console.log("");
9562
9656
  if (scan.dlpFindings.length > 0) {
9563
9657
  console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9564
9658
  console.log(
9565
9659
  " " + import_chalk2.default.red.bold("\u{1F511} Credential Leaks") + import_chalk2.default.dim(" \xB7 ") + import_chalk2.default.red(
9566
- `${num(scan.dlpFindings.length)} potential secret leak${scan.dlpFindings.length !== 1 ? "s" : ""}`
9660
+ `${num(scan.dlpFindings.length)} secret${scan.dlpFindings.length !== 1 ? "s" : ""} found in plain text`
9567
9661
  )
9568
9662
  );
9569
9663
  const shownDlp = drillDown ? scan.dlpFindings : scan.dlpFindings.slice(0, topN);
@@ -9573,7 +9667,7 @@ function registerScanCommand(program2) {
9573
9667
  const agentBadge = f.agent === "gemini" ? import_chalk2.default.blue("[Gemini] ") : f.agent === "codex" ? import_chalk2.default.magenta("[Codex] ") : import_chalk2.default.cyan("[Claude] ");
9574
9668
  const sessionSuffix = f.sessionId ? import_chalk2.default.dim(` \u2192 ${f.sessionId.slice(0, 8)}`) : "";
9575
9669
  console.log(
9576
- ` ${ts}${proj}${agentBadge}` + import_chalk2.default.yellow(f.patternName) + import_chalk2.default.dim(" ") + import_chalk2.default.gray(f.redactedSample) + sessionSuffix
9670
+ ` \u{1F6A8} ${ts}${proj}${agentBadge}` + import_chalk2.default.yellow(f.patternName) + import_chalk2.default.dim(" ") + import_chalk2.default.gray(f.redactedSample) + sessionSuffix
9577
9671
  );
9578
9672
  }
9579
9673
  if (!drillDown && scan.dlpFindings.length > topN) {
@@ -9585,6 +9679,21 @@ function registerScanCommand(program2) {
9585
9679
  }
9586
9680
  console.log("");
9587
9681
  }
9682
+ const blockedRuleSections = summary.sections.map((s) => ({ ...s, rules: s.rules.filter((r) => r.verdict === "block") })).filter((s) => s.rules.length > 0);
9683
+ if (blockedRuleSections.length > 0) {
9684
+ console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9685
+ console.log(
9686
+ " " + import_chalk2.default.red.bold("\u{1F6D1} Blocked") + import_chalk2.default.dim(" \xB7 ") + import_chalk2.default.red(
9687
+ `${blockedCount} operation${blockedCount !== 1 ? "s" : ""} node9 would have stopped`
9688
+ )
9689
+ );
9690
+ for (const section of blockedRuleSections) {
9691
+ for (const rule of section.rules) {
9692
+ printRuleGroup(rule, topN, drillDown, previewWidth);
9693
+ }
9694
+ }
9695
+ console.log("");
9696
+ }
9588
9697
  if (scan.loopFindings.length > 0) {
9589
9698
  console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9590
9699
  console.log(
@@ -9611,12 +9720,32 @@ function registerScanCommand(program2) {
9611
9720
  }
9612
9721
  console.log("");
9613
9722
  }
9614
- }
9615
- if (scan.totalCostUSD > 0) {
9616
- console.log(
9617
- " " + import_chalk2.default.bold("Agent spend:") + " " + import_chalk2.default.yellow(fmtCost(scan.totalCostUSD)) + import_chalk2.default.dim(" (for per-period breakdown: node9 report)")
9723
+ for (const section of summary.sections) {
9724
+ const reviewRules = section.rules.filter((r) => r.verdict !== "block");
9725
+ if (reviewRules.length === 0) continue;
9726
+ const enableHint = section.shieldKey ? import_chalk2.default.dim(` \u2192 node9 shield enable ${section.shieldKey}`) : "";
9727
+ console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9728
+ console.log(
9729
+ " " + import_chalk2.default.bold(section.label) + (section.subtitle ? import_chalk2.default.dim(` \xB7 ${section.subtitle}`) : "") + " " + import_chalk2.default.yellow(`${section.reviewCount} review`) + enableHint
9730
+ );
9731
+ for (const rule of reviewRules) {
9732
+ printRuleGroup(rule, topN, drillDown, previewWidth);
9733
+ }
9734
+ console.log("");
9735
+ }
9736
+ const activeShieldIds = new Set(
9737
+ summary.sections.filter((s) => s.sourceType === "shield" && s.shieldKey).map((s) => s.shieldKey)
9618
9738
  );
9619
- console.log("");
9739
+ const emptyShields = Object.keys(SHIELDS).filter((n) => !activeShieldIds.has(n)).sort();
9740
+ if (emptyShields.length > 0) {
9741
+ console.log(" " + import_chalk2.default.dim("\u2500".repeat(70)));
9742
+ console.log(
9743
+ " " + import_chalk2.default.bold("\u{1F6E1} Inactive Shields") + import_chalk2.default.dim(" \xB7 enable for more coverage")
9744
+ );
9745
+ console.log(" " + import_chalk2.default.dim(emptyShields.join(" \xB7 ")));
9746
+ console.log(" " + import_chalk2.default.dim("\u2192 node9 shield enable <name> to activate"));
9747
+ console.log("");
9748
+ }
9620
9749
  }
9621
9750
  if (isInstalled) {
9622
9751
  console.log(import_chalk2.default.green(" \u2705 node9 is active \u2014 your future sessions are protected."));
@@ -9657,7 +9786,7 @@ function registerScanCommand(program2) {
9657
9786
  console.log("");
9658
9787
  if (!isTestingMode()) {
9659
9788
  if (isInstalled) {
9660
- const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/`;
9789
+ const url = `http://${DAEMON_HOST}:${DAEMON_PORT}/?openscan=1`;
9661
9790
  if (isDaemonRunning()) {
9662
9791
  const internalToken = getInternalToken();
9663
9792
  if (internalToken) {
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
 
@@ -9406,7 +9525,7 @@ function registerScanCommand(program2) {
9406
9525
  chalk2.cyan.bold("\u{1F50D} Scanning your AI history") + chalk2.dim(" \u2014 what would node9 have caught?")
9407
9526
  );
9408
9527
  console.log("");
9409
- const useTTY = process.stdout.isTTY === true;
9528
+ const useTTY = process.stdout.isTTY === true && process.env.NODE9_WRAPPER !== "1";
9410
9529
  if (!useTTY) {
9411
9530
  process.stdout.write(
9412
9531
  " " + chalk2.dim("Scanning your history \u2014 this may take a moment...\n")
@@ -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,
package/dist/index.mjs CHANGED
@@ -965,6 +965,17 @@ var ASSIGNMENT_CONTEXT_RE = /\b(?:password|passwd|secret|token|api[_-]?key|auth(
965
965
  function isAssignmentContext(text) {
966
966
  return ASSIGNMENT_CONTEXT_RE.test(text);
967
967
  }
968
+ function shannonEntropy(s) {
969
+ if (s.length === 0) return 0;
970
+ const freq = /* @__PURE__ */ new Map();
971
+ for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
972
+ let h = 0;
973
+ for (const count of freq.values()) {
974
+ const p = count / s.length;
975
+ h -= p * Math.log2(p);
976
+ }
977
+ return h;
978
+ }
968
979
  var DLP_STOPWORDS = [
969
980
  "example",
970
981
  "placeholder",
@@ -987,7 +998,9 @@ var DLP_STOPWORDS = [
987
998
  "%{",
988
999
  "<your",
989
1000
  "test_key",
990
- "test_token"
1001
+ "test_token",
1002
+ "your",
1003
+ "here"
991
1004
  ];
992
1005
  var DLP_PATTERNS = [
993
1006
  // ── AWS ───────────────────────────────────────────────────────────────────
@@ -1038,7 +1051,8 @@ var DLP_PATTERNS = [
1038
1051
  name: "OpenAI API Key",
1039
1052
  regex: /\bsk-[a-zA-Z0-9_-]{20,}\b/,
1040
1053
  severity: "block",
1041
- keywords: ["sk-"]
1054
+ keywords: ["sk-"],
1055
+ minEntropy: 3.5
1042
1056
  },
1043
1057
  // ── Stripe ────────────────────────────────────────────────────────────────
1044
1058
  {
@@ -1109,7 +1123,13 @@ var DLP_PATTERNS = [
1109
1123
  keywords: ["hvb."]
1110
1124
  },
1111
1125
  // ── Hugging Face ──────────────────────────────────────────────────────────
1112
- { name: "HuggingFace Token", regex: /\bhf_[A-Za-z]{34}\b/, severity: "block", keywords: ["hf_"] },
1126
+ {
1127
+ name: "HuggingFace Token",
1128
+ regex: /\bhf_[A-Za-z]{34}\b/,
1129
+ severity: "block",
1130
+ keywords: ["hf_"],
1131
+ minEntropy: 3
1132
+ },
1113
1133
  // ── Postman ───────────────────────────────────────────────────────────────
1114
1134
  {
1115
1135
  name: "Postman API Token",
@@ -1261,7 +1281,8 @@ var DLP_PATTERNS = [
1261
1281
  name: "PyPI Upload Token",
1262
1282
  regex: /\bpypi-[A-Za-z0-9_-]{50,}\b/,
1263
1283
  severity: "block",
1264
- keywords: ["pypi-"]
1284
+ keywords: ["pypi-"],
1285
+ minEntropy: 3
1265
1286
  },
1266
1287
  // ── Bearer Token ─────────────────────────────────────────────────────────
1267
1288
  // contextBoost: promoted to block when assigned (e.g. AUTH_TOKEN=Bearer eyJ...)
@@ -1270,7 +1291,99 @@ var DLP_PATTERNS = [
1270
1291
  regex: /Bearer\s+[a-zA-Z0-9\-._~+/]{20,}=*/i,
1271
1292
  severity: "review",
1272
1293
  keywords: ["bearer"],
1273
- contextBoost: true
1294
+ contextBoost: true,
1295
+ minEntropy: 3
1296
+ },
1297
+ // ── Resend ────────────────────────────────────────────────────────────────
1298
+ {
1299
+ name: "Resend API Key",
1300
+ regex: /\bre_[a-zA-Z0-9]{24}\b/,
1301
+ severity: "block",
1302
+ keywords: ["re_"]
1303
+ },
1304
+ // ── Telegram ──────────────────────────────────────────────────────────────
1305
+ {
1306
+ name: "Telegram Bot Token",
1307
+ regex: /\b[0-9]{7,10}:AA[a-zA-Z0-9_-]{33}\b/,
1308
+ severity: "block",
1309
+ keywords: [":aa"]
1310
+ },
1311
+ // ── Mapbox ────────────────────────────────────────────────────────────────
1312
+ {
1313
+ name: "Mapbox Access Token",
1314
+ regex: /\bpk\.eyJ1[a-zA-Z0-9._-]{20,}\b/,
1315
+ severity: "block",
1316
+ keywords: ["pk.eyj1"]
1317
+ },
1318
+ // ── Notion ────────────────────────────────────────────────────────────────
1319
+ {
1320
+ name: "Notion Integration Token",
1321
+ regex: /\bsecret_[a-zA-Z0-9]{43}\b/,
1322
+ severity: "block",
1323
+ keywords: ["secret_"]
1324
+ },
1325
+ // ── Square ────────────────────────────────────────────────────────────────
1326
+ {
1327
+ name: "Square Access Token",
1328
+ regex: /\bsq0atp-[0-9A-Za-z_-]{22}\b/,
1329
+ severity: "block",
1330
+ keywords: ["sq0atp-"]
1331
+ },
1332
+ {
1333
+ name: "Square OAuth Secret",
1334
+ regex: /\bsq0csp-[0-9A-Za-z_-]{43}\b/,
1335
+ severity: "block",
1336
+ keywords: ["sq0csp-"]
1337
+ },
1338
+ // ── Typeform ──────────────────────────────────────────────────────────────
1339
+ {
1340
+ name: "Typeform Token",
1341
+ regex: /\btfp_[a-zA-Z0-9_]{59}\b/,
1342
+ severity: "block",
1343
+ keywords: ["tfp_"]
1344
+ },
1345
+ // ── Cloudinary ────────────────────────────────────────────────────────────
1346
+ {
1347
+ name: "Cloudinary URL",
1348
+ regex: /\bcloudinary:\/\/[0-9]+:[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+/,
1349
+ severity: "block",
1350
+ keywords: ["cloudinary://"]
1351
+ },
1352
+ // ── Airtable ──────────────────────────────────────────────────────────────
1353
+ // New PAT format: pat + 14 alphanum + . + 64 alphanum
1354
+ {
1355
+ name: "Airtable PAT",
1356
+ regex: /\bpat[a-zA-Z0-9]{14}\.[a-zA-Z0-9]{64}\b/,
1357
+ severity: "block",
1358
+ keywords: ["pat"]
1359
+ },
1360
+ // ── RubyGems ──────────────────────────────────────────────────────────────
1361
+ {
1362
+ name: "RubyGems API Key",
1363
+ regex: /\brubygems_[a-f0-9]{48}\b/,
1364
+ severity: "block",
1365
+ keywords: ["rubygems_"]
1366
+ },
1367
+ // ── Shippo ────────────────────────────────────────────────────────────────
1368
+ {
1369
+ name: "Shippo Token",
1370
+ regex: /\bshippo_(?:live|test)_[a-f0-9]{40}\b/,
1371
+ severity: "block",
1372
+ keywords: ["shippo_"]
1373
+ },
1374
+ // ── Plaid ─────────────────────────────────────────────────────────────────
1375
+ {
1376
+ name: "Plaid Access Token",
1377
+ 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/,
1378
+ severity: "block",
1379
+ keywords: ["access-sandbox", "access-development", "access-production"]
1380
+ },
1381
+ // ── Age ───────────────────────────────────────────────────────────────────
1382
+ {
1383
+ name: "Age Identity Key",
1384
+ regex: /\bAGE-SECRET-KEY-1[QPZRY9X8GF2TVDW0S3JNLH]{58}\b/,
1385
+ severity: "block",
1386
+ keywords: ["age-secret-key-"]
1274
1387
  }
1275
1388
  ];
1276
1389
  var SENSITIVE_PATH_PATTERNS = [
@@ -1367,8 +1480,9 @@ function scanArgs(args, depth = 0, fieldPath = "args") {
1367
1480
  continue;
1368
1481
  }
1369
1482
  if (pattern.regex.test(text)) {
1370
- const matchedValue = (text.match(pattern.regex)?.[0] ?? "").toLowerCase();
1371
- if (DLP_STOPWORDS.some((sw) => matchedValue.includes(sw))) continue;
1483
+ const raw = text.match(pattern.regex)?.[0] ?? "";
1484
+ if (DLP_STOPWORDS.some((sw) => raw.toLowerCase().includes(sw))) continue;
1485
+ if (pattern.minEntropy !== void 0 && shannonEntropy(raw) < pattern.minEntropy) continue;
1372
1486
  const severity = pattern.contextBoost && assignmentCtx ? "block" : pattern.severity;
1373
1487
  return {
1374
1488
  patternName: pattern.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node9/proxy",
3
- "version": "1.12.5",
3
+ "version": "1.12.7",
4
4
  "description": "The Sudo Command for AI Agents. Execution Security for Claude Code & MCP.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",