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.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;
@@ -7314,6 +7336,7 @@ var secretInURLParam = {
7314
7336
  while ((m = re.exec(content)) !== null) {
7315
7337
  if (isCommentLine(content, m.index)) continue;
7316
7338
  if (isInsideFixMessage(content, m.index)) continue;
7339
+ if (isInlineSilenced(content, m.index, "VC146")) continue;
7317
7340
  const lineNum = content.substring(0, m.index).split("\n").length;
7318
7341
  findings.push({
7319
7342
  rule: "VC146",
@@ -7323,7 +7346,7 @@ var secretInURLParam = {
7323
7346
  file: filePath,
7324
7347
  line: lineNum,
7325
7348
  snippet: getSnippet(content, lineNum),
7326
- 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."
7327
7350
  });
7328
7351
  }
7329
7352
  }
@@ -7514,10 +7537,14 @@ var webhookSignatureVerification = {
7514
7537
  check(content, filePath) {
7515
7538
  if (!filePath.match(/\.(js|ts|jsx|tsx)$/)) return [];
7516
7539
  if (isTestFile(filePath)) return [];
7517
- if (!/\/api\/|\/webhook/i.test(filePath)) return [];
7540
+ if (!/webhook/i.test(filePath)) return [];
7518
7541
  if (!/export\s+(?:async\s+)?function\s+POST/i.test(content)) return [];
7519
7542
  const services = [
7520
- { 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 },
7521
7548
  { name: "GitHub", content: /github/i, events: /push|pull_request|issues|installation/i, verify: /createHmac|x-hub-signature|verify.*signature|GITHUB_WEBHOOK_SECRET/i },
7522
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 },
7523
7550
  { name: "SendGrid", content: /sendgrid/i, events: /inbound.*parse|event.*webhook/i, verify: /EventWebhook|x-twilio-email|verifySignature|SENDGRID_WEBHOOK_VERIFICATION/i },