Warn that deny-all takes unrelated notifications down with it #17

Merged
branderson merged 1 commits from denyall-consequences into main 2026-08-29 01:00:28 -07:00
Collaborator

Summary generated by LLM agent

The ntfy section recommended a server-wide setting for one topic's sake and then described only what it did to that topic. Applied to a real server, auth-default-access: deny-all closes every other topic too — anything that was subscribing anonymously stops, including the web app and the phone app on topics that have nothing to do with dejima.

One unreadable topic denies the whole connection

This is the part that makes a single missing entry look like a broken server. The apps subscribe to all of a server's topics over one request — /topic-a,topic-b,topic-c/ws — and ntfy authorises it by looping the list and returning 403 for the entire request on the first topic the account cannot read:

for _, t := range topics {
    if err := s.userManager.Authorize(u, t.ID, perm); err != nil {
        return errHTTPForbidden.With(t)
    }
}

So one stray subscription with no grant takes down alerts and notifications together, both of which were configured correctly.

The symptom points at the wrong component

A denied subscribe fails the WebSocket upgrade, which the clients report as WebSocketNotSupportedException or websocket upgrade failed with HTTP 403 — which reads as a broken reverse proxy rather than as the ACL doing what it was told. Publishing keeps working throughout, because that path carries a token, so a test alert arriving proves nothing about whether anyone can read it.

Also stated, because it is the natural worry

An everyone grant does cover a logged-in user, on topics where that user has no entry of their own. authorizeTopicAccess resolves permissions from the specific user's rows and everyone's, preferring the user's own and then the longer topic pattern. So logging in to read the alert topic does not cost you the anonymous grant on dejima's — which is the thing the previous revision left a reader to guess at.

Placement

The warning sits between the server.yml block and the commands, so a reader meets it before running them rather than after — a test holds it in that order. And the grant block hands the reading account read-only on "*" rather than on the alert topic alone: the narrow version is exactly the configuration that took everything else down, so it should not be the one somebody pastes.

Added

Grant the other topics back, with a wildcard where there are several. Log the subscribers in, and where that setting lives in each app. curl commands that separate the ACL from the proxy and then find a stray, without involving WebSockets at all. And the ?auth= query parameter, since a browser cannot set an Authorization header on a WebSocket — its value is base64 of the literal Basic followed by base64 of user:pass, verified by decoding ntfy's own example.

Behaviour checked against ntfy's source rather than inferred: the docs do not cover either the multi-topic authorisation loop or the everyone-versus-authenticated-user precedence.

Tested: 83 passing.

**Summary generated by LLM agent** The ntfy section recommended a server-wide setting for one topic's sake and then described only what it did to that topic. Applied to a real server, `auth-default-access: deny-all` closes every other topic too — anything that was subscribing anonymously stops, including the web app and the phone app on topics that have nothing to do with dejima. ## One unreadable topic denies the whole connection This is the part that makes a single missing entry look like a broken server. The apps subscribe to all of a server's topics over one request — `/topic-a,topic-b,topic-c/ws` — and ntfy authorises it by looping the list and returning 403 for the entire request on the first topic the account cannot read: ```go for _, t := range topics { if err := s.userManager.Authorize(u, t.ID, perm); err != nil { return errHTTPForbidden.With(t) } } ``` So one stray subscription with no grant takes down alerts and notifications together, both of which were configured correctly. ## The symptom points at the wrong component A denied subscribe fails the WebSocket upgrade, which the clients report as `WebSocketNotSupportedException` or `websocket upgrade failed with HTTP 403` — which reads as a broken reverse proxy rather than as the ACL doing what it was told. Publishing keeps working throughout, because that path carries a token, so a test alert arriving proves nothing about whether anyone can read it. ## Also stated, because it is the natural worry An `everyone` grant does cover a logged-in user, on topics where that user has no entry of their own. `authorizeTopicAccess` resolves permissions from the specific user's rows *and* `everyone`'s, preferring the user's own and then the longer topic pattern. So logging in to read the alert topic does not cost you the anonymous grant on dejima's — which is the thing the previous revision left a reader to guess at. ## Placement The warning sits between the `server.yml` block and the commands, so a reader meets it before running them rather than after — a test holds it in that order. And the grant block hands the reading account `read-only` on `"*"` rather than on the alert topic alone: the narrow version is exactly the configuration that took everything else down, so it should not be the one somebody pastes. ## Added Grant the other topics back, with a wildcard where there are several. Log the subscribers in, and where that setting lives in each app. `curl` commands that separate the ACL from the proxy and then find a stray, without involving WebSockets at all. And the `?auth=` query parameter, since a browser cannot set an `Authorization` header on a WebSocket — its value is base64 of the literal `Basic ` followed by base64 of `user:pass`, verified by decoding ntfy's own example. Behaviour checked against ntfy's source rather than inferred: the docs do not cover either the multi-topic authorisation loop or the `everyone`-versus-authenticated-user precedence. Tested: 83 passing.
dejima-agent force-pushed denyall-consequences from 5cd5fba7d6 to 8c142897d8 2026-08-29 00:29:44 -07:00 Compare
dejima-agent added 1 commit 2026-08-29 00:43:26 -07:00
The page recommended a server-wide setting for one topic's sake and described
only the effect on that topic. Applied to a real server it closes every other
one, and one unreadable topic denies the rest along with it: the apps subscribe
to all of a server's topics over a single request, and ntfy authorises that by
looping the list and returning 403 for the whole request on the first topic the
account cannot read. Nothing reports which one. So a stray subscription takes
down alerts and notifications together, both correctly configured, and it reads
as the server being broken.

The symptom hides it further. A denied subscribe fails the WebSocket upgrade,
which the apps report as WebSocketNotSupportedException or a 403 on the upgrade
- which points at the reverse proxy. Publishing keeps working the whole time,
since that path carries a token, so a test alert arriving proves nothing about
whether anybody can read it.

The warning sits between the setting and the commands, where a reader meets it
before running them rather than after, and a test holds it in that order. The
grant block gives the reading account read on "*" rather than on the alert
topic alone - the narrow version is the configuration that took everything
else down, so the block somebody pastes should not be it.

Also stated, because it is the natural worry and the answer is not obvious: an
everyone grant does cover a logged-in user, on topics where that user has no
entry of their own. Logging in to read the alert topic does not cost you the
anonymous grant on dejima's.

Behaviour read from ntfy's source. The documentation covers neither the
multi-topic authorisation loop nor the everyone-versus-authenticated precedence.
dejima-agent force-pushed denyall-consequences from 8c142897d8 to b2e578c7e1 2026-08-29 00:43:26 -07:00 Compare
dejima-agent changed title from Say what deny-all does to the rest of the server to Warn that deny-all takes unrelated notifications down with it 2026-08-29 00:43:26 -07:00
branderson approved these changes 2026-08-29 01:00:23 -07:00
branderson merged commit b9221c4dba into main 2026-08-29 01:00:28 -07:00
Sign in to join this conversation.