xploitscan-shared-rules 1.6.2 → 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;
@@ -7053,6 +7075,7 @@ var secretInURLParam = {
7053
7075
  while ((m = re.exec(content)) !== null) {
7054
7076
  if (isCommentLine(content, m.index)) continue;
7055
7077
  if (isInsideFixMessage(content, m.index)) continue;
7078
+ if (isInlineSilenced(content, m.index, "VC146")) continue;
7056
7079
  const lineNum = content.substring(0, m.index).split("\n").length;
7057
7080
  findings.push({
7058
7081
  rule: "VC146",
@@ -7062,7 +7085,7 @@ var secretInURLParam = {
7062
7085
  file: filePath,
7063
7086
  line: lineNum,
7064
7087
  snippet: getSnippet(content, lineNum),
7065
- 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."
7066
7089
  });
7067
7090
  }
7068
7091
  }
@@ -7253,10 +7276,14 @@ var webhookSignatureVerification = {
7253
7276
  check(content, filePath) {
7254
7277
  if (!filePath.match(/\.(js|ts|jsx|tsx)$/)) return [];
7255
7278
  if (isTestFile(filePath)) return [];
7256
- if (!/\/api\/|\/webhook/i.test(filePath)) return [];
7279
+ if (!/webhook/i.test(filePath)) return [];
7257
7280
  if (!/export\s+(?:async\s+)?function\s+POST/i.test(content)) return [];
7258
7281
  const services = [
7259
- { 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 },
7260
7287
  { name: "GitHub", content: /github/i, events: /push|pull_request|issues|installation/i, verify: /createHmac|x-hub-signature|verify.*signature|GITHUB_WEBHOOK_SECRET/i },
7261
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 },
7262
7289
  { name: "SendGrid", content: /sendgrid/i, events: /inbound.*parse|event.*webhook/i, verify: /EventWebhook|x-twilio-email|verifySignature|SENDGRID_WEBHOOK_VERIFICATION/i },