Files
xamxam/nginx/HTACCESS_TO_NGINX.md
Théophile Gervreau-Mercier a2b1ff5f41 security: fix all HIGH priority items from TODO.SECURITY.md
Items resolved:
- #3 (HIGH): Move file uploads outside webroot to STORAGE_ROOT (/var/www/posterg/storage).
  Uploads were previously stored in public/admin/actions/data/ which is web-accessible.
- #4 (HIGH): Align file paths and add media.php controller.
  DB paths are now storage-relative (theses/YEAR/ID/file, covers/file).
  New public/media.php serves files with path-traversal jail, MIME allow-list,
  and proper caching headers. memoire.php and search.php updated to use /media.php?path=.
  Also fixed: cover images were never recorded in thesis_files (broken INSERT).
- #5 (HIGH): RateLimit::getClientIdentifier() now uses REMOTE_ADDR only.
  HTTP_X_FORWARDED_FOR and HTTP_CLIENT_IP are attacker-controlled headers that
  allowed unlimited rate-limit bypass by rotating spoofed IPs.
- #6 (HIGH): Port public/admin/.htaccess security rules to nginx/posterg.conf.
  Apache .htaccess directives are silently ignored by nginx; none were active.
  CSP added to /admin/ location block, .log file denial added globally,
  autoindex off made explicit. Documented in nginx/HTACCESS_TO_NGINX.md.

Supporting changes:
- config/bootstrap.php: add STORAGE_ROOT constant
- nginx/SECURITY_HEADERS.md: updated to reflect admin CSP and pending public CSP
- docs/TODO.SECURITY.md: items #3-6 moved to resolved; priority order updated
2026-02-08 14:01:45 +01:00

2.2 KiB

.htaccess → nginx migration (item #6)

Problem: public/admin/.htaccess contained Apache-specific security directives that nginx silently ignores. None of the rules were active in production.


Rules migrated into nginx/posterg.conf

Apache .htaccess rule nginx equivalent Location
Header always set X-Frame-Options "SAMEORIGIN" add_header X-Frame-Options "SAMEORIGIN" always; main server block (already present)
Header always set X-Content-Type-Options "nosniff" add_header X-Content-Type-Options "nosniff" always; main server block (already present)
Header always set X-XSS-Protection "1; mode=block" Intentionally omitted — deprecated & counterproductive; see SECURITY_HEADERS.md
Header always set Referrer-Policy "strict-origin-when-cross-origin" add_header Referrer-Policy "strict-origin-when-cross-origin" always; main server block (already present)
Header always set Content-Security-Policy "..." add_header Content-Security-Policy "..." always; /admin/ location block (added)
Options -Indexes autoindex off; /admin/ location block (added; nginx default is off, explicit for clarity)
<FilesMatch "^\."> Require all denied location ~ /\.(?!well-known).* deny main server block (already present)
<FilesMatch "(composer\.(json|lock)|error\.log)$"> Require all denied location ~* \.(md|txt|sql|sh|json|gitignore)$ deny + location ~* \.log$ deny main server block (log rule added)
php_flag display_errors Off Handled by config/bootstrap.php (ini_set('display_errors', '0')) PHP
php_flag log_errors On Handled by config/bootstrap.php (ini_set('log_errors', '1')) PHP
php_value error_log error.log Handled by config/bootstrap.php; should use absolute path (item #9) PHP

Status of public/admin/.htaccess

The file is now dead code on this nginx server. It has been left in place (harmless) so it would still work if the project were ever tested behind Apache (e.g., php -S built-in server doesn't read it either). All security rules it previously attempted to set are now enforced by nginx directly.


Added: 2026-02-08 — security item #6