diff --git a/security/pentest-2026-06-14.md b/security/pentest-2026-06-14.md new file mode 100644 index 0000000..1341347 --- /dev/null +++ b/security/pentest-2026-06-14.md @@ -0,0 +1,54 @@ +# Pentest mini-report — versila22/lima-app — 2026-06-14 + +**Probed URL:** https://limaimpro.duckdns.org/ +**Stack:** React 18 + Vite / npm / PWA=yes (VitePWA autoUpdate, workbox NetworkFirst on API) + Python FastAPI backend +**Counts:** Critical=0 High=1 Medium=2 Low=1 Info=3 + +## Findings + +| Sev | Cat | Title | Location | +|---|---|---|---| +| High | SAST | Hardcoded admin credentials in seed data | `backend/app/main.py:54` | +| Medium | SAST | npm critical/high vulns (devDependencies) — vitest CRITICAL, esbuild/vite/serialize-js HIGH | `package.json` (devDeps) | +| Medium | DAST | CSP `style-src 'unsafe-inline'` — allows injected inline styles | `nginx.conf:16` | +| Low | DAST | `/health/db` and `/health/migrations` leak DB URL prefix + migration tracebacks | `backend/app/main.py:211–237` | +| Info | DAST | Both deployed URLs blocked (403 host_not_allowed) from audit environment — no live DAST possible | Network | +| Info | PWA | SW uses NetworkFirst for API; auth routes cached with 1h TTL — acceptable pattern but auth responses may be served stale offline | `vite.config.ts:55–70` | +| Info | Infra | TLS 1.0/1.1 disabled, TLS 1.2+1.3 enabled — good posture | `limaimpro.duckdns.org:443` | + +## Top 3 fixes +1. **Hardcoded admin credentials** — Replace plaintext passwords in `_SEED_MEMBERS` with hashed values or move seed to a CLI command using env-injected credentials; rotate the `Admin1234!` password immediately if seeded in production. +2. **npm devDep vulnerabilities** — Run `npm audit fix` or pin `vitest ≥ 3.x`, `vite ≥ 6.3.4`, `esbuild ≥ 0.25.3`; these don't affect the production bundle but compromise CI/build environments. +3. **CSP unsafe-inline styles** — Replace `'unsafe-inline'` in `style-src` with a nonce or hash; Tailwind's JIT/purge output is already static — no dynamic inline styles needed in production. + +## Evidence (Critical/High only) + +### [High] Hardcoded admin credentials — `backend/app/main.py:54` +```python +_SEED_MEMBERS = [ + {"email": "admin@lima-impro.fr", "app_role": "admin", "password": "Admin1234!", ...}, + {"email": "marie.leroy@exemple.fr", "password": "Password1!", ...}, + ... # 9 members total with plaintext passwords +] +``` +**Impact:** Anyone with read access to the repo obtains the production admin password; if seeded into prod, immediate admin-level account takeover possible. +**Fix:** Remove plaintext passwords from source; seed via `SEED_ADMIN_PASSWORD` env var or a one-time CLI script; rotate credentials if already seeded to prod. + +## Verified safe +- No `dangerouslySetInnerHTML`, `innerHTML=`, or `eval()` usage in frontend source +- No tokens in localStorage; no client-side JWT handling +- No hardcoded API keys, Stripe keys, or cloud credentials in tracked files +- Backend CORS uses explicit origin allowlist (not wildcard) with `allow_credentials=True` — correctly restricted +- Security headers middleware sets HSTS, nosniff, X-Frame-Options DENY, strict CSP for API responses +- FastAPI docs/redoc disabled in production (`settings.is_development` guard) +- JWT_SECRET has validator that rejects default value in non-dev mode +- `.env.production` contains only a non-sensitive `VITE_API_URL` — no secrets tracked + +## Needs server-side verification +- Confirm `Admin1234!` / `Password1!` credentials have been changed in the live DB (or seed was never triggered in prod) +- Verify `/health/db` and `/health/migrations` endpoints are not publicly reachable without authentication in production (rate-limit or IP-restrict them) +- Confirm CORS_ORIGINS in production Railway env does not include wildcards +- Verify PWA service worker cache headers: ensure auth token cookies are not cached by SW + +## Tools +ran=npm-audit, grep-secrets, openssl-tls; skipped=curl-DAST (host_not_allowed from audit environment — Anthropic egress proxy blocks outbound to duckdns.org)