# LexMind Security Audit Report **Date:** 2026-02-01 **Auditor:** Automated Security Audit **Status:** ✅ COMPLETED --- ## Executive Summary Comprehensive security audit of the LexMind application (Next.js 16 + Prisma + PostgreSQL + NextAuth + Stripe). Found and fixed **12 vulnerabilities** across 7 categories. Zero npm vulnerabilities remain. --- ## 1. NPM Vulnerabilities **Status:** ✅ FIXED | Before | After | |--------|-------| | 21 high severity | 0 vulnerabilities | - **Root cause:** `fast-xml-parser` 5.2.5 (via AWS SDK) had RangeError DoS bug - **Fix:** Added npm override for `fast-xml-parser@5.3.4` in package.json --- ## 2. SQL Injection / Prisma **Status:** ✅ CLEAN - No `$queryRaw` or `$executeRaw` usage found - All database access uses Prisma's parameterized queries - No raw SQL anywhere in the codebase --- ## 3. XSS (Cross-Site Scripting) **Status:** ✅ CLEAN - No `dangerouslySetInnerHTML` or `innerHTML` usage found - React's default escaping protects against XSS - Added Content-Security-Policy header (see Section 9) --- ## 4. CSRF Protection **Status:** ✅ VERIFIED - NextAuth CSRF tokens working correctly - Cookies use `__Host-` prefix with `HttpOnly; Secure; SameSite=Lax` - All mutating API routes require authenticated session --- ## 5. Authentication & Authorization **Status:** ✅ FIXED (2 critical issues) ### 🔴 CRITICAL: Unauthenticated Checkout Route - **File:** `/api/checkout/route.ts` - **Issue:** No `getServerSession` check — anyone could create Stripe checkout sessions - **Fix:** Added authentication check, uses session email instead of user-provided email ### 🔴 CRITICAL: Unauthenticated DOCX Export Route - **File:** `/api/export/docx/route.ts` - **Issue:** No authentication — anyone could generate DOCX documents - **Fix:** Added `getServerSession` check ### Auth Coverage (all routes verified): | Route | Auth | IDOR Protected | |-------|------|----------------| | /api/admin/stats | ✅ ADMIN check | N/A | | /api/analise-processo | ✅ session.user.id | ✅ userId filter | | /api/analise-processo/[id] | ✅ session.user.id | ✅ userId filter | | /api/auditoria | ✅ session.user.id | ✅ userId filter | | /api/auditoria/[id] | ✅ session.user.id | ✅ userId filter | | /api/chat | ✅ session.user.id | ✅ userId filter | | /api/chat/[chatId] | ✅ session.user.id | ✅ userId filter | | /api/checkout | ✅ **FIXED** | N/A | | /api/documents | ✅ session.user.id | ✅ userId filter | | /api/documents/[id] | ✅ session.user.id | ✅ userId filter | | /api/documents/generate | ✅ session.user.id | N/A | | /api/export/docx | ✅ **FIXED** | N/A | | /api/jurisprudencia | ✅ session.user.id | N/A (public data) | | /api/jurisprudencia/search | ✅ session.user.id | N/A (public data) | | /api/keys | ✅ session.user.id | ✅ userId filter | | /api/keys/[id] | ✅ session.user.id | ✅ userId filter | | /api/prazos | ✅ session.user.id | ✅ userId filter | | /api/prazos/[id] | ✅ session.user.id | ✅ userId filter | | /api/register | N/A (public) | N/A | | /api/stripe/checkout | ✅ session.user | ✅ | | /api/stripe/portal | ✅ session.user | ✅ | | /api/stripe/webhook | N/A (Stripe sig) | ✅ signature verified | | /api/templates | ✅ session.user.id | ✅ userId filter | | /api/uploads | ✅ session.user.id | ✅ userId filter | | /api/uploads/[id] | ✅ session.user.id | ✅ userId filter | --- ## 6. Rate Limiting **Status:** ✅ VERIFIED Nginx rate limiting active: - Auth routes: 5 req/min (`zone=auth`) - API routes: 20 req/sec (`zone=api`) - General: 30 req/sec (`zone=general`) - Connection limit: 20 per IP (`conn_limit`) - Scanner/bot blocking via User-Agent filter --- ## 7. Input Validation **Status:** ✅ FIXED (5 improvements) - **Created:** `src/lib/validate.ts` with sanitization utilities - **Register route:** Added input length limits for all fields - **Chat route:** Added 10,000 char message limit - **Auditoria route:** Added title (500) and content (100,000) limits - **Prazos route:** Added title (500) and description (5,000) limits - **Pagination:** Bounded page/limit params in uploads and jurisprudencia routes - **Uploads:** Added server-side file extension validation (defense in depth) --- ## 8. Sensitive Data Exposure **Status:** ✅ FIXED - **Created `.gitignore`** — `.env` was not being excluded (no `.gitignore` existed!) - **Stripe error messages:** Stopped leaking `error.message` to client in checkout/portal routes - **API responses:** Verified no password hashes or internal IDs are exposed - **API keys:** Properly hashed (SHA-256), only shown once on creation, masked in listings - **NEXT_PUBLIC vars:** Only publishable Stripe key and app URL (safe) - **Error handling:** All routes return generic error messages, details logged server-side --- ## 9. Security Headers **Status:** ✅ FIXED (2 new headers added) ### Headers now active: | Header | Value | Status | |--------|-------|--------| | X-Frame-Options | SAMEORIGIN | ✅ existing | | X-Content-Type-Options | nosniff | ✅ existing | | X-XSS-Protection | 1; mode=block | ✅ existing | | Referrer-Policy | strict-origin-when-cross-origin | ✅ existing | | Strict-Transport-Security | max-age=31536000; includeSubDomains | ✅ existing | | Content-Security-Policy | Full CSP policy | ✅ **ADDED** | | Permissions-Policy | camera=(), microphone=(), geolocation=() | ✅ **ADDED** | | server_tokens | off | ✅ existing | --- ## 10. Database Security **Status:** ✅ VERIFIED - PostgreSQL listens only on localhost (default, `listen_addresses = 'localhost'`) - `pg_hba.conf` uses `scram-sha-256` for TCP connections - Local connections use `peer` authentication - No remote access configured --- ## 11. File Upload Security **Status:** ✅ VERIFIED + IMPROVED - MIME type whitelist: PDF, DOC, DOCX, TXT only - **Added:** File extension validation (defense in depth) - Max size: 50MB (enforced both in app and nginx `client_max_body_size`) - Storage limits per plan (1GB-20GB) - File paths sanitized via `buildKey()` — strips all special chars - No path traversal possible (`../` becomes `___`) - Files stored as `private` ACL on DigitalOcean Spaces - Access via signed URLs (15 min expiry) --- ## 12. Session Security **Status:** ✅ IMPROVED - Cookies: `__Host-` prefix, `HttpOnly`, `Secure`, `SameSite=Lax` - **Reduced session maxAge from 30 days to 7 days** (more appropriate for legal app) - JWT strategy with strong NEXTAUTH_SECRET (44 chars, base64) - CSRF token verified on all auth requests --- ## 13. Other Fixes ### Duplicate Webhook Route Removed - **Deleted:** `/api/webhook/stripe/route.ts` (incomplete, only logged events, no DB updates) - **Active:** `/api/stripe/webhook/route.ts` (fully functional with DB updates) ### Next.js Middleware Added - **Created:** `src/middleware.ts` — adds security headers at application level as backup --- ## 14. Remaining Notes (Low Risk) | Item | Risk | Notes | |------|------|-------| | `typescript: { ignoreBuildErrors: true }` in next.config | Low | Could hide type errors; recommend fixing eventually | | AWS SDK DoS vuln was in XML parsing | Info | Fixed via override, but only exploitable if attacker controls S3 responses | | File upload MIME check trusts client header | Low | Mitigated by extension whitelist + private storage | | No email verification on registration | Medium | Users can register with unverified emails | --- ## Deployment Status - ✅ All fixes applied - ✅ `npm audit`: 0 vulnerabilities - ✅ Build successful - ✅ PM2 restarted - ✅ Nginx reloaded with new headers - ✅ Application verified working