@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 +181 -52
- package/dist/cli.mjs +181 -52
- package/dist/index.js +121 -7
- package/dist/index.mjs +121 -7
- package/package.json +1 -1
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
|
|
1166
|
-
if (DLP_STOPWORDS.some((sw) =>
|
|
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
|
|
1199
|
-
if (DLP_STOPWORDS.some((sw) =>
|
|
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
|
-
{
|
|
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 (
|
|
9630
|
+
if (scan.totalCostUSD > 0) {
|
|
9512
9631
|
console.log(
|
|
9513
|
-
" " + import_chalk2.default.
|
|
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 (
|
|
9640
|
+
if (blockedCount > 0) {
|
|
9527
9641
|
console.log(
|
|
9528
|
-
" " + import_chalk2.default.
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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)}
|
|
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
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
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
|
-
|
|
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
|
|
1146
|
-
if (DLP_STOPWORDS.some((sw) =>
|
|
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
|
|
1179
|
-
if (DLP_STOPWORDS.some((sw) =>
|
|
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
|
-
{
|
|
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 (
|
|
9610
|
+
if (scan.totalCostUSD > 0) {
|
|
9492
9611
|
console.log(
|
|
9493
|
-
" " + chalk2.
|
|
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 (
|
|
9620
|
+
if (blockedCount > 0) {
|
|
9507
9621
|
console.log(
|
|
9508
|
-
" " + chalk2.
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
"
|
|
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)}
|
|
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
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
|
1401
|
-
if (DLP_STOPWORDS.some((sw) =>
|
|
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
|
-
{
|
|
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
|
|
1371
|
-
if (DLP_STOPWORDS.some((sw) =>
|
|
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,
|