Initial commit: LexMind - Plataforma Jurídica Inteligente
This commit is contained in:
212
SECURITY-AUDIT.md
Normal file
212
SECURITY-AUDIT.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user