xploitscan 1.0.8 → 1.0.9

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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  **Security scanner for AI-generated code.** Find vulnerabilities before attackers do.
7
7
 
8
- Built for developers shipping code via Cursor, Lovable, Bolt, Replit, and Claude Code. 131 security rules. Plain-English results. Copy-paste fixes.
8
+ Built for developers shipping code via Cursor, Lovable, Bolt, Replit, and Claude Code. 151 security rules. Plain-English results. Copy-paste fixes.
9
9
 
10
10
  ## Quick Start
11
11
 
@@ -17,7 +17,7 @@ No install, no config, no account required. Your code stays 100% local.
17
17
 
18
18
  ## What It Catches
19
19
 
20
- 131 rules across 15+ categories:
20
+ 151 rules across 15+ categories:
21
21
 
22
22
  | Category | Examples | Rules |
23
23
  |----------|---------|-------|
@@ -130,7 +130,7 @@ Scan via the web at [xploitscan.com](https://xploitscan.com):
130
130
  - SOC2/ISO27001 compliance mapping
131
131
  - Slack and Discord webhook notifications
132
132
 
133
- **Free**: 5 scans/day, 30 core rules. **Pro** ($29/mo): unlimited scans, all 131 rules, and all dashboard features. **Team** ($99/mo): everything in Pro plus 5 seats, shared scan history, RBAC, and portfolio reports. Annual plans save 20%.
133
+ **Free**: 5 scans/day, 30 core rules. **Pro** ($29/mo): unlimited scans, all 151 rules, and all dashboard features. **Team** ($99/mo): everything in Pro plus 5 seats, shared scan history, RBAC, and portfolio reports. Annual plans save 20%.
134
134
 
135
135
  ## Supported Languages
136
136
 
package/dist/index.js CHANGED
@@ -398,7 +398,12 @@ var missingAuthMiddleware = {
398
398
  // Next.js API routes
399
399
  /export\s+(?:async\s+)?function\s+(?:GET|POST|PUT|PATCH|DELETE)\s*\(/gi
400
400
  ];
401
- if (filePath.includes("test") || filePath.includes("spec") || filePath.includes("mock")) return [];
401
+ if (isTestFile(filePath)) return [];
402
+ if (filePath.match(/\.(md|txt|rst|html|css|json|yaml|yml)$/)) return [];
403
+ const isAuthRoute = /\/auth\/|\/login|\/signup|\/register|\/logout|\/password\/|\/forgot|\/reset/i.test(filePath);
404
+ if (isAuthRoute) return [];
405
+ const isWebhookRoute = /\/webhook/i.test(filePath);
406
+ if (isWebhookRoute) return [];
402
407
  const authPatterns = [
403
408
  /auth/i,
404
409
  /session/i,
@@ -409,6 +414,8 @@ var missingAuthMiddleware = {
409
414
  /currentUser/i,
410
415
  /isAuthenticated/i,
411
416
  /requireAuth/i,
417
+ /requireUser/i,
418
+ /requireUserForApi/i,
412
419
  /clerk/i,
413
420
  /supabase\.auth/i,
414
421
  /getServerSession/i,
@@ -420,7 +427,10 @@ var missingAuthMiddleware = {
420
427
  /withAuth/i,
421
428
  /passport/i,
422
429
  /firebase\.auth/i,
423
- /cognito/i
430
+ /cognito/i,
431
+ /verifyCronSecret/i,
432
+ /verifySecret/i,
433
+ /checkApiKey/i
424
434
  ];
425
435
  const hasAuth = authPatterns.some((p) => p.test(content));
426
436
  if (hasAuth) return [];
@@ -482,18 +492,33 @@ var stripeWebhookUnprotected = {
482
492
  category: "Payment Security",
483
493
  description: "Stripe webhook endpoints without signature verification allow attackers to fake payment events.",
484
494
  check(content, filePath) {
485
- if (!/stripe|webhook/i.test(content)) return [];
486
- const hasWebhookRoute = /webhook/i.test(filePath) || /(?:post|handler).*webhook/i.test(content);
487
- if (!hasWebhookRoute) return [];
488
- const hasVerification = /constructEvent|verifyHeader|stripe-signature|webhook_secret/i.test(content);
489
- if (hasVerification) return [];
490
- return findMatches(
491
- content,
492
- /webhook/gi,
493
- stripeWebhookUnprotected,
494
- filePath,
495
- () => "Verify the Stripe webhook signature using stripe.webhooks.constructEvent(body, sig, webhookSecret) to prevent forged payment events."
496
- );
495
+ if (!filePath.match(/\.(js|ts|jsx|tsx|py|rb|go|java|php)$/)) return [];
496
+ if (isTestFile(filePath)) return [];
497
+ if (!/stripe/i.test(content)) return [];
498
+ const hasStripeWebhookHandler = /(?:stripe.*webhook|webhook.*stripe)/i.test(content) || /(?:checkout\.session\.completed|invoice\.paid|payment_intent|customer\.subscription)/i.test(content);
499
+ if (!hasStripeWebhookHandler) return [];
500
+ if (/constructEvent|verifyHeader|stripe[_-]?signature|webhook[_-]?secret|STRIPE_WEBHOOK_SECRET/i.test(content)) return [];
501
+ const handlerPatterns = [
502
+ // POST handler that processes Stripe events
503
+ /export\s+(?:async\s+)?function\s+POST\s*\(/g,
504
+ // Express-style Stripe webhook route
505
+ /\.(post|all)\s*\(\s*["'`][^"'`]*(?:stripe|webhook)[^"'`]*["'`]/gi,
506
+ // Event type checking without prior verification
507
+ /(?:event\.type|req\.body\.type)\s*===?\s*["'`](?:checkout|invoice|payment|customer)\./g
508
+ ];
509
+ const matches = [];
510
+ for (const pattern of handlerPatterns) {
511
+ matches.push(
512
+ ...findMatches(
513
+ content,
514
+ pattern,
515
+ stripeWebhookUnprotected,
516
+ filePath,
517
+ () => "Verify the Stripe webhook signature using stripe.webhooks.constructEvent(body, sig, webhookSecret) to prevent forged payment events."
518
+ )
519
+ );
520
+ }
521
+ return matches;
497
522
  }
498
523
  };
499
524
  var sqlInjection = {
@@ -1119,16 +1144,31 @@ var dangerousInnerHTML = {
1119
1144
  description: "Using dangerouslySetInnerHTML without sanitization (DOMPurify) enables XSS attacks. User-controlled content injected as raw HTML can execute arbitrary JavaScript.",
1120
1145
  check(content, filePath) {
1121
1146
  if (!filePath.match(/\.(jsx|tsx)$/)) return [];
1147
+ if (isTestFile(filePath)) return [];
1122
1148
  if (!/dangerouslySetInnerHTML/i.test(content)) return [];
1123
1149
  const hasSanitize = /DOMPurify|sanitize|purify|xss|sanitizeHtml|isomorphic-dompurify/i.test(content);
1124
1150
  if (hasSanitize) return [];
1125
- return findMatches(
1126
- content,
1127
- /dangerouslySetInnerHTML\s*=\s*\{\s*\{/g,
1128
- dangerousInnerHTML,
1129
- filePath,
1130
- () => "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify"
1131
- );
1151
+ const findings = [];
1152
+ const re = /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*([^}]+)\}/g;
1153
+ let m;
1154
+ while ((m = re.exec(content)) !== null) {
1155
+ if (isCommentLine(content, m.index)) continue;
1156
+ const value = m[1].trim();
1157
+ if (/^[A-Z][A-Z0-9_]+$/.test(value)) continue;
1158
+ if (/^["'`][^$]*["'`]$/.test(value)) continue;
1159
+ const lineNum = content.substring(0, m.index).split("\n").length;
1160
+ findings.push({
1161
+ rule: "VC063",
1162
+ title: dangerousInnerHTML.title,
1163
+ severity: "critical",
1164
+ category: "Injection",
1165
+ file: filePath,
1166
+ line: lineNum,
1167
+ snippet: getSnippet(content, lineNum),
1168
+ fix: "Sanitize HTML before using dangerouslySetInnerHTML: dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(content) }}. Install: npm install dompurify"
1169
+ });
1170
+ }
1171
+ return findings;
1132
1172
  }
1133
1173
  };
1134
1174
  function detectFramework(files) {
@@ -2496,6 +2536,8 @@ function scanEntropy(files) {
2496
2536
  for (const { path: filePath, content } of files) {
2497
2537
  if (SKIP_FILES.test(filePath)) continue;
2498
2538
  if (SKIP_FILENAMES.test(filePath)) continue;
2539
+ const basename = filePath.split("/").pop() || "";
2540
+ if (SKIP_FILENAMES.test(basename)) continue;
2499
2541
  if (filePath.includes("node_modules")) continue;
2500
2542
  if (filePath.includes(".min.")) continue;
2501
2543
  if (/(?:\.test\.|\.spec\.|__tests__|__mocks__|fixtures?\/)/i.test(filePath)) continue;
@@ -3470,9 +3512,9 @@ async function scanCommand(directory, options) {
3470
3512
  if (tier === "free" && !isSilent) {
3471
3513
  console.log("");
3472
3514
  if (userPlan === "anonymous") {
3473
- console.log(chalk2.gray(" Scanned with 30 free rules.") + chalk2.cyan(" Log in to unlock all 131 rules \u2192") + chalk2.bold(" xploitscan auth login"));
3515
+ console.log(chalk2.gray(" Scanned with 30 free rules.") + chalk2.cyan(" Log in to unlock all 151 rules \u2192") + chalk2.bold(" xploitscan auth login"));
3474
3516
  } else {
3475
- console.log(chalk2.gray(" Scanned with 30 rules.") + chalk2.cyan(" Upgrade to Pro for all 131 rules \u2192") + chalk2.bold(" xploitscan upgrade"));
3517
+ console.log(chalk2.gray(" Scanned with 30 rules.") + chalk2.cyan(" Upgrade to Pro for all 151 rules \u2192") + chalk2.bold(" xploitscan upgrade"));
3476
3518
  }
3477
3519
  console.log("");
3478
3520
  }