Last updated: 2026-03-04
Tip
All 6 Critical , 12 High , 13 Medium , and 8 Low severity findings have been remediated (1 Medium marked “won’t fix”). See status column in each section.
Domain
Critical
High
Medium
Low
Authentication & Sessions
2 ✅
4 ✅
4
4
Input Validation & Injection
1 ✅
0
3
0
Payments & Billing
0
3 ✅
4
0
Authorization & IDOR
3 ✅
3 ✅
1
2
Infrastructure & Config
0
4 ✅
4
1
GDPR & Privacy
0
3 ✅
5
3
Total
6 ✅
12 ✅
14 (13 fixed, 1 won’t fix)
8 ✅
Overall posture: All critical, high, and medium severity issues have been remediated. The codebase now has OAuth CSRF protection, multi-tenant authorization, secure env handling, security headers, magic link hashing, PII redaction, GDPR-compliant erasure, and HTTPS enforcement.
File
apps/api/src/services/teacher/retention-metrics-service.ts
Type
SQL Injection
CVSS
9.8
Status
✅ Fixed — replaced sql.raw() with Drizzle’s inArray() operator
File
apps/api/src/services/auth/google-oauth.ts, apps/api/src/routes/auth/index.ts
Type
CSRF / Account Takeover
Status
✅ Fixed — crypto.randomBytes(32) state stored in Redis (10-min TTL), validated and deleted on callback
File
apps/api/src/services/billing/enrollment-service.ts
Routes
GET /teacher/enrollments/:id, POST /teacher/enrollments/:id/cancel
Status
✅ Fixed — teacherId parameter added to getEnrollmentById() and cancelEnrollment(), all callers updated
File
apps/api/src/services/scheduling/session-participant-service.ts
Routes
GET/POST/DELETE /teacher/sessions/:id/participants
Status
✅ Fixed — verifySessionOwnership() helper added, teacherId parameter in all public methods, all callers updated
File
apps/api/src/services/scheduling/service-schedule-service.ts
Route
GET /teacher/services/:id/schedule
Status
✅ Fixed — listScheduleItems() now requires teacherId and verifies service ownership
File
apps/api/src/services/billing/enrollment-service.ts
Type
Broken Access Control
Status
✅ Fixed — student.teacherId !== service.teacherId check added before enrollment creation
File
apps/api/src/lib/env.ts
Status
✅ Fixed — requireEnv() helper crashes on startup if SESSION_SECRET is missing in production. Dev fallback only in non-production.
File
apps/api/src/services/billing/stripe-service.ts, apps/api/src/lib/env.ts
Status
✅ Fixed — STRIPE_WEBHOOK_SECRET required via requireEnv(), crashes on missing in production. Uses env.STRIPE_WEBHOOK_SECRET instead of process.env.
File
apps/api/src/routes/webhooks/index.ts
Status
✅ Fixed — replaced SELECT-then-INSERT with atomic INSERT ... ON CONFLICT DO NOTHING, checks returning() for dedup
File
apps/api/src/routes/webhooks/index.ts
Status
✅ Fixed — on handler failure, webhook record is deleted and 500 is returned so Stripe retries
File
apps/api/src/routes/auth/index.ts
Status
✅ Fixed — rate limit (5/hour) and audit logging added to dev-login route
File
apps/api/src/services/auth/session-service.ts
Status
✅ Fixed — invalidateAllSessionsExcept(teacherId, currentSessionId) method added
File
apps/api/src/app.ts
Status
✅ Fixed — @fastify/helmet installed with HSTS (production), X-Frame-Options, X-Content-Type-Options, Referrer-Policy
File
docker-compose.yml (retirado el 1-oct-2026 con Neon)
Status
✅ Fixed — services bound to 127.0.0.1, passwords via env vars, Redis requirepass + memory limits
File
apps/api/src/services/reviews/review-service.ts
Status
✅ Fixed — reviewerAvatarUrl cleared on anonymous reviews (create, update, and public API sanitization)
File
apps/api/src/services/privacy/erasure-request.ts
Status
✅ Fixed — overdue requests logged with GDPR COMPLIANCE WARNING via Pino structured logging
File
apps/api/src/services/scheduling/session-service.ts
Status
✅ Fixed — anonymizeSoftDeletedStudents() helper strips PII from deleted students in 5 query methods
File
apps/api/src/services/billing/enrollment-service.ts
Status
✅ Fixed — priceAmount > 0 validation before Stripe checkout creation
ID
Issue
File
Status
MED-1
CORS origin not validated for HTTPS
app.ts
✅ Fixed — production restricts to APP_URL, warns if not HTTPS
MED-2
Global rate limit too high (100/min)
app.ts
✅ Fixed — reduced to 60/min
MED-3
SameSite=Lax instead of Strict
session-cookie.ts
Won’t fix — Lax is correct for OAuth redirects
MED-4
No HTTPS redirect enforcement
app.ts
✅ Fixed — onRequest hook redirects HTTP→HTTPS in production
MED-5
Magic link token in URL query param
magic-link-service.ts
✅ Fixed — DB stores SHA-256 hash, URL token is opaque
MED-6
Magic link token stored in plaintext
magic-link-service.ts
✅ Fixed — crypto.createHash('sha256') before storing
MED-7
console.log in production workers
Multiple worker files
✅ Fixed — replaced with Pino structured logging
MED-8
Avatar MIME type not strictly validated
profile-service.ts
✅ Fixed — allowlist: jpeg/png/webp/gif, SVG rejected
MED-9
Credit ledger amounts not validated
credit-ledger-service.ts
✅ Fixed — validateAmount() checks positive integer
MED-10
Session payment records not in GDPR erasure
student-data-erasure.ts
✅ Fixed — Stripe IDs anonymized, amounts preserved
MED-11
No pre-booking consent for data processing
class-session.ts
✅ Fixed — consentAccepted: z.literal(true) required
MED-12
Audit logs over-collect PII in changes field
audit-service.ts
✅ Fixed — PII fields redacted to [redacted] in diffs
MED-13
Student phone exposed in session detail
session-service.ts
✅ Fixed — phone removed from session query columns
MED-14
Env vars validated lazily (runtime crash)
env.ts
✅ Fixed — requireEnv() validates at startup
ID
Issue
File
Status
LOW-1
Impersonation session 1h without sliding expiry
session-service.ts
✅ Fixed — reduced to 30 min, fixed expiry (no sliding)
LOW-2
No session enumeration timing protection
session-service.ts
✅ Fixed — session ID format validation (64 hex chars) before DB query
LOW-3
Google token refresh not validated before storing
google-client.ts
✅ Fixed — validates access_token exists before persisting
LOW-4
Unvalidated query params in profile routes
routes/teacher/profile.ts
✅ Fixed — Zod schemas with z.coerce.number().int().min(1).max(365)
LOW-5
request.body as any in student update route
routes/teacher/students-mgmt.ts
✅ Fixed — uses updateStudentSchema.parse()
LOW-6
Seed SQL files in git working directory
.gitignore
✅ Fixed — seed-*.sql added to .gitignore
LOW-7
Notification logs not cleaned by retention worker
data-retention-worker.ts
✅ Fixed — cleanup added matching contact_log retention
LOW-8
Drive files marked deleted but not actually removed
student-data-erasure.ts
✅ Fixed — GoogleDriveService.trashFile() called post-transaction
The audit also confirmed strong security in several areas:
SQL Injection: Drizzle ORM parameterizes all queries safely (the one sql.raw() finding is now fixed)
XSS: React safe rendering throughout, react-markdown sanitizes by default, no dangerouslySetInnerHTML
Command Injection: No exec(), spawn(), or child_process usage with user input
Path Traversal: No filesystem operations with user input (Google Drive used instead)
Stripe Webhook Signature: constructEvent() properly verifies signatures (secret now required)
Credit Idempotency: idempotencyKey with partial UNIQUE index prevents duplicate credits
Cookie Security: httpOnly: true, sameSite: 'lax', secure in production
Rate Limiting: Auth endpoints properly rate-limited (magic link 5/hour, verify 10/min, dev-login 5/hour)
Audit Trail: Comprehensive append-only audit logging with IP, user agent, request ID
Soft-Delete: 7 tables use deletedAt pattern with notDeleted() helper
Zod Validation: 33+ schema files validate all API input on both frontend and backend
Security Headers: @fastify/helmet provides HSTS, X-Frame-Options, X-Content-Type-Options, etc.
OAuth CSRF: State parameter with Redis-backed single-use tokens
GDPR Article
Status
Notes
Art. 5 — Data Minimization
Compliant
Avatar leak fixed, phone removed from sessions, audit PII redacted
Art. 7 — Consent
Compliant
Legal doc acceptance tracked, pre-booking consent required
Art. 15 — Right to Access
Compliant
Export includes all data correctly
Art. 17 — Right to Erasure
Compliant
Deadline warnings logged, soft-deleted students anonymized, session_payment included
Art. 25 — Data Protection by Design
Compliant
IDOR fixes, env validation, security headers, magic link hashing
Art. 32 — Security of Processing
Compliant
SQL injection fixed, IDOR fixed, databases secured, HTTPS enforced
Art. 5 — Storage Limitation
Compliant
Retention tiers well-designed, notification logs cleanup added
After remediation, verify with:
IDOR testing: For every teacher endpoint, attempt access with a different teacher’s session
SQL injection: Fuzz all sql.raw() and sql template usages
Webhook replay: Send duplicate webhooks with same event ID concurrently
OAuth CSRF: Attempt callback without valid state parameter
Rate limit bypass: Test concurrent requests from different IPs
GDPR erasure: Run full erasure and verify ALL tables are cleaned
Soft-delete leak: Query sessions after student erasure, verify no PII returned