Files
projectsend/docker/production/nginx.conf
ignacionelson 9192779ee4 Close four small gaps before the release goes out
Four unrelated one-liners, each already written down and none of them
worth a branch of its own.

The lock was still pinned to the community package's previous commit,
which is the one before it started shipping its own sixteen catalogues.
The mechanism that carries a package's translations to the browser landed
here last week; without this bump the release would have shipped that
mechanism with nothing to carry, and the Custom Assets screen would have
stayed half-English in every language.

The stock `local` disk had `serve` left on. Nothing in this application
writes to it, so the framework's /storage route was a door with nothing
behind it — but it was still a door, and closing it costs one word.

nginx evaluated `\.php$` before `/protected-files/`, so a protected path
ending in .php would have reached the PHP handler instead of streaming
under the sandbox headers that block sets. Not reachable on a default
install — the upload allowlist refuses php and X-Accel paths are UUIDs —
but the guarantee read stronger than it was. `^~` makes it true.

And `.release-build` is now ignored by eslint, so linting after building a
zip stops walking the vendored minified JS inside it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 20:28:16 -03:00

84 lines
3.6 KiB
Nginx Configuration File

# Production server block for the official image.
#
# Kept deliberately close to docker/web/nginx.conf — the dev stack's config
# — because the security properties below were reviewed as a set. Two
# differences, both forced by this
# being one container instead of two:
#
# 1. fastcgi_pass targets 127.0.0.1, not the `app` compose service.
# 2. There is no user/uid coordination to do here, because the Dockerfile
# has already done it: it points the `user` directive in the package's
# own nginx.conf at www-data, so nginx and php-fpm are the same user
# and storage/ needs no group juggling. Without that step nginx runs
# as the alpine package's `nginx` (uid 100) and cannot read what
# php-fpm just wrote — see the comment on that line.
server {
listen 80 default_server;
server_name _;
root /var/www/html/public;
index index.php;
# Uploads arrive in chunks (Uppy resumable), so this caps a single
# chunk, not a file. Raising it does not raise the maximum file size.
client_max_body_size 100m;
# The nginx version number is nobody's business but ours — matches
# docker/web/nginx.conf.
server_tokens off;
# Baseline hardening for every response. `always` so they survive error
# responses too. NOTE: nginx does not merge add_header across levels —
# a location that declares any add_header of its own inherits none of
# these, so /protected-files/ below repeats them deliberately.
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;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Protected file serving: PHP authorizes, nginx streams.
# PHP responds with X-Accel-Redirect: /protected-files/<path>.
#
# This is the only location that returns bytes someone else uploaded,
# so it gets the strictest headers in the file. `sandbox` puts anything
# rendered as a document into an opaque origin with scripts disabled —
# if a payload ever does reach here with a renderable content type
# (FileThumbnailController's allowlist is the primary defence), it
# cannot touch this app's origin or the viewer's session. Images loaded
# as subresources are unaffected: a CSP on a subresource response never
# creates a browsing context, so thumbnails and previews still render.
# `^~` so this prefix beats the `\.php$` regex below: without it a
# protected path ending in .php would be handed to the PHP handler
# instead of streaming under the sandbox headers this block sets.
location ^~ /protected-files/ {
internal;
alias /var/www/html/storage/app/files/;
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 Content-Security-Policy "sandbox; default-src 'none'" always;
}
location ~ \.php$ {
# Never hand a path to PHP-FPM that isn't a real script on disk:
# without this, any URI ending in .php reaches the interpreter and
# PATH_INFO resolution decides what actually runs.
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffer_size 32k;
fastcgi_buffers 8 32k;
}
location ~ /\.(?!well-known) {
deny all;
}
}