Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions security/pentest-2026-06-14.md
Original file line number Diff line number Diff line change
@@ -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)
Loading