Review date: 2026-06-19. Scope: fixit-pr3/fixit-backend/ + fixit-pr3/fixit-frontend/.
| Area | Rating | Notes |
|---|---|---|
| SQL injection | Pass | PDO prepared statements only in all Models |
| Authentication | Pass (after fixes) | bcrypt, JWT, admin self-registration blocked |
| Authorization | Pass | RoleGuard + per-resource ownership checks |
| Input validation | Pass (after fixes) | Field validation, status enum whitelist |
| CORS | Pass (after fixes) | Explicit CORS_ORIGIN required; no wildcard default |
| Transport | Deploy | HTTPS required in production (configure at host) |
| Token storage | Acceptable | sessionStorage + short JWT TTL; httpOnly cookies recommended for v2 |
| Rate limiting | Basic | IP-based on auth endpoints (10/min); upgrade to Redis in scale |
- Admin self-registration —
POST /auth/registeracceptedrole: admin. Now limited tocustomerandprovider. - HTML escaping stored in DB —
htmlspecialcharson write corrupted data and is not a substitute for parameterized queries. Replaced withcleanText()normalization. - Permissive CORS default —
*fallback removed; server fails fast withoutCORS_ORIGIN. - Weak boot secrets —
JWT_SECRETmust be ≥32 chars; required DB env vars validated at startup. - Mock user data in frontend build —
public/mock/removed so production builds do not ship seed credentials.
- Missing security headers — Added
X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-Policy,Cache-Control: no-store. - No auth rate limiting — Added 10 attempts/minute per IP on login/register.
- Weak password policy — Minimum 8 characters with letters and numbers.
- KYC filename injection —
basename()+ character whitelist on uploaded filename metadata. - Role guards on provider mutations —
PUT/DELETE /providers/:idnow require provider or admin role at middleware level.
- JWT in sessionStorage — Vulnerable to XSS; Vue escapes template output by default. For hardened production, migrate to httpOnly secure cookies + CSRF token.
- No refresh token rotation — 7-day JWT; reduce
JWT expinAuthController::issueToken()for production. - File-based rate limit — Works on single server; use Redis/Memcached behind load balancer.
- No audit logging — Add structured logs for auth failures and admin actions in production.
- KYC upload is metadata-only — No real file storage; implement virus scanning + private object storage before production KYC.
- Set
APP_DEBUG=false - Generate
JWT_SECRET≥ 32 random bytes (openssl rand -base64 48) - Set
CORS_ORIGINto exact frontend URL(s), comma-separated if multiple - MySQL user with least privilege (not root)
- TLS termination at reverse proxy (nginx/Caddy/Render)
- Import
schema.sql+seed.sql; change all seed passwords - Restrict
composer install --no-devin production - Enable PHP
opcacheand disabledisplay_errors
- Set
VITE_API_URLto HTTPS API URL at build time - Serve
dist/with CSP headers (see below) - Never commit
.envwith production secrets - Rebuild after any API URL change (Vite inlines env at build)
Content-Security-Policy: default-src 'self'; connect-src 'self' https://your-api.example.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'
- Messages encrypted client-side with AES-256-GCM before upload
- Server stores ciphertext only — cannot read message bodies
- Each user has RSA-2048 keypair; private key wrapped with PIN-derived AES key (PBKDF2, 310k iterations)
- Job AES keys distributed per participant via RSA-OAEP
- New device: user must enter PIN to unwrap private key and decrypt messages
- Harm review: client screens content pre-encrypt; flagged messages queue metadata (categories + SHA-256 hash) for admin — not plaintext
All queries use bound parameters in:
src/Models/UserModel.phpsrc/Models/ProviderModel.phpsrc/Models/BookingModel.phpsrc/Models/ReviewModel.phpsrc/Models/MessageModel.phpsrc/Models/CategoryModel.php
Register/Login → bcrypt verify → JWT (HS256, 7d) → JwtAuth middleware → RoleGuard → Controller