coverme-scanner 1.12.0 → 2.0.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.
@@ -6,6 +6,28 @@ You are a **senior security consultant** with 15+ years of experience performing
6
6
 
7
7
  **Ultrathink** - Analyze deeply, read actual code, trace data flows, think like an attacker, profile performance hotspots.
8
8
 
9
+ ## ROI: Human vs Claude Code
10
+
11
+ For every finding, include estimated fix time comparison:
12
+
13
+ ```json
14
+ "estimatedEffort": {
15
+ "human": "4-6 hours (research, implement, test, deploy)",
16
+ "claudeCode": "15-20 minutes (automated detection, code generation, test creation)",
17
+ "roi": "18x faster with Claude Code"
18
+ }
19
+ ```
20
+
21
+ **Why this matters**:
22
+ - Shows tangible value to developers and managers
23
+ - Highlights Claude Code's ability to accelerate fix implementation
24
+ - Quantifies ROI on security investment
25
+
26
+ **Time estimates guide**:
27
+ - **Quick fixes**: Human 30min-2h, Claude Code 5-10min (5-12x faster)
28
+ - **Moderate fixes**: Human 4-6h, Claude Code 15-30min (12-18x faster)
29
+ - **Complex fixes**: Human 1-2 days, Claude Code 1-2h (8-16x faster)
30
+
9
31
  ---
10
32
 
11
33
  ## PHASE 1: DISCOVERY & CONTEXT
@@ -489,13 +511,77 @@ Create `.coverme/scan.json` with this structure:
489
511
  "endLine": 52,
490
512
  "code": "const query = `SELECT * FROM users WHERE name = '${req.query.name}'`;",
491
513
  "description": "User input from req.query.name is directly concatenated into SQL query without sanitization. An attacker can inject arbitrary SQL commands.",
514
+
492
515
  "impact": "Complete database compromise. Attacker can:\n1. Extract all user data including passwords\n2. Modify or delete records\n3. Execute administrative operations\n4. Potential remote code execution via database extensions",
516
+
517
+ "businessImpact": {
518
+ "financial": "GDPR fine: €20M or 4% of revenue. Average data breach cost: $4.35M (IBM 2023)",
519
+ "reputation": "Customer trust loss: 6-12 months recovery. 65% customers leave after breach (PwC)",
520
+ "legal": "Class action lawsuit risk. SEC investigation if publicly traded",
521
+ "operational": "2-4 weeks incident response, forensics, customer notifications"
522
+ },
523
+
524
+ "realWorldExamples": [
525
+ "Equifax (2017): SQL injection → 147M records stolen → $700M settlement",
526
+ "TalkTalk (2015): SQL injection → 157K customers exposed → £400K fine",
527
+ "Your industry: 23% of companies experienced SQL injection in 2023 (Verizon DBIR)"
528
+ ],
529
+
493
530
  "attackChain": [
494
- {"step": 1, "action": "Send name=' OR '1'='1' --", "result": "Query returns all users"},
495
- {"step": 2, "action": "Use UNION SELECT to read other tables", "result": "Extract payment data, API keys"},
496
- {"step": 3, "action": "Use INTO OUTFILE to write webshell", "result": "Remote code execution on server"}
531
+ {"step": 1, "action": "Send: GET /api/users?name=' OR '1'='1' --", "result": "Returns all users (authentication bypass)"},
532
+ {"step": 2, "action": "Send: name=' UNION SELECT password,email FROM admins--", "result": "Extract admin credentials"},
533
+ {"step": 3, "action": "Send: name='; DROP TABLE users; --", "result": "Data destruction (if permissions allow)"},
534
+ {"step": 4, "action": "Send: name=' INTO OUTFILE '/var/www/shell.php'--", "result": "Remote code execution"}
497
535
  ],
498
- "recommendation": "Use parameterized queries:\n\n```typescript\nconst query = 'SELECT * FROM users WHERE name = $1';\nconst result = await db.query(query, [req.query.name]);\n```",
536
+
537
+ "proofOfConcept": "# Test this vulnerability:\ncurl 'https://api.example.com/users?name=%27%20OR%20%271%27%3D%271' \\\n -H 'Authorization: Bearer $TOKEN'\n\n# Expected (vulnerable): Returns all users\n# Expected (fixed): Returns 400 Bad Request",
538
+
539
+ "recommendation": "Use parameterized queries:\n\n```typescript\n// ✅ SECURE - Parameterized query\nconst query = 'SELECT * FROM users WHERE name = $1';\nconst result = await db.query(query, [req.query.name]);\n```",
540
+
541
+ "quickFix": {
542
+ "description": "Add input validation (1 hour)",
543
+ "code": "// Quick temporary fix\nconst safeName = req.query.name.replace(/[^a-zA-Z0-9 ]/g, '');\nconst query = `SELECT * FROM users WHERE name = '${safeName}'`;",
544
+ "limitations": "Not foolproof - still vulnerable to advanced attacks"
545
+ },
546
+
547
+ "properFix": {
548
+ "description": "Switch to parameterized queries + add WAF (1 day)",
549
+ "code": "// Proper fix\nconst query = 'SELECT * FROM users WHERE name = $1';\nconst result = await db.query(query, [req.query.name]);",
550
+ "additionalSteps": [
551
+ "Add input validation middleware (Joi/Zod schema)",
552
+ "Enable SQL injection protection in WAF (Cloudflare/AWS WAF)",
553
+ "Add query logging for security monitoring",
554
+ "Run database user with minimal privileges (not root)"
555
+ ]
556
+ },
557
+
558
+ "testing": {
559
+ "description": "How to verify the fix works",
560
+ "manual": [
561
+ "Test: curl 'https://api.example.com/users?name=%27%20OR%20%271%27%3D%271'",
562
+ "Expected: 400 Bad Request with error message",
563
+ "Test: curl 'https://api.example.com/users?name=John'",
564
+ "Expected: Returns user 'John' only"
565
+ ],
566
+ "automated": "# Add security test\ntest('should reject SQL injection attempts', async () => {\n const response = await request(app)\n .get('/api/users?name=\\' OR \\'1\\'=\\'1')\n .expect(400);\n expect(response.body.error).toContain('Invalid input');\n});"
567
+ },
568
+
569
+ "detection": {
570
+ "description": "How to check if already exploited",
571
+ "commands": [
572
+ "# Check access logs for SQL injection patterns",
573
+ "grep -E \"(UNION|SELECT|INSERT|DROP|--|;)\" /var/log/nginx/access.log",
574
+ "# Check database audit logs",
575
+ "SELECT * FROM pg_stat_statements WHERE query LIKE '%UNION%' OR query LIKE '%--';"
576
+ ],
577
+ "indicators": [
578
+ "Unusual SQL errors in application logs",
579
+ "Multiple failed queries from same IP",
580
+ "Database queries with suspicious patterns (UNION, --, ;)",
581
+ "Unexpected spike in database CPU usage"
582
+ ]
583
+ },
584
+
499
585
  "dread": {
500
586
  "damage": 10,
501
587
  "reproducibility": 10,
@@ -504,33 +590,139 @@ Create `.coverme/scan.json` with this structure:
504
590
  "discoverability": 8,
505
591
  "score": 9.4
506
592
  },
593
+
507
594
  "cwe": "CWE-89",
508
595
  "owasp": "A03:2021-Injection",
596
+ "cvss": "9.8 (Critical)",
597
+
598
+ "references": [
599
+ "https://owasp.org/www-community/attacks/SQL_Injection",
600
+ "https://cwe.mitre.org/data/definitions/89.html",
601
+ "https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html"
602
+ ],
603
+
509
604
  "evidence": [
510
605
  "Input source: req.query.name (line 44) - no validation",
511
606
  "Sink: db.query() (line 45) - string concatenation",
512
607
  "No sanitization between source and sink",
513
- "Endpoint is publicly accessible"
608
+ "Endpoint is publicly accessible",
609
+ "Database user has full privileges (checked schema)"
514
610
  ],
515
- "fixOwner": "developer"
611
+
612
+ "fixOwner": "developer",
613
+ "estimatedEffort": {
614
+ "human": "4-6 hours (research parameterized queries, update all SQL calls, write tests, deploy)",
615
+ "claudeCode": "15-20 minutes (finds all SQL concatenations, generates parameterized versions, writes tests)",
616
+ "roi": "18x faster with Claude Code"
617
+ },
618
+ "priority": 1
516
619
  },
517
620
  {
518
621
  "id": "QUAL-001",
519
- "title": "Silent Payment Error Handling",
622
+ "title": "Silent Payment Error Handling - Financial Loss Risk",
520
623
  "severity": "critical",
521
624
  "category": "quality",
522
625
  "file": "src/services/paymentService.ts",
523
626
  "line": 89,
524
627
  "endLine": 95,
525
628
  "code": "try {\n await stripe.charges.create(charge);\n} catch (err) {\n console.error('Payment error:', err);\n}\nreturn { success: true };",
526
- "description": "Payment errors are caught and logged but execution continues. The function returns success:true even when payment fails. Customer receives product without paying.",
527
- "impact": "Financial loss. Estimated $X per failed payment. Data shows 3% of payments fail, so ~$Y/month revenue loss.",
528
- "recommendation": "Rethrow error or return failure:\n\n```typescript\ntry {\n await stripe.charges.create(charge);\n return { success: true };\n} catch (err) {\n logger.error('Payment failed', { orderId, error: err });\n throw new PaymentError('Payment processing failed');\n}\n```",
629
+
630
+ "description": "Payment errors are caught and logged but execution continues. The function returns success:true even when payment fails. Customer receives product without paying. This is a SILENT FAILURE - the most dangerous type of bug.",
631
+
632
+ "impact": "Direct financial loss. Orders are fulfilled without payment. Customer sees 'success' and receives product/service for $0.",
633
+
634
+ "businessImpact": {
635
+ "financial": "Stripe reports 3% payment failure rate. At 1000 orders/month × $50 avg = $1,500/month revenue loss = $18K/year",
636
+ "reputation": "Accounting discovers revenue discrepancy → loss of investor confidence",
637
+ "legal": "Incorrect financial reporting → potential audit/compliance issues",
638
+ "operational": "Manual reconciliation required to find unpaid orders"
639
+ },
640
+
641
+ "realWorldExamples": [
642
+ "Knight Capital (2012): Silent error handling → $440M loss in 45 minutes",
643
+ "Your scenario: Payment fails, user gets premium subscription free. Stripe dispute shows no payment.",
644
+ "Common pattern: 30% of payment bugs are silent failures (Sentry data 2023)"
645
+ ],
646
+
647
+ "attackChain": [
648
+ {"step": 1, "action": "User submits order for $100 product", "result": "Order processing begins"},
649
+ {"step": 2, "action": "Stripe payment fails (network error, card declined, etc)", "result": "Exception thrown, caught silently"},
650
+ {"step": 3, "action": "Code continues, returns {success: true}", "result": "User sees success message"},
651
+ {"step": 4, "action": "Fulfillment system processes 'successful' order", "result": "Product shipped/service activated for $0"}
652
+ ],
653
+
654
+ "proofOfConcept": "// Reproduce the bug:\n// 1. Set STRIPE_TEST_MODE=true\n// 2. Use test card 4000000000000341 (card declined)\n// 3. Submit order\n// 4. Observe: Payment fails but order succeeds\n\nawait fetch('/api/orders', {\n method: 'POST',\n body: JSON.stringify({\n cardNumber: '4000000000000341', // Test card that declines\n amount: 100\n })\n});\n// Result: {success: true} even though payment failed",
655
+
656
+ "recommendation": "Rethrow error or return failure:\n\n```typescript\n// ✅ PROPER ERROR HANDLING\ntry {\n const charge = await stripe.charges.create(chargeData);\n await db.orders.update(orderId, { status: 'paid', chargeId: charge.id });\n return { success: true, chargeId: charge.id };\n} catch (err) {\n // Log with context\n logger.error('Payment failed', { \n orderId, \n amount, \n error: err.message,\n userId \n });\n \n // Update order status\n await db.orders.update(orderId, { status: 'payment_failed' });\n \n // Return failure to caller\n throw new PaymentError('Payment processing failed', { cause: err });\n}\n```",
657
+
658
+ "quickFix": {
659
+ "description": "Add return statement in catch (30 minutes)",
660
+ "code": "try {\n await stripe.charges.create(charge);\n} catch (err) {\n console.error('Payment error:', err);\n return { success: false, error: 'Payment failed' }; // FIX: Return failure\n}\nreturn { success: true };",
661
+ "limitations": "Still loses error details, no proper logging, no order status update"
662
+ },
663
+
664
+ "properFix": {
665
+ "description": "Comprehensive error handling with transaction rollback (4 hours)",
666
+ "code": "async function processPayment(orderId, chargeData) {\n const transaction = await db.transaction();\n \n try {\n // 1. Charge the card\n const charge = await stripe.charges.create(chargeData);\n \n // 2. Update order in transaction\n await transaction.orders.update(orderId, { \n status: 'paid',\n chargeId: charge.id,\n paidAt: new Date()\n });\n \n await transaction.commit();\n \n return { success: true, chargeId: charge.id };\n \n } catch (err) {\n // Rollback database changes\n await transaction.rollback();\n \n // Structured logging\n logger.error('Payment failed', {\n orderId,\n userId: chargeData.customer,\n amount: chargeData.amount,\n currency: chargeData.currency,\n errorType: err.type,\n errorMessage: err.message,\n stripeRequestId: err.requestId\n });\n \n // Update order status\n await db.orders.update(orderId, { \n status: 'payment_failed',\n failureReason: err.message \n });\n \n // Throw typed error\n throw new PaymentError('Payment processing failed', { \n cause: err,\n orderId,\n retryable: err.type === 'card_error'\n });\n }\n}",
667
+ "additionalSteps": [
668
+ "Add payment monitoring/alerting (e.g., Sentry, DataDog)",
669
+ "Implement dead letter queue for failed payments",
670
+ "Add manual reconciliation job (daily check: orders vs Stripe charges)",
671
+ "Create customer notification for payment failures"
672
+ ]
673
+ },
674
+
675
+ "testing": {
676
+ "description": "Test all error scenarios",
677
+ "manual": [
678
+ "Test 1: Use Stripe test card 4000000000000341 (card declined) → should return error",
679
+ "Test 2: Disable internet → should handle network error",
680
+ "Test 3: Invalid API key → should catch auth error",
681
+ "Test 4: Check database: failed orders should have status='payment_failed'"
682
+ ],
683
+ "automated": "describe('Payment error handling', () => {\n it('should fail order when payment declines', async () => {\n // Mock Stripe to throw error\n stripe.charges.create.mockRejectedValue(\n new Error('card_declined')\n );\n \n await expect(\n processPayment(orderId, chargeData)\n ).rejects.toThrow(PaymentError);\n \n // Verify order status updated\n const order = await db.orders.find(orderId);\n expect(order.status).toBe('payment_failed');\n });\n \n it('should not fulfill order on payment failure', async () => {\n stripe.charges.create.mockRejectedValue(new Error());\n \n try {\n await processPayment(orderId, chargeData);\n } catch (err) {\n // Expected\n }\n \n const order = await db.orders.find(orderId);\n expect(order.fulfilledAt).toBeNull();\n });\n});"
684
+ },
685
+
686
+ "detection": {
687
+ "description": "Find orders that were fulfilled without payment",
688
+ "commands": [
689
+ "# Find orders with status='success' but no Stripe charge",
690
+ "SELECT * FROM orders WHERE status = 'completed' AND charge_id IS NULL;",
691
+ "",
692
+ "# Check logs for silent payment errors",
693
+ "grep 'Payment error' /var/log/app.log | grep -v 'PaymentError thrown'",
694
+ "",
695
+ "# Reconciliation: Compare orders vs Stripe charges",
696
+ "node scripts/reconcile-payments.js --date 2026-02-01"
697
+ ],
698
+ "indicators": [
699
+ "Orders table: status='completed' but charge_id is NULL",
700
+ "Stripe dashboard: Fewer charges than completed orders",
701
+ "Revenue mismatch: Reported revenue < Stripe deposits",
702
+ "Customer support: Users report being charged $0"
703
+ ]
704
+ },
705
+
706
+ "references": [
707
+ "https://stripe.com/docs/error-handling",
708
+ "https://martinfowler.com/articles/replaceThrowWithNotification.html"
709
+ ],
710
+
529
711
  "evidence": [
530
- "catch block at line 91 only logs",
531
- "No return/throw in catch",
532
- "Success returned at line 95 regardless of payment result"
533
- ]
712
+ "catch block at line 91 only logs - no rethrow, no return false",
713
+ "No return/throw in catch block",
714
+ "Success returned at line 95 regardless of payment result",
715
+ "No database rollback on payment failure",
716
+ "Checked Stripe webhook logs: confirms payment failures happen but are ignored"
717
+ ],
718
+
719
+ "fixOwner": "developer",
720
+ "estimatedEffort": {
721
+ "human": "4-6 hours (identify all error paths, add transaction handling, write error tests, manual reconciliation check)",
722
+ "claudeCode": "25-30 minutes (finds all try-catch blocks, adds proper error handling, generates test cases)",
723
+ "roi": "12x faster with Claude Code"
724
+ },
725
+ "priority": 1
534
726
  }
535
727
  ],
536
728
 
@@ -665,15 +857,91 @@ Before finishing:
665
857
  "id": "AUTH-001",
666
858
  "title": "JWT Accepts 'none' Algorithm - Authentication Bypass",
667
859
  "severity": "critical",
860
+ "category": "security",
668
861
  "file": "src/middleware/auth.ts",
669
862
  "line": 34,
863
+ "endLine": 34,
670
864
  "code": "jwt.verify(token, secret, { algorithms: ['HS256', 'none'] })",
671
- "description": "JWT verification explicitly allows the 'none' algorithm. An attacker can forge tokens by removing the signature and setting alg='none'.",
865
+
866
+ "description": "JWT verification explicitly allows the 'none' algorithm. An attacker can forge tokens by removing the signature and setting alg='none'. This is a COMPLETE authentication bypass.",
867
+
868
+ "impact": "Total authentication bypass. Attacker can:\n1. Impersonate any user (including admins)\n2. Access all protected endpoints\n3. Modify/delete any data\n4. Create backdoor admin accounts",
869
+
870
+ "businessImpact": {
871
+ "financial": "Complete data breach. GDPR fine: €20M or 4% revenue. Average breach cost: $4.35M",
872
+ "reputation": "Loss of customer trust. 81% users abandon service after auth breach (Okta 2023)",
873
+ "legal": "Class action lawsuit risk. Negligence claim - 'none' algorithm is well-known vulnerability",
874
+ "operational": "Full incident response, forensics, password reset for all users, potential service shutdown"
875
+ },
876
+
877
+ "realWorldExamples": [
878
+ "Auth0 CVE-2015-9235: 'none' algorithm allowed → millions of tokens compromised",
879
+ "Pfsense (2018): JWT 'none' bypass → full admin access to firewall configs",
880
+ "Your scenario: Attacker becomes admin, exports entire customer database, posts on dark web"
881
+ ],
882
+
672
883
  "attackChain": [
673
- {"step": 1, "action": "Capture valid JWT from /api/login response", "result": "Obtain token structure"},
674
- {"step": 2, "action": "Base64 decode header, change alg to 'none', remove signature", "result": "Forged token: eyJ...Cg==.eyJ...fQ."},
675
- {"step": 3, "action": "Send forged token with admin=true claim", "result": "Authenticated as admin"}
884
+ {"step": 1, "action": "Register normal user account, capture JWT from /api/login", "result": "Valid token: eyJhbGc..."},
885
+ {"step": 2, "action": "Base64 decode header, change {\"alg\":\"HS256\"} to {\"alg\":\"none\"}", "result": "Forged header"},
886
+ {"step": 3, "action": "Change payload to {\"userId\":1,\"role\":\"admin\"}, remove signature", "result": "Admin token without signature"},
887
+ {"step": 4, "action": "Send to /api/admin/users with forged token", "result": "Returns all users - authentication bypassed"}
676
888
  ],
889
+
890
+ "proofOfConcept": "# Exploit the vulnerability:\n\n# 1. Get valid token\nTOKEN=$(curl -X POST https://api.example.com/login \\\n -d '{\"email\":\"attacker@evil.com\",\"password\":\"pass123\"}' | jq -r .token)\n\n# 2. Forge admin token (Python)\npython3 << EOF\nimport base64\nimport json\n\n# Decode original token parts\nheader = {\"alg\": \"none\", \"typ\": \"JWT\"}\npayload = {\"userId\": 1, \"role\": \"admin\", \"exp\": 9999999999}\n\n# Encode without signature\nforged = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=') + b'.'\nforged += base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=') + b'.'\n\nprint(forged.decode())\nEOF\n\n# 3. Use forged token\ncurl https://api.example.com/admin/users \\\n -H \"Authorization: Bearer eyJhbGc...\" \\\n # Result: Full user database returned",
891
+
892
+ "recommendation": "Remove 'none' from algorithms array:\n\n```typescript\n// ✅ SECURE - Only allow HS256\njwt.verify(token, secret, { algorithms: ['HS256'] })\n```",
893
+
894
+ "quickFix": {
895
+ "description": "Remove 'none' from algorithms array (5 minutes)",
896
+ "code": "jwt.verify(token, secret, { algorithms: ['HS256'] })",
897
+ "limitations": "Still using symmetric key (HS256) - shared between services. Consider asymmetric (RS256) for multi-service architecture."
898
+ },
899
+
900
+ "properFix": {
901
+ "description": "Upgrade to RS256 with public/private key pairs (4 hours)",
902
+ "code": "// Generate key pair (run once)\n// openssl genrsa -out private.key 2048\n// openssl rsa -in private.key -pubout -out public.key\n\nimport fs from 'fs';\nimport jwt from 'jsonwebtoken';\n\nconst publicKey = fs.readFileSync('public.key');\n\n// Verify with public key only\nfunction verifyToken(token: string) {\n try {\n const decoded = jwt.verify(token, publicKey, {\n algorithms: ['RS256'], // Only asymmetric\n issuer: 'your-service',\n audience: 'your-api'\n });\n return decoded;\n } catch (err) {\n throw new AuthError('Invalid token', { cause: err });\n }\n}",
903
+ "additionalSteps": [
904
+ "Store private key in secure vault (AWS Secrets Manager, HashiCorp Vault)",
905
+ "Add key rotation policy (rotate every 90 days)",
906
+ "Implement token revocation list (Redis with TTL)",
907
+ "Add JWT ID (jti) claim for replay protection",
908
+ "Monitor for suspicious token patterns (e.g., expired tokens, wrong issuer)"
909
+ ]
910
+ },
911
+
912
+ "testing": {
913
+ "description": "Verify the fix prevents 'none' algorithm attacks",
914
+ "manual": [
915
+ "Test 1: Forge token with alg='none' → should reject with 'invalid algorithm'",
916
+ "Test 2: Valid HS256 token → should work",
917
+ "Test 3: Token with wrong signature → should reject",
918
+ "Test 4: Expired token → should reject"
919
+ ],
920
+ "automated": "describe('JWT security', () => {\n it('should reject none algorithm', () => {\n const forgedToken = 'eyJhbGciOiJub25lIn0.eyJ1c2VySWQiOjF9.';\n \n expect(() => {\n verifyToken(forgedToken);\n }).toThrow('invalid algorithm');\n });\n \n it('should only accept HS256', () => {\n const validToken = jwt.sign({ userId: 1 }, secret, { algorithm: 'HS256' });\n const decoded = verifyToken(validToken);\n expect(decoded.userId).toBe(1);\n \n // RS256 should fail\n const rs256Token = jwt.sign({ userId: 1 }, privateKey, { algorithm: 'RS256' });\n expect(() => verifyToken(rs256Token)).toThrow();\n });\n});"
921
+ },
922
+
923
+ "detection": {
924
+ "description": "Check if this vulnerability was already exploited",
925
+ "commands": [
926
+ "# Check logs for tokens with 'none' algorithm",
927
+ "grep 'eyJhbGciOiJub25lIi' /var/log/nginx/access.log",
928
+ "",
929
+ "# Decode suspicious tokens from logs",
930
+ "cat access.log | grep 'Authorization: Bearer' | cut -d' ' -f3 | while read token; do",
931
+ " echo $token | cut -d. -f1 | base64 -d",
932
+ "done | grep '\"alg\":\"none\"'",
933
+ "",
934
+ "# Check for admin actions from non-admin users",
935
+ "SELECT * FROM audit_log WHERE action LIKE 'admin.%' AND user_id NOT IN (SELECT id FROM users WHERE role='admin');"
936
+ ],
937
+ "indicators": [
938
+ "Tokens in logs with alg='none' in header",
939
+ "Admin actions performed by non-admin user IDs",
940
+ "Unusual API access patterns (e.g., all endpoints accessed by one user)",
941
+ "Failed JWT verification errors followed by successful requests"
942
+ ]
943
+ },
944
+
677
945
  "dread": {
678
946
  "damage": 10,
679
947
  "reproducibility": 10,
@@ -682,12 +950,296 @@ Before finishing:
682
950
  "discoverability": 7,
683
951
  "score": 9.2
684
952
  },
685
- "recommendation": "Remove 'none' from algorithms array:\n\n```typescript\njwt.verify(token, secret, { algorithms: ['HS256'] })\n```",
953
+
954
+ "cwe": "CWE-347",
955
+ "owasp": "A02:2021-Cryptographic Failures",
956
+ "cvss": "9.8 (Critical)",
957
+
958
+ "references": [
959
+ "https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/",
960
+ "https://cwe.mitre.org/data/definitions/347.html",
961
+ "https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/06-Session_Management_Testing/10-Testing_JSON_Web_Tokens"
962
+ ],
963
+
686
964
  "evidence": [
687
965
  "algorithms array at line 34 includes 'none'",
688
966
  "No additional signature validation",
689
- "Endpoint /api/admin uses this middleware (routes.ts:89)"
690
- ]
967
+ "Endpoint /api/admin uses this middleware (routes.ts:89)",
968
+ "No issuer/audience validation - accepts any token structure",
969
+ "Secret key hardcoded in config file (not rotated)"
970
+ ],
971
+
972
+ "fixOwner": "developer",
973
+ "estimatedEffort": {
974
+ "human": "4-6 hours (research JWT best practices, implement RS256, generate key pairs, update auth flow, test)",
975
+ "claudeCode": "20-30 minutes (updates jwt.verify config, generates RS256 implementation, creates key management code, writes tests)",
976
+ "roi": "12x faster with Claude Code"
977
+ },
978
+ "priority": 1
979
+ }
980
+ ```
981
+
982
+ ### Excellent Performance Finding (Do This!)
983
+
984
+ ```json
985
+ {
986
+ "id": "PERF-001",
987
+ "title": "N+1 Query in User Dashboard - 3000ms Page Load",
988
+ "severity": "high",
989
+ "category": "performance",
990
+ "file": "src/controllers/dashboardController.ts",
991
+ "line": 45,
992
+ "endLine": 52,
993
+ "code": "const users = await User.findAll();\nfor (const user of users) {\n user.orders = await Order.findByUserId(user.id);\n}",
994
+
995
+ "description": "Dashboard loads all users (1,000+) then makes individual database query for each user's orders. 1 query to get users + 1,000 queries for orders = 1,001 total queries. At 3ms per query = 3+ seconds page load.",
996
+
997
+ "impact": "Severe performance degradation. Page load time increases linearly with user count:\n- 100 users: 300ms\n- 1,000 users: 3,000ms (current)\n- 10,000 users: 30,000ms (unusable)\n\nDatabase CPU spikes to 90% during peak hours. Users abandon page before it loads.",
998
+
999
+ "businessImpact": {
1000
+ "financial": "53% users abandon pages loading >3s (Google). At 10k visits/day × $50 conversion = $265k/year lost revenue",
1001
+ "reputation": "Poor reviews mentioning 'slow dashboard'. PageSpeed score: 23/100",
1002
+ "operational": "Database overload causes cascading failures. RDS CPU alerts firing 20x/day",
1003
+ "scalability": "Cannot scale past 1,000 users without major infrastructure upgrade"
1004
+ },
1005
+
1006
+ "realWorldExamples": [
1007
+ "Shopify (2015): N+1 queries → 20s checkout → fixed with eager loading → 2s",
1008
+ "Your scenario: Admin dashboard times out during business hours",
1009
+ "Industry data: N+1 queries are #2 cause of slow Rails apps (Rails community survey)"
1010
+ ],
1011
+
1012
+ "attackChain": [
1013
+ {"step": 1, "action": "Normal user visits /dashboard", "result": "Triggers 1,001 database queries"},
1014
+ {"step": 2, "action": "Attacker sends 10 concurrent requests", "result": "10,010 queries → database CPU 95%"},
1015
+ {"step": 3, "action": "Legitimate users affected", "result": "All API requests slow down"},
1016
+ {"step": 4, "action": "Attacker continues requests", "result": "Accidental DoS - database becomes unresponsive"}
1017
+ ],
1018
+
1019
+ "proofOfConcept": "# Measure the N+1 problem:\n\n# 1. Enable query logging\nDATABASE_LOG_QUERIES=true npm start\n\n# 2. Load dashboard\ncurl https://api.example.com/dashboard\n\n# 3. Count queries in log\ngrep 'SELECT.*FROM orders' logs/db.log | wc -l\n# Result: 1000+ queries\n\n# 4. Measure timing\ntime curl https://api.example.com/dashboard\n# Result: real 3.2s",
1020
+
1021
+ "recommendation": "Use eager loading (JOIN) to fetch all data in one query:\n\n```typescript\n// ✅ OPTIMIZED - Single query with JOIN\nconst users = await User.findAll({\n include: [{\n model: Order,\n as: 'orders'\n }]\n});\n\n// SQL generated:\n// SELECT users.*, orders.*\n// FROM users\n// LEFT JOIN orders ON orders.user_id = users.id\n// (1 query instead of 1,001)\n```",
1022
+
1023
+ "quickFix": {
1024
+ "description": "Add eager loading to existing query (30 minutes)",
1025
+ "code": "const users = await User.findAll({\n include: ['orders'] // Sequelize eager loading\n});",
1026
+ "limitations": "Still loads all users - may be slow with 10k+ users. Pagination recommended."
1027
+ },
1028
+
1029
+ "properFix": {
1030
+ "description": "Eager loading + pagination + caching (4 hours)",
1031
+ "code": "// Proper solution with pagination\nasync function getDashboard(page = 1, limit = 50) {\n const cacheKey = `dashboard:${page}`;\n \n // Check cache first\n const cached = await redis.get(cacheKey);\n if (cached) return JSON.parse(cached);\n \n // Single query with JOIN + pagination\n const users = await User.findAll({\n include: [{\n model: Order,\n as: 'orders',\n limit: 10 // Limit orders per user\n }],\n limit,\n offset: (page - 1) * limit,\n order: [['createdAt', 'DESC']]\n });\n \n // Cache for 5 minutes\n await redis.setex(cacheKey, 300, JSON.stringify(users));\n \n return users;\n}",
1032
+ "additionalSteps": [
1033
+ "Add database index on orders.user_id (if missing)",
1034
+ "Consider materialized view for dashboard data",
1035
+ "Add connection pooling (min: 5, max: 20)",
1036
+ "Set up read replicas for heavy queries",
1037
+ "Monitor query performance with APM (DataDog, New Relic)"
1038
+ ]
1039
+ },
1040
+
1041
+ "testing": {
1042
+ "description": "Verify performance improvement",
1043
+ "manual": [
1044
+ "Test 1: Load dashboard, check DB logs → should see 1-2 queries (not 1000+)",
1045
+ "Test 2: Use Chrome DevTools → Time to First Byte should be <500ms",
1046
+ "Test 3: Run with 10k users in test DB → should still load <1s",
1047
+ "Test 4: Check database CPU during load → should stay <50%"
1048
+ ],
1049
+ "automated": "describe('Dashboard performance', () => {\n it('should use eager loading (no N+1)', async () => {\n // Track queries\n const queries = [];\n db.on('query', q => queries.push(q));\n \n await getDashboard();\n \n // Should be 1 SELECT with JOIN\n expect(queries.length).toBeLessThan(5);\n expect(queries[0]).toContain('JOIN orders');\n });\n \n it('should load dashboard in <500ms', async () => {\n const start = Date.now();\n await getDashboard();\n const duration = Date.now() - start;\n \n expect(duration).toBeLessThan(500);\n });\n});"
1050
+ },
1051
+
1052
+ "detection": {
1053
+ "description": "Find other N+1 query problems in codebase",
1054
+ "commands": [
1055
+ "# Find loops with database queries inside",
1056
+ "grep -r 'for.*await.*find' src/",
1057
+ "grep -r '.map.*await' src/",
1058
+ "",
1059
+ "# Check database slow query log",
1060
+ "cat /var/log/postgresql/slow-queries.log | grep 'SELECT.*FROM orders'",
1061
+ "",
1062
+ "# Use database profiler",
1063
+ "npm install --save-dev sequelize-profiler",
1064
+ "# Add to app: db.use(require('sequelize-profiler'))"
1065
+ ],
1066
+ "indicators": [
1067
+ "Database query count proportional to result set size",
1068
+ "Slow query log shows same query repeated 100+ times",
1069
+ "APM shows 'database' taking 80%+ of response time",
1070
+ "Database CPU spikes during list page loads"
1071
+ ]
1072
+ },
1073
+
1074
+ "dread": {
1075
+ "damage": 7,
1076
+ "reproducibility": 10,
1077
+ "exploitability": 10,
1078
+ "affectedUsers": 10,
1079
+ "discoverability": 8,
1080
+ "score": 9.0
1081
+ },
1082
+
1083
+ "references": [
1084
+ "https://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations",
1085
+ "https://sequelize.org/docs/v6/advanced-association-concepts/eager-loading/",
1086
+ "https://use-the-index-luke.com/"
1087
+ ],
1088
+
1089
+ "evidence": [
1090
+ "Loop at line 46 calls Order.findByUserId() inside",
1091
+ "No 'include' option in User.findAll() query",
1092
+ "APM data shows 1,247 queries for single dashboard load",
1093
+ "Database slow query log: 'SELECT * FROM orders WHERE user_id = ?' appears 1000+ times",
1094
+ "PageSpeed Insights: Time to Interactive = 3.2s"
1095
+ ],
1096
+
1097
+ "fixOwner": "developer",
1098
+ "estimatedEffort": {
1099
+ "human": "3-4 hours (find all N+1 queries, research eager loading, implement pagination, add caching, performance test)",
1100
+ "claudeCode": "15-20 minutes (detects N+1 patterns, converts to eager loading, adds pagination and caching, generates performance tests)",
1101
+ "roi": "12x faster with Claude Code"
1102
+ },
1103
+ "priority": 1
1104
+ }
1105
+ ```
1106
+
1107
+ ### Excellent Architecture Finding (Do This!)
1108
+
1109
+ ```json
1110
+ {
1111
+ "id": "ARCH-001",
1112
+ "title": "Payment Secrets Hardcoded in Repository - Exposure Risk",
1113
+ "severity": "critical",
1114
+ "category": "architecture",
1115
+ "file": "src/config/payment.ts",
1116
+ "line": 12,
1117
+ "endLine": 12,
1118
+ "code": "const STRIPE_SECRET_KEY = 'sk_live_51Hqr2x...'",
1119
+
1120
+ "description": "Stripe LIVE secret key hardcoded in source code. Key has been committed to git history (commit abc123). Anyone with repository access can process payments, issue refunds, export customer data.",
1121
+
1122
+ "impact": "Complete payment system compromise:\n1. Process fraudulent charges\n2. Issue refunds to attacker's accounts\n3. Export all customer payment data (PCI violation)\n4. Delete customer payment methods\n5. Key exposed in git history - even if removed now, still accessible",
1123
+
1124
+ "businessImpact": {
1125
+ "financial": "Fraudulent charges/refunds = unlimited financial loss. Stripe may suspend account. PCI Level 1 fine: $5k-$100k/month",
1126
+ "reputation": "PCI compliance failure = cannot process credit cards. Customer data breach = loss of trust",
1127
+ "legal": "PCI DSS violation = potential lawsuits. FTC investigation. Payment processor termination",
1128
+ "operational": "Emergency key rotation, customer notification, forensic investigation, potential service shutdown"
1129
+ },
1130
+
1131
+ "realWorldExamples": [
1132
+ "Uber (2016): AWS keys in GitHub → 57M users exposed → $148M settlement",
1133
+ "Twitter (2020): API keys leaked → account takeovers including Obama, Biden",
1134
+ "Your risk: Developer laptop stolen → attacker has payment keys → can issue refunds to themselves"
1135
+ ],
1136
+
1137
+ "attackChain": [
1138
+ {"step": 1, "action": "Clone repository or access via ex-employee's account", "result": "Source code with secret key"},
1139
+ {"step": 2, "action": "Use key to list all customers: curl https://api.stripe.com/v1/customers -u sk_live_...", "result": "Export entire customer database"},
1140
+ {"step": 3, "action": "Issue refunds to attacker-controlled accounts", "result": "$10k+ fraudulent refunds"},
1141
+ {"step": 4, "action": "Even if key rotated, check git history: git log -p config/payment.ts", "result": "Original key still in commits"}
1142
+ ],
1143
+
1144
+ "proofOfConcept": "# Exploit hardcoded secret:\n\n# 1. Find the key in code\ngrep -r 'sk_live' .\n# Result: src/config/payment.ts:12:const STRIPE_SECRET_KEY = 'sk_live_51Hqr2x...'\n\n# 2. Check git history (even if removed)\ngit log --all -p | grep 'sk_live'\n# Result: Found in commits abc123, def456, ghi789\n\n# 3. Use the key to issue refund\ncurl https://api.stripe.com/v1/refunds \\\n -u sk_live_51Hqr2x...: \\\n -d charge=ch_xyz123 \\\n -d amount=10000\n# Result: $100 refunded to attacker's card",
1145
+
1146
+ "recommendation": "Move secrets to environment variables and secret manager:\n\n```typescript\n// ✅ SECURE - Load from environment\nconst STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;\n\nif (!STRIPE_SECRET_KEY) {\n throw new Error('STRIPE_SECRET_KEY environment variable required');\n}\n```",
1147
+
1148
+ "quickFix": {
1149
+ "description": "Move to .env file (NOT committed) - 1 hour",
1150
+ "code": "// .env (add to .gitignore)\nSTRIPE_SECRET_KEY=sk_live_51Hqr2x...\n\n// src/config/payment.ts\nimport dotenv from 'dotenv';\ndotenv.config();\n\nconst STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;",
1151
+ "limitations": ".env still on server filesystem. If server compromised, key exposed. Better: use secret manager."
1152
+ },
1153
+
1154
+ "properFix": {
1155
+ "description": "Migrate to AWS Secrets Manager with key rotation (1 day)",
1156
+ "code": "import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';\n\nconst client = new SecretsManagerClient({ region: 'us-east-1' });\n\n// Fetch secret from AWS Secrets Manager\nasync function getStripeKey(): Promise<string> {\n const response = await client.send(\n new GetSecretValueCommand({ SecretId: 'prod/stripe/secret_key' })\n );\n \n if (!response.SecretString) {\n throw new Error('Stripe secret not found in Secrets Manager');\n }\n \n return JSON.parse(response.SecretString).STRIPE_SECRET_KEY;\n}\n\n// Cache for 5 minutes (secrets manager charges per request)\nlet cachedKey: string | null = null;\nlet cacheExpiry = 0;\n\nexport async function getPaymentKey(): Promise<string> {\n if (cachedKey && Date.now() < cacheExpiry) {\n return cachedKey;\n }\n \n cachedKey = await getStripeKey();\n cacheExpiry = Date.now() + 5 * 60 * 1000;\n return cachedKey;\n}",
1157
+ "additionalSteps": [
1158
+ "Rotate Stripe secret key IMMEDIATELY (current key is compromised)",
1159
+ "Enable automatic key rotation in AWS Secrets Manager (90 days)",
1160
+ "Remove key from ALL git history: git filter-branch or BFG Repo-Cleaner",
1161
+ "Audit Stripe logs for suspicious activity (past 90 days)",
1162
+ "Set up IAM roles - only production servers can read secrets",
1163
+ "Enable CloudTrail logging for secret access",
1164
+ "Add alerts for secret access from unexpected IPs",
1165
+ "Document key rotation runbook"
1166
+ ]
1167
+ },
1168
+
1169
+ "testing": {
1170
+ "description": "Verify secrets are properly secured",
1171
+ "manual": [
1172
+ "Test 1: grep -r 'sk_live' . → should return 0 results",
1173
+ "Test 2: git log --all -p | grep 'sk_live' → check if still in history",
1174
+ "Test 3: docker run --rm app → should fail with 'STRIPE_SECRET_KEY required'",
1175
+ "Test 4: AWS Secrets Manager console → verify secret exists",
1176
+ "Test 5: Attempt to access secret from dev laptop → should fail (IAM)"
1177
+ ],
1178
+ "automated": "describe('Secret management', () => {\n it('should not have hardcoded secrets', () => {\n const files = glob.sync('**/*.{ts,js}');\n files.forEach(file => {\n const content = fs.readFileSync(file, 'utf8');\n expect(content).not.toMatch(/sk_live_[a-zA-Z0-9]{32}/);\n expect(content).not.toMatch(/pk_live_[a-zA-Z0-9]{32}/);\n });\n });\n \n it('should require environment variables', () => {\n delete process.env.STRIPE_SECRET_KEY;\n expect(() => require('./config/payment')).toThrow('STRIPE_SECRET_KEY');\n });\n});"
1179
+ },
1180
+
1181
+ "detection": {
1182
+ "description": "Find ALL hardcoded secrets in codebase",
1183
+ "commands": [
1184
+ "# Find Stripe keys",
1185
+ "git log --all -p | grep -E 'sk_live_[a-zA-Z0-9]+'",
1186
+ "",
1187
+ "# Find AWS keys",
1188
+ "git log --all -p | grep -E 'AKIA[A-Z0-9]{16}'",
1189
+ "",
1190
+ "# Find generic secrets (high entropy strings)",
1191
+ "trufflehog git file://. --only-verified",
1192
+ "",
1193
+ "# Use automated scanner",
1194
+ "npm install -g detect-secrets",
1195
+ "detect-secrets scan --all-files",
1196
+ "",
1197
+ "# Check Stripe for unauthorized activity",
1198
+ "# Login to Stripe dashboard → Developers → Logs → Filter by IP address"
1199
+ ],
1200
+ "indicators": [
1201
+ "Stripe keys (sk_live_, pk_live_) in source code",
1202
+ "AWS credentials (AKIA...) in config files",
1203
+ "High entropy strings (40+ random chars) in code",
1204
+ "Suspicious Stripe activity: refunds from unknown IPs, customer exports",
1205
+ "GitHub secret scanning alerts (if repo is on GitHub)"
1206
+ ]
1207
+ },
1208
+
1209
+ "dread": {
1210
+ "damage": 10,
1211
+ "reproducibility": 10,
1212
+ "exploitability": 10,
1213
+ "affectedUsers": 10,
1214
+ "discoverability": 9,
1215
+ "score": 9.8
1216
+ },
1217
+
1218
+ "cwe": "CWE-798",
1219
+ "owasp": "A02:2021-Cryptographic Failures",
1220
+ "cvss": "10.0 (Critical)",
1221
+
1222
+ "references": [
1223
+ "https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html",
1224
+ "https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password",
1225
+ "https://stripe.com/docs/keys#safe-keys"
1226
+ ],
1227
+
1228
+ "evidence": [
1229
+ "Line 12: const STRIPE_SECRET_KEY = 'sk_live_51Hqr2x...'",
1230
+ "Git history shows key in 14 commits dating back 6 months",
1231
+ "Repository has 23 contributors (all have access to key)",
1232
+ "No .gitignore entry for config files",
1233
+ "GitHub shows 'Secret scanning alert' for this repository"
1234
+ ],
1235
+
1236
+ "fixOwner": "devops + developer",
1237
+ "estimatedEffort": {
1238
+ "human": "1-2 days (setup AWS Secrets Manager, migrate all secrets, update deployment scripts, rotate keys, audit git history, cleanup with BFG)",
1239
+ "claudeCode": "1-2 hours (finds all secrets, generates Secrets Manager integration, updates code, creates rotation scripts, provides git cleanup commands)",
1240
+ "roi": "12x faster with Claude Code"
1241
+ },
1242
+ "priority": 1
691
1243
  }
692
1244
  ```
693
1245
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coverme-scanner",
3
- "version": "1.12.0",
3
+ "version": "2.0.0",
4
4
  "description": "AI-powered code scanner with multi-agent verification for Claude Code. One command scans everything.",
5
5
  "main": "dist/index.js",
6
6
  "files": [