Connections list: ntfy topics and webhook paths are printed in full, though the form says secret parts are masked #11

Closed
opened 2026-10-01 10:48:13 +00:00 by tiagoagueda · 1 comment
Owner

Found on 2026-09-29 in a read-only review of postulo-apprise at 7637d899d, running AppriseNotifier.summary() from the snapshot against Apprise 1.13.1 for a range of services.

Related to postulo/postulo#5.

The one-line description on Settings → Connections relies on Apprise to know which parts of a URL are secret. Apprise masks what it models as a password or token; it does not mask a path or an ntfy topic. For a custom webhook the path is often the whole credential, and on a public ntfy server the topic name is the only thing protecting the channel. Both are shown in clear, in a field the form describes as never shown again.

  • Where: src/postulo_apprise/notifier.py:123-126 (describe): masked = server.url(privacy=True).split("?", 1)[0].rstrip("/"). The promise is in the help text at src/postulo_apprise/notifier.py:147-149 and in README.md:49-52; the core's contract for summary asks for "every secret part masked" (plugins/base.py:714-715). tests/test_apprise.py:270 asserts that the topic is on the page.
  • What happens: for the lines ntfy://ntfy.sh/my-private-topic, json://hooks.example.org/services/T000/B000/secretpath and mailto://me:pw@example.org, summary() returns ntfy ntfy://ntfy.sh/my-private-topic · JSON json://hooks.example.org/services/T000/B000/secretpath · E-Mail mailto://me:****@example.org. The same holds for xml:// and form:// paths, for the topic-only form ntfy://topic (shown as ntfys://topic), and for the phone numbers of a signal:// line. Tokens Apprise models are masked as intended (tgram://1...1/987654, gotify://host/a...n), and anything in the query string is cut.
  • Who can trigger it: nobody but the owner sees the list (ConnectionListView is narrowed to the signed-in person). The exposure is the owner's own screen: the value is rendered on every visit to the connections page, so it is in a shared screen, a screenshot attached to a bug report, or a page left open, after the person was told it would not be shown back.
  • Why it matters: whoever reads an ntfy topic on a public server can read and publish to that channel, and whoever reads a webhook path can post to it. The promise that connection secrets are never shown again is one the core makes for every plugin; this is the one place it does not hold.
  • Fix: stop repeating the URL. Show the service name and, for a scheme that names a server, the host and port only: nothing from the path, the user part or the query. The topic-only ntfy form needs its own care, because Apprise keeps the topic in host there (server.mode == "cloud"): show the service name alone, or ntfy.sh. For hosted services (Telegram, Discord, Pushover) the service name is enough. Reword the help text and the README to say what is shown, and change the test to assert that the topic is absent.

Done when

  • A test in tests/test_apprise.py asserts that summary() for an ntfy line with a server, an ntfy line with a topic only, and a json:// line with a path contains the service names and hosts and none of the topic or path. It fails today.
  • test_the_connection_form_keeps_the_urls_secret_and_the_list_masks_them asserts that postulo (the topic) is not in the list page.
Found on 2026-09-29 in a read-only review of postulo-apprise at 7637d899d, running `AppriseNotifier.summary()` from the snapshot against Apprise 1.13.1 for a range of services. Related to postulo/postulo#5. The one-line description on Settings → Connections relies on Apprise to know which parts of a URL are secret. Apprise masks what it models as a password or token; it does not mask a path or an ntfy topic. For a custom webhook the path is often the whole credential, and on a public ntfy server the topic name is the only thing protecting the channel. Both are shown in clear, in a field the form describes as never shown again. - **Where:** `src/postulo_apprise/notifier.py:123-126` (`describe`): `masked = server.url(privacy=True).split("?", 1)[0].rstrip("/")`. The promise is in the help text at `src/postulo_apprise/notifier.py:147-149` and in `README.md:49-52`; the core's contract for `summary` asks for "every secret part masked" (`plugins/base.py:714-715`). `tests/test_apprise.py:270` asserts that the topic is on the page. - **What happens:** for the lines `ntfy://ntfy.sh/my-private-topic`, `json://hooks.example.org/services/T000/B000/secretpath` and `mailto://me:pw@example.org`, `summary()` returns `ntfy ntfy://ntfy.sh/my-private-topic · JSON json://hooks.example.org/services/T000/B000/secretpath · E-Mail mailto://me:****@example.org`. The same holds for `xml://` and `form://` paths, for the topic-only form `ntfy://topic` (shown as `ntfys://topic`), and for the phone numbers of a `signal://` line. Tokens Apprise models are masked as intended (`tgram://1...1/987654`, `gotify://host/a...n`), and anything in the query string is cut. - **Who can trigger it:** nobody but the owner sees the list (`ConnectionListView` is narrowed to the signed-in person). The exposure is the owner's own screen: the value is rendered on every visit to the connections page, so it is in a shared screen, a screenshot attached to a bug report, or a page left open, after the person was told it would not be shown back. - **Why it matters:** whoever reads an ntfy topic on a public server can read and publish to that channel, and whoever reads a webhook path can post to it. The promise that connection secrets are never shown again is one the core makes for every plugin; this is the one place it does not hold. - **Fix:** stop repeating the URL. Show the service name and, for a scheme that names a server, the host and port only: nothing from the path, the user part or the query. The topic-only ntfy form needs its own care, because Apprise keeps the topic in `host` there (`server.mode == "cloud"`): show the service name alone, or `ntfy.sh`. For hosted services (Telegram, Discord, Pushover) the service name is enough. Reword the help text and the README to say what is shown, and change the test to assert that the topic is absent. ## Done when - A test in `tests/test_apprise.py` asserts that `summary()` for an ntfy line with a server, an ntfy line with a topic only, and a `json://` line with a path contains the service names and hosts and none of the topic or path. It fails today. - `test_the_connection_form_keeps_the_urls_secret_and_the_list_masks_them` asserts that `postulo` (the topic) is not in the list page.
tiagoagueda added this to the 0.5.0 milestone 2026-10-01 10:48:13 +00:00
Author
Owner

Fixed by pull request #23.

Fixed by pull request #23.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-apprise#11
No description provided.