xploitscan-shared-rules 1.6.1 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +56 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +56 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -887,6 +887,17 @@ function isInsideFixMessage(content, matchIndex) {
|
|
|
887
887
|
const lineText = content.substring(lineStart, content.indexOf("\n", matchIndex));
|
|
888
888
|
return /(?:fix|description|message|suggestion|hint|help|example|doc|comment)\s*[:=(]/i.test(lineText) || /return\s*["'`].*\b(?:Use|Replace|Add|Move|Set|Enable|Disable|Never|Don't|Do not|Instead)\b/i.test(lineText);
|
|
889
889
|
}
|
|
890
|
+
function isInlineSilenced(content, matchIndex, ruleId) {
|
|
891
|
+
const lines = content.split("\n");
|
|
892
|
+
const matchLineNum = content.substring(0, matchIndex).split("\n").length - 1;
|
|
893
|
+
const matchLine = lines[matchLineNum] ?? "";
|
|
894
|
+
const prevLine = matchLineNum > 0 ? lines[matchLineNum - 1] ?? "" : "";
|
|
895
|
+
const marker = new RegExp(
|
|
896
|
+
`(?://|/\\*|#)\\s*(?:${ruleId}|scanner)-OK\\b`,
|
|
897
|
+
"i"
|
|
898
|
+
);
|
|
899
|
+
return marker.test(matchLine) || marker.test(prevLine);
|
|
900
|
+
}
|
|
890
901
|
function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
891
902
|
const matches = [];
|
|
892
903
|
const lines = content.split("\n");
|
|
@@ -1015,6 +1026,16 @@ var hardcodedSecrets = {
|
|
|
1015
1026
|
const secretMatch = lineText.match(/[:=]\s*["'`]([^"'`]*)["'`]/);
|
|
1016
1027
|
if (secretMatch && secretMatch[1].length < floor) continue;
|
|
1017
1028
|
}
|
|
1029
|
+
if (pi === 6) {
|
|
1030
|
+
const valMatch = lineText.match(/[:=]\s*["'`]([^"'`]+)["'`]/);
|
|
1031
|
+
const nameMatch = lineText.match(/\b([A-Z][A-Z0-9_]*_(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD))\b/);
|
|
1032
|
+
if (valMatch && nameMatch) {
|
|
1033
|
+
const value = valMatch[1];
|
|
1034
|
+
const isKeySuffix = nameMatch[1].endsWith("_KEY");
|
|
1035
|
+
const isKebabIdentifier = value.length < 40 && /^[a-z0-9]+(-[a-z0-9]+)+$/.test(value);
|
|
1036
|
+
if (isKeySuffix && isKebabIdentifier) continue;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1018
1039
|
matches.push(rm);
|
|
1019
1040
|
}
|
|
1020
1041
|
}
|
|
@@ -1208,7 +1229,7 @@ var sqlInjection = {
|
|
|
1208
1229
|
/(?:SELECT|INSERT|UPDATE|DELETE|WHERE)\s+.*\$\{(?!.*parameterized)/gi
|
|
1209
1230
|
];
|
|
1210
1231
|
const matches = [];
|
|
1211
|
-
const usesParams = /\?\s
|
|
1232
|
+
const usesParams = /\?\s*,|\?[^?,;]{1,20}\?|\$\d+|:[\w]+|\bprepare\b|\bplaceholders?\b/i.test(content);
|
|
1212
1233
|
if (usesParams) return [];
|
|
1213
1234
|
for (const pattern of patterns) {
|
|
1214
1235
|
const raw = findMatches(
|
|
@@ -3299,6 +3320,7 @@ var dangerousInnerHTML = {
|
|
|
3299
3320
|
let m;
|
|
3300
3321
|
while ((m = re.exec(content)) !== null) {
|
|
3301
3322
|
if (isCommentLine(content, m.index)) continue;
|
|
3323
|
+
if (isInlineSilenced(content, m.index, "VC063")) continue;
|
|
3302
3324
|
const value = m[1].trim();
|
|
3303
3325
|
if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
|
|
3304
3326
|
if (/^["'`][^$]*["'`]$/.test(value)) continue;
|
|
@@ -3312,7 +3334,7 @@ var dangerousInnerHTML = {
|
|
|
3312
3334
|
file: filePath,
|
|
3313
3335
|
line: lineNum,
|
|
3314
3336
|
snippet: getSnippet(content, lineNum),
|
|
3315
|
-
fix: "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify"
|
|
3337
|
+
fix: "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify. If this site is intentional (server-built template, nonce-attribute boot script, etc.), add an inline `// VC063-OK: <reason>` comment above the line to silence."
|
|
3316
3338
|
});
|
|
3317
3339
|
}
|
|
3318
3340
|
return findings;
|
|
@@ -4604,7 +4626,12 @@ var complianceMap = {
|
|
|
4604
4626
|
var consoleLogProduction = {
|
|
4605
4627
|
id: "VC097",
|
|
4606
4628
|
title: "Console.log Left in Production Code",
|
|
4607
|
-
|
|
4629
|
+
// Demoted from "low" to "info" 2026-05-11. This is a code-hygiene
|
|
4630
|
+
// signal (leaked debug logs, occasionally PII), not a security
|
|
4631
|
+
// vulnerability in the OWASP sense. Was inflating severity counts
|
|
4632
|
+
// on real codebases (11+ hits on vibecheck's own scan), drowning
|
|
4633
|
+
// the actual security signal.
|
|
4634
|
+
severity: "info",
|
|
4608
4635
|
category: "Performance",
|
|
4609
4636
|
description: "console.log statements left in production code can leak sensitive data, slow down rendering, and clutter browser consoles.",
|
|
4610
4637
|
check(content, filePath) {
|
|
@@ -4627,7 +4654,11 @@ var consoleLogProduction = {
|
|
|
4627
4654
|
var syncFileOps = {
|
|
4628
4655
|
id: "VC098",
|
|
4629
4656
|
title: "Synchronous File Operations",
|
|
4630
|
-
|
|
4657
|
+
// Demoted from "medium" to "info" 2026-05-11. Already a Performance-
|
|
4658
|
+
// category rule (see below) — it's a perf concern, not a security
|
|
4659
|
+
// one, so it shouldn't have been at "medium" alongside actual
|
|
4660
|
+
// security findings. The severity scale should reflect risk class.
|
|
4661
|
+
severity: "info",
|
|
4631
4662
|
category: "Performance",
|
|
4632
4663
|
description: "Synchronous file operations (readFileSync, writeFileSync) block the event loop, causing all other requests to wait.",
|
|
4633
4664
|
check(content, filePath) {
|
|
@@ -4664,7 +4695,9 @@ var eventListenerLeak = {
|
|
|
4664
4695
|
var nPlusOneQuery = {
|
|
4665
4696
|
id: "VC100",
|
|
4666
4697
|
title: "N+1 Query Pattern Detected",
|
|
4667
|
-
|
|
4698
|
+
// Demoted from "medium" to "info" 2026-05-11. Performance pattern,
|
|
4699
|
+
// not a security issue — same rationale as VC098.
|
|
4700
|
+
severity: "info",
|
|
4668
4701
|
category: "Performance",
|
|
4669
4702
|
description: "Database queries inside loops cause N+1 performance problems \u2014 one query per iteration instead of a single batch query.",
|
|
4670
4703
|
check(content, filePath) {
|
|
@@ -4749,7 +4782,11 @@ var todoLeftInCode = {
|
|
|
4749
4782
|
var emptyCatchBlock = {
|
|
4750
4783
|
id: "VC104",
|
|
4751
4784
|
title: "Empty Catch Block",
|
|
4752
|
-
|
|
4785
|
+
// Demoted from "medium" to "info" 2026-05-11. Already a Code-Quality
|
|
4786
|
+
// category — empty catch blocks are a maintainability concern, not a
|
|
4787
|
+
// security vulnerability. Worth flagging, not worth counting as a
|
|
4788
|
+
// security "medium" alongside actual SQL-injection / XSS findings.
|
|
4789
|
+
severity: "info",
|
|
4753
4790
|
category: "Code Quality",
|
|
4754
4791
|
description: "Empty catch blocks silently swallow errors, making bugs impossible to diagnose. At minimum, log the error.",
|
|
4755
4792
|
check(content, filePath) {
|
|
@@ -4783,7 +4820,11 @@ var callbackHell = {
|
|
|
4783
4820
|
var magicNumbers = {
|
|
4784
4821
|
id: "VC106",
|
|
4785
4822
|
title: "Magic Numbers in Code",
|
|
4786
|
-
|
|
4823
|
+
// Demoted from "low" to "info" 2026-05-11. Already a Code-Quality
|
|
4824
|
+
// category — magic numbers are a style/readability concern, not a
|
|
4825
|
+
// security vulnerability. Was the single noisiest rule on the
|
|
4826
|
+
// vibecheck self-scan (44 hits) drowning real security signal.
|
|
4827
|
+
severity: "info",
|
|
4787
4828
|
category: "Code Quality",
|
|
4788
4829
|
description: "Unnamed numeric constants in conditions or calculations make code hard to understand. Extract them into named constants.",
|
|
4789
4830
|
check(content, filePath) {
|
|
@@ -7295,6 +7336,7 @@ var secretInURLParam = {
|
|
|
7295
7336
|
while ((m = re.exec(content)) !== null) {
|
|
7296
7337
|
if (isCommentLine(content, m.index)) continue;
|
|
7297
7338
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7339
|
+
if (isInlineSilenced(content, m.index, "VC146")) continue;
|
|
7298
7340
|
const lineNum = content.substring(0, m.index).split("\n").length;
|
|
7299
7341
|
findings.push({
|
|
7300
7342
|
rule: "VC146",
|
|
@@ -7304,7 +7346,7 @@ var secretInURLParam = {
|
|
|
7304
7346
|
file: filePath,
|
|
7305
7347
|
line: lineNum,
|
|
7306
7348
|
snippet: getSnippet(content, lineNum),
|
|
7307
|
-
fix: "Pass secrets in the Authorization header (Bearer token) or request body, never in URL parameters. URL parameters are logged in server access logs, browser history, and referrer headers."
|
|
7349
|
+
fix: "Pass secrets in the Authorization header (Bearer token) or request body, never in URL parameters. URL parameters are logged in server access logs, browser history, and referrer headers. If this finding is legitimate (magic-link / claim-token flow), add an inline `// VC146-OK: <reason>` comment above the line to silence it."
|
|
7308
7350
|
});
|
|
7309
7351
|
}
|
|
7310
7352
|
}
|
|
@@ -7495,10 +7537,14 @@ var webhookSignatureVerification = {
|
|
|
7495
7537
|
check(content, filePath) {
|
|
7496
7538
|
if (!filePath.match(/\.(js|ts|jsx|tsx)$/)) return [];
|
|
7497
7539
|
if (isTestFile(filePath)) return [];
|
|
7498
|
-
if (
|
|
7540
|
+
if (!/webhook/i.test(filePath)) return [];
|
|
7499
7541
|
if (!/export\s+(?:async\s+)?function\s+POST/i.test(content)) return [];
|
|
7500
7542
|
const services = [
|
|
7501
|
-
|
|
7543
|
+
// Tightened Clerk events: require the specific subevent suffix
|
|
7544
|
+
// rather than bare `session.`. The bare-prefix version matched
|
|
7545
|
+
// Stripe Checkout `session.url`, which has nothing to do with
|
|
7546
|
+
// Clerk webhooks.
|
|
7547
|
+
{ name: "Clerk", content: /clerk/i, events: /user\.(created|updated|deleted)|session\.(created|removed|ended|revoked)/i, verify: /svix|Webhook\(\)\.verify|webhook-id|wh_secret/i },
|
|
7502
7548
|
{ name: "GitHub", content: /github/i, events: /push|pull_request|issues|installation/i, verify: /createHmac|x-hub-signature|verify.*signature|GITHUB_WEBHOOK_SECRET/i },
|
|
7503
7549
|
{ name: "Resend", content: /resend/i, events: /email\.sent|email\.delivered|email\.bounced/i, verify: /svix|webhook.*verify|x-webhook-signature|RESEND_WEBHOOK_SECRET/i },
|
|
7504
7550
|
{ name: "SendGrid", content: /sendgrid/i, events: /inbound.*parse|event.*webhook/i, verify: /EventWebhook|x-twilio-email|verifySignature|SENDGRID_WEBHOOK_VERIFICATION/i },
|