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