Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ RUN { \
echo 'opcache.fast_shutdown=1'; \
echo 'realpath_cache_size=4M'; \
echo 'realpath_cache_ttl=600'; \
echo 'expose_php=Off'; \
} > /usr/local/etc/php/conf.d/scriptor-demo.ini

# Composer + a writable home for it (composer.json may also pull dev
Expand Down
24 changes: 23 additions & 1 deletion docker/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,30 @@ http {
tcp_nopush on;
keepalive_timeout 65;
client_max_body_size 16M;
server_tokens off;

access_log /dev/stdout;

# Security headers shared by every location. nginx's add_header is
# shadowed when an inner location declares its own add_header — so
# locations that need a per-location header (e.g. /uploads/ for
# Cache-Control) repeat the security set below.
map $sent_http_content_type $scriptor_security_csp {
default "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: blob:; font-src 'self' https://cdn.jsdelivr.net data:; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'";
}

server {
listen 80;
server_name _;
root /var/www/scriptor/public;
index index.php;

add_header Content-Security-Policy $scriptor_security_csp always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), interest-cohort=()" always;

# Dotfiles (.git lives outside the webroot anyway, but .htaccess
# is here, .env would be if it existed).
location ~ /\.(?!well-known) {
Expand All @@ -49,7 +64,14 @@ http {
# (path includes itemId/fieldId/<safe-filename>).
location /uploads/ {
expires 30d;
add_header Cache-Control "public, immutable";
add_header Cache-Control "public, immutable" always;
# Re-declare security headers because the parent's
# add_header set is shadowed when this block has its own.
add_header Content-Security-Policy $scriptor_security_csp always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), interest-cohort=()" always;
}

# Front controller. We deliberately leave `$uri/` OUT of the
Expand Down
14 changes: 14 additions & 0 deletions editor/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@

if (! isset($_SESSION)) {
session_name('IMSESSID');
// X-Forwarded-Proto so the cookie picks up Secure when sitting
// behind a TLS-terminating reverse proxy (nginx-proxy on Hetzner,
// Caddy on the live site). Falls back to direct HTTPS / off for
// local dev (http://scriptor.cms via ServBay).
$proto = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null;
$secure = $proto === 'https'
|| (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
}

Expand Down