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
40 changes: 40 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3071,6 +3071,46 @@ contacting every contributor individually.
back to the raw message for unrecognised codes so debugging
stays possible. Anti-pattern: surfacing `$e->getMessage()`
directly to operator-facing error banners.
- Adding a `<FilesMatch>` regex pattern in
`docker/apache/sbpp-prod.conf` whose alternation matches a
basename shared by a published browser asset → removed at
#1419. Pre-fix the union carried `api-contract\..*` (intended
to shield some never-existed `api-contract.*` config artifact
at the panel root). `<FilesMatch>` matches the **basename**
regardless of which directory the URL resolves under, so the
regex also denied the published `web/scripts/api-contract.js`
— the chrome JS `core/header.tpl` loads on every page render
to define the `Actions.*` / `Perms.*` namespaces every
`sb.api.call(Actions.PascalName, …)` site depends on. The
panel-runtime symptom under the production Docker image:
password login spins forever (the form's submit handler
`e.preventDefault()`s and `setBusy(submitBtn, true)` BEFORE
`sb.api.call(Actions.AuthLogin, …)` fires; with `Actions`
undefined the call throws and neither `.then` branch ever
releases the spinner), and every panel chrome action button
that drives a JSON action (Notes pane, ban/comm unblock,
admin/mod delete, group-ban dispatcher, server refresh, …)
is dead-on-arrival. Steam login is unaffected because the
OpenID round-trip is server-side redirects with no JS
dependency on `Actions.*` — exactly the asymmetry the bug
reporter saw. **The bug only surfaces under
`docker/Dockerfile.prod` →
`ghcr.io/sbpp/sourcebans-pp:*`** (the dev compose stack
uses a different Apache config; tarball installs don't
ship Apache at all). When extending the deny list, prefer
`<Files "exact-name">` for root-only configs that have a
unique basename, or a path-anchored
`<LocationMatch "^/exact-path$">` block when the basename
could collide with a published asset. Regression guard:
`web/tests/integration/ProdApacheConfigTest.php` runs every
extracted `<FilesMatch>` regex (and every `<Files>`
exact-name) against every published basename under
`web/scripts/` and `web/themes/default/js/` — a match
anywhere fails the build. The local `./sbpp.sh test`
runner stays symmetric with CI because
`docker-compose.yml` bind-mounts `./docker:/var/www/html/docker:ro`
so the test can read the conf from inside the dev
container.
- `openTab()` JS (and the matching `<button onclick="openTab(...)">`
chrome on `core/admin_tabs.tpl`) → the JS handler was dropped with
sourcebans.js at #1123 D1; the buttons did nothing and every pane
Expand Down
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ services:
- vendor:/var/www/html/web/includes/vendor
- cache:/var/www/html/web/cache
- smarty:/var/www/html/web/templates_c
# Read-only mount of the docker/ tree so file-shape integration
# tests under web/tests/integration/ can read the production
# Apache + entrypoint config they're gating without leaving the
# container. CI's `actions/checkout@v4` pulls the full repo, so
# `web/../docker/...` resolves naturally there; this mount keeps
# the local `./sbpp.sh test` runner symmetric with CI.
- ./docker:/var/www/html/docker:ro

db:
image: mariadb:10.11
Expand Down
8 changes: 7 additions & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ sbpp.sh thin wrapper around `docker compose` + common dev tasks

The web container mounts `./web` from the host, with two named-volume
overlays (`vendor/` and `cache/`) so Composer artifacts and Smarty cache
don't leak onto the host filesystem.
don't leak onto the host filesystem. The `./docker` tree is also bind-
mounted read-only at `/var/www/html/docker` so file-shape integration
tests under `web/tests/integration/` can read production-only configs
(`docker/apache/sbpp-prod.conf`, `docker/Dockerfile.prod`, etc.) the
runtime panel never touches; CI's `actions/checkout@v4` pulls the full
repo, so this mount keeps the local `./sbpp.sh test` runner symmetric
with the CI gate.

## Common tasks

Expand Down
20 changes: 17 additions & 3 deletions docker/apache/sbpp-prod.conf
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,23 @@

# `composer.json` / `composer.lock` are checked-in but should never be
# fetched (they reveal exact dependency versions for vulnerability
# scanning). Same for the project's own helpers (`init-recovery.php`,
# `phpstan.neon`, etc.) at the panel root.
<FilesMatch "(?i)^(composer\.(json|lock)|phpstan\..*|phpunit\.xml.*|package\.json|tsconfig\.json|api-contract\..*)$">
# scanning). Same for the project's own root-level helpers
# (`phpstan.neon`, `phpunit.xml.dist`, `package.json`, `tsconfig.json`).
#
# IMPORTANT: every basename listed here must be a panel-root-only
# config file with NO published browser asset sharing the basename.
# `<FilesMatch>` matches the basename component regardless of which
# directory the request resolves to, so a pattern like
# `api-contract\..*` would also match the published `/scripts/api-contract.js`
# the chrome loads from `core/header.tpl` — silently 403'ing it and
# breaking every `sb.api.call(Actions.PascalName, …)` site in the
# panel JS (login spinner runs forever, all admin row actions are
# dead). See #1419 for the bug class. Prefer adding new deny rules
# to a `<Files "exact-name">` block (panel-root only) when the
# protected file has a unique basename, or to a path-anchored
# `<LocationMatch "^/exact-path$">` block when basename collisions
# are a real concern.
<FilesMatch "(?i)^(composer\.(json|lock)|phpstan\..*|phpunit\.xml.*|package\.json|tsconfig\.json)$">
Require all denied
</FilesMatch>

Expand Down
Loading
Loading