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 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*,|\$\d+|:[\w]+|\bprepare\b|\bplaceholder\b/i.test(content);
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
- severity: "low",
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
- severity: "medium",
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
- severity: "medium",
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
- severity: "medium",
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
- severity: "low",
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 (!/\/api\/|\/webhook/i.test(filePath)) return [];
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
- { name: "Clerk", content: /clerk/i, events: /user\.created|user\.updated|user\.deleted|session\./i, verify: /svix|Webhook\(\)\.verify|webhook-id|wh_secret/i },
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 },