portfolio-page/public/.htaccess

56 lines
3.1 KiB
ApacheConf

Options -MultiViews -Indexes
DirectoryIndex index.html
# ---------------------------------------------------------------------------
# Routing
# ---------------------------------------------------------------------------
RewriteEngine On
# Serve the prerendered pages produced by prerender.js.
# /de/technical is a directory, not a file, so the SPA fallback below would
# otherwise swallow it and ship the empty shell to crawlers.
RewriteCond %{REQUEST_FILENAME} -d
RewriteCond %{REQUEST_FILENAME}/index.html -f
RewriteRule ^(.+?)/?$ $1/index.html [L]
# SPA fallback, only for paths that are neither a file nor a directory.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [QSA,L]
# ---------------------------------------------------------------------------
# Security headers
# ---------------------------------------------------------------------------
<IfModule mod_headers.c>
# script-src keeps 'unsafe-inline' on purpose: Vite injects an inline
# modulepreload polyfill, and index.html carries four inline JSON-LD blocks
# that browsers also evaluate against script-src. Removing it silently breaks
# both the app and the structured data.
# style-src likewise: React sets inline style attributes (--reveal-delay).
Header always set Content-Security-Policy "default-src 'self'; base-uri 'self'; font-src 'self'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.sascha-bach.de; object-src 'none'; frame-ancestors 'none'; form-action 'self' mailto:; upgrade-insecure-requests"
# No 'preload' yet - that directive is practically irreversible and should
# only be added once every subdomain is permanently HTTPS.
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Cross-Origin-Opener-Policy "same-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), browsing-topics=()"
# Agent discovery documents (ported from the former vercel.json).
Header always set Link "</llms.txt>; rel=\"alternate\"; type=\"text/markdown\", </llms-full.txt>; rel=\"alternate\"; type=\"text/markdown\", </AGENTS.md>; rel=\"alternate\"; type=\"text/markdown\", </.well-known/agents.json>; rel=\"agents\"; type=\"application/json\", </.well-known/agent-card.json>; rel=\"agent-card\"; type=\"application/json\", </.well-known/webmcp.json>; rel=\"service-desc\"; type=\"application/json\", </.well-known/mcp.json>; rel=\"service-desc\"; type=\"application/json\""
</IfModule>
# ---------------------------------------------------------------------------
# Keep dotfiles unreachable (.env, .git remnants, editor leftovers).
# This matches the final filename only, so /.well-known/agent-card.json stays
# reachable - its basename does not start with a dot.
# ---------------------------------------------------------------------------
<IfModule mod_authz_core.c>
<FilesMatch "^\.">
Require all denied
</FilesMatch>
</IfModule>