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
77 changes: 77 additions & 0 deletions security/pentest-2026-06-19.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Pentest mini-report — versila22/lima-app — 2026-06-19

**Probed URL:** https://limaimpro.duckdns.org/
**Stack:** React 18 / Vite + FastAPI (Python) / Nginx / No PWA
**Counts:** Critical=0 High=4 Medium=4 Low=1 Info=1

## Findings

| Sev | Cat | Title | Location |
|---|---|---|---|
| High | SAST | Hardcoded plaintext admin+member passwords in seed data | backend/app/main.py:49–57 |
| High | SCA | serialize-javascript RCE via crafted RegExp/Date objects | package.json → @rollup/plugin-terser |
| High | SCA | undici TLS certificate validation bypass via SOCKS5 ProxyAgent | package.json (transitive) |
| High | SCA | fast-uri path traversal via percent-encoded dot segments | package.json (transitive) |
| Medium | DAST | Unauthenticated /health/db & /health/migrations expose DB URL prefix | backend/app/main.py:202–237 |
| Medium | SAST | User enumeration via distinct auth error messages | backend/app/routers/auth.py:130–131 |
| Medium | DAST | CSP unsafe-inline in style-src weakens XSS protection | nginx.conf:14 |
| Medium | SCA | react-router open redirect via protocol-relative path (//) | package.json |
| Low | SAST | Default CORS_ORIGINS includes localhost origins (no env override) | backend/app/config.py:13 |
| Info | SCA | vitest GHSA-5xrq-8626-4rwp arbitrary file read/exec — devDep only | devDependencies |

## Top 3 fixes
1. **Hardcoded seed passwords** — replace with env vars or randomly-generated one-time passwords; never commit plaintext creds, especially for `admin@lima-impro.fr`
2. **Unauthenticated health endpoints** — add `_: Member = Depends(require_admin)` to `/health/db` and `/health/migrations` routes in `main.py`
3. **User enumeration** — unify login error to `"Identifiants invalides."` regardless of whether email is absent or password is wrong; keep per-reason logging server-side only

## Evidence (High only)

**Hardcoded seed passwords** — `backend/app/main.py:49–57`
```python
{"email": "admin@lima-impro.fr", "app_role": "admin", "password": "Admin1234!", ...},
{"email": "marie.leroy@exemple.fr", "password": "Password1!", ...},
```
Impact: Anyone with repo access knows the admin password; if not rotated post-deploy, full account takeover.
Fix: Seed passwords via env var `SEED_ADMIN_PASSWORD` or use a generated token sent by email on first run.

**DB URL prefix leak** — `backend/app/main.py:202–237`
```python
"async_url_prefix": settings.async_database_url[:60], # unauthenticated GET /health/db
```
Impact: 60-char prefix of DB connection string (host, port, user, start of password) exposed publicly.
Fix: Gate `/health/db` and `/health/migrations` behind `require_admin` dependency.

**User enumeration** — `backend/app/routers/auth.py:130–131`
```python
"email_not_found": "Aucun compte trouvé pour cet email.",
"wrong_password": "Mot de passe incorrect.",
```
Impact: Attacker can enumerate valid email addresses, reducing brute-force search space.
Fix: Return identical `"Identifiants invalides."` for both cases at HTTP response level.

**serialize-javascript RCE** — `npm audit GHSA-5c6j-r48x-rmvq / GHSA-qj8w-gfj5-8c6v`
Impact: Malicious input to bundler pipeline can trigger RCE during build/CI.
Fix: `npm update serialize-javascript` (or update @rollup/plugin-terser to latest).

## Verified safe
- No hardcoded secrets in source files (API keys, bearer tokens, private keys)
- Admin routes protected by `require_admin` dependency (proper role check)
- JWT secret has validator that blocks default in non-dev mode
- Rate limiting on auth endpoints (5/min login, 3/min forgot-password)
- HSTS `max-age=31536000; includeSubDomains` set in nginx.conf
- X-Content-Type-Options, X-Frame-Options DENY set in nginx.conf
- Sensitive file paths (.env, .git) return 403 (CDN layer)
- TLS 1.0/1.1 disabled; TLS 1.2+1.3 enabled
- TLS certificate valid (expires 2026-07-19)
- No XSS sinks (innerHTML, dangerouslySetInnerHTML) found in src/
- No localStorage/sessionStorage token storage found
- Vitest critical (GHSA-5xrq-8626-4rwp) is devDependency only — not in production bundle

## Needs server-side verification
- Full HTTP response headers from live URL (CDN blocked DAST from remote env)
- CORS origin validation on the Railway-deployed API endpoint
- Actual DB URL length — 60-char prefix may or may not include credentials depending on URL format
- Whether SEED_MEMBERS admin password has been rotated in production DB

## Tools
ran=npm-audit, grep-secrets, curl (TLS/headers), openssl; skipped=pnpm-audit (not available in repo)
Loading