# Security

Vulnerability analysis and resolution status for posterg-website.

> Based on security audit (2026-02-08). All items tracked below.

---

## Resolved

### Infrastructure / Deployment

| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | TLS terminated upstream by reverse proxy. nginx.conf doesn't need to handle TLS directly. |
| 3 | Uploaded files stored inside webroot | 🟠 HIGH | Storage moved to `STORAGE_ROOT` (`/var/www/posterg/storage/`), defined in `config/bootstrap.php`. |
| 4 | File path mismatch — media broken & insecure | 🟠 HIGH | DB paths now storage-relative. New `public/media.php` serves files safely. `memoire.php` and `search.php` use `/media.php?path=…`. Cover recording fixed. |
| 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `src/RateLimit.php` `getClientIdentifier()` uses `REMOTE_ADDR` only. |
| 6 | `.htaccess` rules silently ignored by nginx | 🟠 HIGH | All rules ported to `nginx/posterg.conf`. See `nginx/HTACCESS_TO_NGINX.md`. |
| 13 | Deprecated `X-XSS-Protection` header | 🔵 LOW | Removed from `nginx/posterg.conf`. |

### Frontend / Assets

| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 10 | CDN stylesheet without SRI | 🟡 MEDIUM | CDN will not be used in production. Self-hosted, eliminating supply-chain risk. |

### Code Quality / Defence in Depth

| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 14 | Missing `rel="noreferrer"` on external links | 🔵 LOW | `rel="noopener noreferrer"` applied in `public/admin/thanks.php`. |
| 15 | Unescaped integer outputs | 🔵 LOW | Explicit `(int)` casts added in `public/index.php` and `public/search.php`. |
| 16 | Redundant `DATABASE_PATH` constant | 🔵 LOW | Removed from `config/bootstrap.php`. |

### Admin Panel — Authentication & Sessions

| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 2 | No PHP-level authentication in admin | 🔴 CRITICAL | `src/AdminAuth.php` implements session guard with `password_verify` + `session_regenerate_id`. All admin files call `AdminAuth::requireLogin()`. Credentials in gitignored `config/admin_credentials.php`. No-op when constant absent (dev/cli-server). |
| 8 | Session cookies not hardened | 🟡 MEDIUM | Resolved with #2. `AdminAuth::startSession()` sets `HttpOnly=true`, `SameSite=Strict`, `Secure=true` (off on cli-server), `Path=/admin`, `Lifetime=0`. |

---

## In Progress

| # | Issue | Severity | Status |
|---|-------|----------|--------|
| 7 | LIKE wildcard injection in admin search | 🟡 MEDIUM | Public `Database::searchTheses()` escapes `%` and `_` correctly. Same pattern must be applied to admin search and any other raw LIKE queries. |

---

## Not Yet Implemented

| # | Issue | Severity | Files |
|---|-------|----------|-------|
| 11 | Missing Content-Security-Policy on public pages | 🟡 MEDIUM | `nginx/posterg.conf` → add CSP header to main server block |
| 9 | `error.log` in web-accessible path | 🟡 MEDIUM | `public/admin/actions/formulaire.php` → use absolute path outside webroot |
| 12 | CSV import missing server-side MIME validation | 🟡 MEDIUM | `public/admin/import.php` → add `finfo` MIME check |

---

## Priority Order

1. ~~🔴 CRITICAL~~ — All done (items 1–2)
2. 🟡 **MEDIUM** — Items 7, 9, 11, 12 remaining
3. ~~🔵 LOW~~ — All done (items 13–16)

---

## Good Practices Already in Place

- ✅ SQL injection: all queries use PDO prepared statements
- ✅ XSS output: `htmlspecialchars()` on all user-controlled output
- ✅ CSRF: tokens with `bin2hex(random_bytes(32))`, validated with `hash_equals()`
- ✅ File upload: MIME type validated with `finfo`
- ✅ Input validation: year, IDs, pagination cast to integers
- ✅ LIKE wildcard escaping in public search (`Database::escapeLikeString`)

---

*Last updated: 2026-02-08*
