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
12 changes: 10 additions & 2 deletions .docker/web/nextcloud.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Nextcloud nginx configuration — root installation
# Version 2026-06-09 (official baseline)
# Docker adaptation: TLS terminates at nginx-proxy; PHP-FPM is reached via Docker DNS.
upstream php-handler {
zone php-handler 64k;
resolver 127.0.0.11 valid=10s ipv6=off;
Expand Down Expand Up @@ -39,7 +42,8 @@ server {
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" always;
# TLS terminates at nginx-proxy, which already adds the HSTS header.
#add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# set max upload size and increase upload timeout:
client_max_body_size 20G;
Expand Down Expand Up @@ -87,7 +91,7 @@ server {
include mime.types;
types {
text/javascript mjs;
application/wasm wasm;
#application/wasm wasm; # Already defined in nginx:alpine mime.types.
}

# Specify how to handle directories -- specifying `/index.php$request_uri`
Expand Down Expand Up @@ -138,6 +142,10 @@ server {
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }

# Hide metadata files which would otherwise be served as plain files and
# leak dependency information (composer.json, package.json, core/shipped.json).
location ~ ^/(?:composer\.(?:json|lock)|package(?:-lock)?\.json|core/shipped\.json)$ { return 404; }

# Ensure this block, which passes PHP files to the PHP process, is above the blocks
# which handle static assets (as seen below). If this block is not declared first,
# then Nginx will encounter an infinite rewriting loop when it prepends `/index.php`
Expand Down
23 changes: 23 additions & 0 deletions tests/web/nginx-dns-recovery.bats
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ setup() {
mkdir -p "$workdir/htdocs" "$workdir/includes"
chmod 755 "$workdir"
printf '%s\n' "<?php echo 'healthy';" > "$workdir/htdocs/status.php"
for file in composer.json composer.lock package.json package-lock.json core/shipped.json; do
mkdir -p "$workdir/htdocs/$(dirname "$file")"
printf '%s\n' '{"fixture":true}' > "$workdir/htdocs/$file"
done

# Docker only permits --ip on networks with a user-configured subnet.
# Let Docker select an available subnet instead of hardcoding an address.
Expand Down Expand Up @@ -85,3 +89,22 @@ assert_healthy() {
return 1
}
}

@test "nginx denies direct access to dependency and shipped metadata" {
start_app
docker run -d --name "$web" --network "$network" \
--volume "$workdir/htdocs:/var/www/html:ro" \
--volume "$workdir/includes:/etc/nginx/conf.d/includes:ro" \
"$WEB_IMAGE" >/dev/null

assert_healthy
for file in composer.json composer.lock package.json package-lock.json core/shipped.json; do
run docker exec "$web" wget -S -O /dev/null "http://127.0.0.1/$file"
[ "$status" -ne 0 ] || { echo "$file was served" >&2; return 1; }
[[ "$output" == *"404 Not Found"* ]] || { echo "$file: $output" >&2; return 1; }
done

run docker exec "$web" nginx -t
[ "$status" -eq 0 ] || { echo "$output" >&2; return 1; }
[[ "$output" != *"duplicate extension"* ]] || { echo "$output" >&2; return 1; }
}
Loading