mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
- Consolidate 36 markdown files → 14 (plus TODO.md) - Merge overlapping docs into authoritative files: - database.md (from DATABASE_SPECIFICATION + QUICK_SCHEMA_REFERENCE + DATABASE_CONFIG + SETUP) - deployment.md (from SERVER_SETUP + COMPLETE_DEPLOYMENT_GUIDE + DEPLOYMENT_STEPS) - security.md (from SECURITY_ANALYSIS + TODO.SECURITY) - development.md (from DEVELOPMENT_GUIDE + LIVE_RELOAD_SETUP + TEST_CENTRALIZATION) - migration-history.md (from 11 past migration docs) - Standardise all filenames to lowercase - Remove non-doc files (Context.md research notes, chat export) - Remove superseded docs (SECURITY.md pre-SQLite, SECURITY_IMPLEMENTATION, README_SECURE_SEARCH) - Fix stale cross-references
3.9 KiB
3.9 KiB
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
🔴 CRITICAL— All done (items 1–2)- 🟡 MEDIUM — Items 7, 9, 11, 12 remaining
🔵 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 withhash_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