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.js
CHANGED
|
@@ -626,6 +626,17 @@ function isInsideFixMessage(content, matchIndex) {
|
|
|
626
626
|
const lineText = content.substring(lineStart, content.indexOf("\n", matchIndex));
|
|
627
627
|
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);
|
|
628
628
|
}
|
|
629
|
+
function isInlineSilenced(content, matchIndex, ruleId) {
|
|
630
|
+
const lines = content.split("\n");
|
|
631
|
+
const matchLineNum = content.substring(0, matchIndex).split("\n").length - 1;
|
|
632
|
+
const matchLine = lines[matchLineNum] ?? "";
|
|
633
|
+
const prevLine = matchLineNum > 0 ? lines[matchLineNum - 1] ?? "" : "";
|
|
634
|
+
const marker = new RegExp(
|
|
635
|
+
`(?://|/\\*|#)\\s*(?:${ruleId}|scanner)-OK\\b`,
|
|
636
|
+
"i"
|
|
637
|
+
);
|
|
638
|
+
return marker.test(matchLine) || marker.test(prevLine);
|
|
639
|
+
}
|
|
629
640
|
function findMatches(content, pattern, rule, filePath, fixTemplate) {
|
|
630
641
|
const matches = [];
|
|
631
642
|
const lines = content.split("\n");
|
|
@@ -754,6 +765,16 @@ var hardcodedSecrets = {
|
|
|
754
765
|
const secretMatch = lineText.match(/[:=]\s*["'`]([^"'`]*)["'`]/);
|
|
755
766
|
if (secretMatch && secretMatch[1].length < floor) continue;
|
|
756
767
|
}
|
|
768
|
+
if (pi === 6) {
|
|
769
|
+
const valMatch = lineText.match(/[:=]\s*["'`]([^"'`]+)["'`]/);
|
|
770
|
+
const nameMatch = lineText.match(/\b([A-Z][A-Z0-9_]*_(?:SECRET|TOKEN|KEY|PASSWORD|PASSWD))\b/);
|
|
771
|
+
if (valMatch && nameMatch) {
|
|
772
|
+
const value = valMatch[1];
|
|
773
|
+
const isKeySuffix = nameMatch[1].endsWith("_KEY");
|
|
774
|
+
const isKebabIdentifier = value.length < 40 && /^[a-z0-9]+(-[a-z0-9]+)+$/.test(value);
|
|
775
|
+
if (isKeySuffix && isKebabIdentifier) continue;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
757
778
|
matches.push(rm);
|
|
758
779
|
}
|
|
759
780
|
}
|
|
@@ -947,7 +968,7 @@ var sqlInjection = {
|
|
|
947
968
|
/(?:SELECT|INSERT|UPDATE|DELETE|WHERE)\s+.*\$\{(?!.*parameterized)/gi
|
|
948
969
|
];
|
|
949
970
|
const matches = [];
|
|
950
|
-
const usesParams = /\?\s
|
|
971
|
+
const usesParams = /\?\s*,|\?[^?,;]{1,20}\?|\$\d+|:[\w]+|\bprepare\b|\bplaceholders?\b/i.test(content);
|
|
951
972
|
if (usesParams) return [];
|
|
952
973
|
for (const pattern of patterns) {
|
|
953
974
|
const raw = findMatches(
|
|
@@ -3038,6 +3059,7 @@ var dangerousInnerHTML = {
|
|
|
3038
3059
|
let m;
|
|
3039
3060
|
while ((m = re.exec(content)) !== null) {
|
|
3040
3061
|
if (isCommentLine(content, m.index)) continue;
|
|
3062
|
+
if (isInlineSilenced(content, m.index, "VC063")) continue;
|
|
3041
3063
|
const value = m[1].trim();
|
|
3042
3064
|
if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
|
|
3043
3065
|
if (/^["'`][^$]*["'`]$/.test(value)) continue;
|
|
@@ -3051,7 +3073,7 @@ var dangerousInnerHTML = {
|
|
|
3051
3073
|
file: filePath,
|
|
3052
3074
|
line: lineNum,
|
|
3053
3075
|
snippet: getSnippet(content, lineNum),
|
|
3054
|
-
fix: "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify"
|
|
3076
|
+
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."
|
|
3055
3077
|
});
|
|
3056
3078
|
}
|
|
3057
3079
|
return findings;
|
|
@@ -4343,7 +4365,12 @@ var complianceMap = {
|
|
|
4343
4365
|
var consoleLogProduction = {
|
|
4344
4366
|
id: "VC097",
|
|
4345
4367
|
title: "Console.log Left in Production Code",
|
|
4346
|
-
|
|
4368
|
+
// Demoted from "low" to "info" 2026-05-11. This is a code-hygiene
|
|
4369
|
+
// signal (leaked debug logs, occasionally PII), not a security
|
|
4370
|
+
// vulnerability in the OWASP sense. Was inflating severity counts
|
|
4371
|
+
// on real codebases (11+ hits on vibecheck's own scan), drowning
|
|
4372
|
+
// the actual security signal.
|
|
4373
|
+
severity: "info",
|
|
4347
4374
|
category: "Performance",
|
|
4348
4375
|
description: "console.log statements left in production code can leak sensitive data, slow down rendering, and clutter browser consoles.",
|
|
4349
4376
|
check(content, filePath) {
|
|
@@ -4366,7 +4393,11 @@ var consoleLogProduction = {
|
|
|
4366
4393
|
var syncFileOps = {
|
|
4367
4394
|
id: "VC098",
|
|
4368
4395
|
title: "Synchronous File Operations",
|
|
4369
|
-
|
|
4396
|
+
// Demoted from "medium" to "info" 2026-05-11. Already a Performance-
|
|
4397
|
+
// category rule (see below) — it's a perf concern, not a security
|
|
4398
|
+
// one, so it shouldn't have been at "medium" alongside actual
|
|
4399
|
+
// security findings. The severity scale should reflect risk class.
|
|
4400
|
+
severity: "info",
|
|
4370
4401
|
category: "Performance",
|
|
4371
4402
|
description: "Synchronous file operations (readFileSync, writeFileSync) block the event loop, causing all other requests to wait.",
|
|
4372
4403
|
check(content, filePath) {
|
|
@@ -4403,7 +4434,9 @@ var eventListenerLeak = {
|
|
|
4403
4434
|
var nPlusOneQuery = {
|
|
4404
4435
|
id: "VC100",
|
|
4405
4436
|
title: "N+1 Query Pattern Detected",
|
|
4406
|
-
|
|
4437
|
+
// Demoted from "medium" to "info" 2026-05-11. Performance pattern,
|
|
4438
|
+
// not a security issue — same rationale as VC098.
|
|
4439
|
+
severity: "info",
|
|
4407
4440
|
category: "Performance",
|
|
4408
4441
|
description: "Database queries inside loops cause N+1 performance problems \u2014 one query per iteration instead of a single batch query.",
|
|
4409
4442
|
check(content, filePath) {
|
|
@@ -4488,7 +4521,11 @@ var todoLeftInCode = {
|
|
|
4488
4521
|
var emptyCatchBlock = {
|
|
4489
4522
|
id: "VC104",
|
|
4490
4523
|
title: "Empty Catch Block",
|
|
4491
|
-
|
|
4524
|
+
// Demoted from "medium" to "info" 2026-05-11. Already a Code-Quality
|
|
4525
|
+
// category — empty catch blocks are a maintainability concern, not a
|
|
4526
|
+
// security vulnerability. Worth flagging, not worth counting as a
|
|
4527
|
+
// security "medium" alongside actual SQL-injection / XSS findings.
|
|
4528
|
+
severity: "info",
|
|
4492
4529
|
category: "Code Quality",
|
|
4493
4530
|
description: "Empty catch blocks silently swallow errors, making bugs impossible to diagnose. At minimum, log the error.",
|
|
4494
4531
|
check(content, filePath) {
|
|
@@ -4522,7 +4559,11 @@ var callbackHell = {
|
|
|
4522
4559
|
var magicNumbers = {
|
|
4523
4560
|
id: "VC106",
|
|
4524
4561
|
title: "Magic Numbers in Code",
|
|
4525
|
-
|
|
4562
|
+
// Demoted from "low" to "info" 2026-05-11. Already a Code-Quality
|
|
4563
|
+
// category — magic numbers are a style/readability concern, not a
|
|
4564
|
+
// security vulnerability. Was the single noisiest rule on the
|
|
4565
|
+
// vibecheck self-scan (44 hits) drowning real security signal.
|
|
4566
|
+
severity: "info",
|
|
4526
4567
|
category: "Code Quality",
|
|
4527
4568
|
description: "Unnamed numeric constants in conditions or calculations make code hard to understand. Extract them into named constants.",
|
|
4528
4569
|
check(content, filePath) {
|
|
@@ -7034,6 +7075,7 @@ var secretInURLParam = {
|
|
|
7034
7075
|
while ((m = re.exec(content)) !== null) {
|
|
7035
7076
|
if (isCommentLine(content, m.index)) continue;
|
|
7036
7077
|
if (isInsideFixMessage(content, m.index)) continue;
|
|
7078
|
+
if (isInlineSilenced(content, m.index, "VC146")) continue;
|
|
7037
7079
|
const lineNum = content.substring(0, m.index).split("\n").length;
|
|
7038
7080
|
findings.push({
|
|
7039
7081
|
rule: "VC146",
|
|
@@ -7043,7 +7085,7 @@ var secretInURLParam = {
|
|
|
7043
7085
|
file: filePath,
|
|
7044
7086
|
line: lineNum,
|
|
7045
7087
|
snippet: getSnippet(content, lineNum),
|
|
7046
|
-
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."
|
|
7088
|
+
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."
|
|
7047
7089
|
});
|
|
7048
7090
|
}
|
|
7049
7091
|
}
|
|
@@ -7234,10 +7276,14 @@ var webhookSignatureVerification = {
|
|
|
7234
7276
|
check(content, filePath) {
|
|
7235
7277
|
if (!filePath.match(/\.(js|ts|jsx|tsx)$/)) return [];
|
|
7236
7278
|
if (isTestFile(filePath)) return [];
|
|
7237
|
-
if (
|
|
7279
|
+
if (!/webhook/i.test(filePath)) return [];
|
|
7238
7280
|
if (!/export\s+(?:async\s+)?function\s+POST/i.test(content)) return [];
|
|
7239
7281
|
const services = [
|
|
7240
|
-
|
|
7282
|
+
// Tightened Clerk events: require the specific subevent suffix
|
|
7283
|
+
// rather than bare `session.`. The bare-prefix version matched
|
|
7284
|
+
// Stripe Checkout `session.url`, which has nothing to do with
|
|
7285
|
+
// Clerk webhooks.
|
|
7286
|
+
{ 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 },
|
|
7241
7287
|
{ name: "GitHub", content: /github/i, events: /push|pull_request|issues|installation/i, verify: /createHmac|x-hub-signature|verify.*signature|GITHUB_WEBHOOK_SECRET/i },
|
|
7242
7288
|
{ name: "Resend", content: /resend/i, events: /email\.sent|email\.delivered|email\.bounced/i, verify: /svix|webhook.*verify|x-webhook-signature|RESEND_WEBHOOK_SECRET/i },
|
|
7243
7289
|
{ name: "SendGrid", content: /sendgrid/i, events: /inbound.*parse|event.*webhook/i, verify: /EventWebhook|x-twilio-email|verifySignature|SENDGRID_WEBHOOK_VERIFICATION/i },
|