Fix the open security and bug issues: what Apprise dials, local schemes, redirects, timeouts, HTML escaping, masking, lock and CI audit #23

Merged
tiagoagueda merged 3 commits from sec2-fixes into main 2026-10-19 09:59:18 +00:00
Owner

One pull request for every open bug/security issue of this repository (the security review of 2026-10-16 and the older audit and review issues), plus this repository's part of #13.

What and why

  • #22 The dev lock is re-resolved against the core's current main (42be52048): pyjwt 2.15.1, urllib3 2.8.0, oauthlib 4.0.0, Apprise 2.0.1. pip-audit==2.10.1 --strict over the exported lock reports nothing.
  • #13 (this repo's part) Both workflows install the core's UV_VERSION (0.12.17) through its installer checked against UV_INSTALLER_SHA256; a new security job audits the exported lock with a pinned pip-audit --strict on every push and every Monday (the test job skips the scheduled run). tests/test_workflows.py fails on an unversioned or unchecked installer or a missing audit. Not done: testing Python 3.12 and 3.13 (the suite still runs on 3.14 only; it would triple a job on a strained runner), and the other five repositories.
  • #21 Apprise is told the words are text (asset body_format and every notify()), so HTML services (Telegram, mail, the API mailers) receive them escaped. A test sends a hostile title through the real Telegram conversion.
  • #10 What a server dials is decided by one table (dialled): mail at smtp_host, XMPP at xmpp_host, a Firebase keyfile= given as an address as an address. A key file given as a path (keyfile=, subfile=) is refused. test() and send() do not reach Apprise for a refused line.
  • #16 ntfy://topic (cloud mode), Mailgun, SparkPost, SMTP2GO, SendPulse and WeChat no longer have their host slot looked up; a server of the person's own is still checked.
  • #11 The connections list shows the service name and, for a server of one's own, host and port; no path, topic, user part or query. Help text and README reworded; test asserts the topic and chat id are absent.
  • #12 syslog, dbus, kde, qt, glib, gio, gnome, macosx, windows, evolution(s), blink1 refused by the scheme as typed (platform independent) and again by the protocol of every server built.
  • #15 Apprise is built with http_redirects=False; redirect=yes is refused. The floor on Apprise rises to 1.11, the first release with that option (checked 1.9.x and 1.10.0: ignored or invalid).
  • #17 rto=/cto= capped at 10 s; one send has an overall 45 s deadline (the work is abandoned in a daemon thread, its sockets being capped); a connection holds at most 20 URLs.
  • #14 The Test result is one ngettext entry. The guard refuses any change to a translation outside Weblate, and a singular gaining a plural counts as one, so the catalogues go in two commits: one drops the two old entries (a string the source lost), the next re-adds the plural empty. postulo-messages guard --base origin/main passes. Weblate's translation memory has the old sentences; French and Portuguese for the new and reworded strings need Weblate before a release. The extract also created the seven catalogues the core's new languages require (empty).

Tests

61 passed (was 30), ruff check and format clean, extract --check, check, guard clean. The new tests fail without the fix (the old module cannot even be imported by them; the behaviours are asserted one by one). pytest -m release is not expected green until Weblate has filled the new strings.

Left open

Nothing among the listed issues. Limits worth knowing: a destination is still resolved twice (check, then Apprise's own connection), as the module docstring says; and "an address-bearing argument the table does not know" is refused only by name (smtp, xmpp, keyfile, subfile), not by a general scan, because Apprise has no reliable marker for such arguments.

Closes #10, #11, #12, #14, #15, #16, #17, #21, #22
Part of #13

🤖 Generated with Claude Code

One pull request for every open `bug`/`security` issue of this repository (the security review of 2026-10-16 and the older audit and review issues), plus this repository's part of #13. ## What and why - **#22** The dev lock is re-resolved against the core's current `main` (`42be52048`): pyjwt 2.15.1, urllib3 2.8.0, oauthlib 4.0.0, Apprise 2.0.1. `pip-audit==2.10.1 --strict` over the exported lock reports nothing. - **#13 (this repo's part)** Both workflows install the core's `UV_VERSION` (0.12.17) through its installer checked against `UV_INSTALLER_SHA256`; a new `security` job audits the exported lock with a pinned `pip-audit --strict` on every push and every Monday (the test job skips the scheduled run). `tests/test_workflows.py` fails on an unversioned or unchecked installer or a missing audit. Not done: testing Python 3.12 and 3.13 (the suite still runs on 3.14 only; it would triple a job on a strained runner), and the other five repositories. - **#21** Apprise is told the words are text (asset `body_format` and every `notify()`), so HTML services (Telegram, mail, the API mailers) receive them escaped. A test sends a hostile title through the real Telegram conversion. - **#10** What a server dials is decided by one table (`dialled`): mail at `smtp_host`, XMPP at `xmpp_host`, a Firebase `keyfile=` given as an address as an address. A key file given as a path (`keyfile=`, `subfile=`) is refused. `test()` and `send()` do not reach Apprise for a refused line. - **#16** `ntfy://topic` (cloud mode), Mailgun, SparkPost, SMTP2GO, SendPulse and WeChat no longer have their host slot looked up; a server of the person's own is still checked. - **#11** The connections list shows the service name and, for a server of one's own, host and port; no path, topic, user part or query. Help text and README reworded; test asserts the topic and chat id are absent. - **#12** `syslog`, `dbus`, `kde`, `qt`, `glib`, `gio`, `gnome`, `macosx`, `windows`, `evolution(s)`, `blink1` refused by the scheme as typed (platform independent) and again by the protocol of every server built. - **#15** Apprise is built with `http_redirects=False`; `redirect=yes` is refused. The floor on Apprise rises to 1.11, the first release with that option (checked 1.9.x and 1.10.0: ignored or invalid). - **#17** `rto=`/`cto=` capped at 10 s; one send has an overall 45 s deadline (the work is abandoned in a daemon thread, its sockets being capped); a connection holds at most 20 URLs. - **#14** The Test result is one `ngettext` entry. The guard refuses any change to a translation outside Weblate, and a singular gaining a plural counts as one, so the catalogues go in two commits: one drops the two old entries (a string the source lost), the next re-adds the plural empty. `postulo-messages guard --base origin/main` passes. Weblate's translation memory has the old sentences; French and Portuguese for the new and reworded strings need Weblate before a release. The extract also created the seven catalogues the core's new languages require (empty). ## Tests 61 passed (was 30), ruff check and format clean, `extract --check`, `check`, `guard` clean. The new tests fail without the fix (the old module cannot even be imported by them; the behaviours are asserted one by one). `pytest -m release` is not expected green until Weblate has filled the new strings. ## Left open Nothing among the listed issues. Limits worth knowing: a destination is still resolved twice (check, then Apprise's own connection), as the module docstring says; and "an address-bearing argument the table does not know" is refused only by name (`smtp`, `xmpp`, `keyfile`, `subfile`), not by a general scan, because Apprise has no reliable marker for such arguments. Closes #10, #11, #12, #14, #15, #16, #17, #21, #22 Part of #13 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The dev lock was resolved on 2026-10-16 or before and carried pyjwt 2.13.0 (fourteen
advisories), urllib3 2.7.0 (three) and oauthlib 3.3.1 (one), and pinned a core commit 210
behind. `uv lock --upgrade` against the core's current main (42be52048) takes them to
pyjwt 2.15.1, urllib3 2.8.0 and oauthlib 4.0.0, and Apprise to 2.0.1; pip-audit 2.10.1
over the exported lock finds nothing.

CI installed whichever uv the installer served that day and audited nothing. It now
installs the core's UV_VERSION through its checksum-checked installer, in both
workflows, and a `security` job audits the exported lock (without the project and without
Postulo, which is not on PyPI) with a pinned `pip-audit --strict` on every push and every
Monday. The test job skips the scheduled run, which has no push to compare translations
against. `tests/test_workflows.py` fails on an unversioned or unchecked installer line and
on the audit going missing.

The floor on Apprise rises to 1.11, the first release that knows `http_redirects`
(see the next commit).

Closes #22
Part of #13 (the other five Python plugin repositories are separate pull requests)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Test button's "Delivered to %(count)d service." / "services." pair becomes one
ngettext entry (#14) under the same singular msgid. Postulo's translation guard keys an
entry by its msgid and refuses any change to a translation made outside Weblate, so a
singular that gains a plural, keeping or emptying what speakers wrote, is refused as a
change. A string the source lost is allowed, and so is a new one with an empty slot: this
commit drops both entries and the next brings the plural back empty, for Weblate to fill
(its translation memory has the old sentences). No translation is written.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Decide what Apprise dials, refuse what acts on the server, and bound every send
All checks were successful
CI / Security audit (pull_request) Successful in 30s
CI / test (pull_request) Successful in 2m20s
664522ad9e
One table now says what a server object really opens (`dialled`), and everything else
follows from it.

- #10: mail is checked at `smtp_host` (the `smtp=` argument), XMPP at `xmpp_host`, and a
  Firebase `keyfile=` given as an address is checked as one; a key file given as a path
  (`keyfile=`, `subfile=`, Vapid's) is refused, as is any other line the policy cannot
  judge. A refused line never reaches Apprise from `test()` or `send()`.
- #16: the host slot is no longer taken for an address where it holds something else:
  `ntfy://topic` (cloud mode), Mailgun, SparkPost, SMTP2GO, SendPulse and WeChat. The
  help text's own example is accepted on a default instance; `ntfy://internal/x` is still
  refused.
- #11: the connections list shows the service name and, for a server of the person's own,
  its host and port. Never a path, a topic, a user part or the query: Apprise masks what
  it models as a password or a token, and a webhook path or an ntfy topic is neither.
  Help text and README say what is shown.
- #12: `syslog`, `dbus`, `kde`, `qt`, `glib`, `gio`, `gnome`, `macosx`, `windows`,
  `evolution(s)` and `blink1` are refused by the scheme as typed, so the refusal does not
  depend on what the platform loads, and again on every server Apprise built (aliases).
- #15: Apprise is built with `http_redirects=False` and a line that turns redirects back
  on (`redirect=yes`) is refused with a sentence naming the line. README says so.
- #17: a line's `rto=` and `cto=` are capped at ten seconds, one send has an overall
  deadline of 45 seconds (the work is abandoned in a daemon thread, its sockets being
  capped), and a connection holds at most twenty URLs.
- #21: Apprise is told the words are text, in the asset and on every `notify()`, so the
  services that send HTML receive them escaped. A hostile page title cannot become markup
  in Telegram or in mail.
- #14: the Test button's result is one `ngettext` entry, and the catalogues are extracted
  (English source and msgids only; the translations come from Weblate).

Closes #10, #11, #12, #14, #15, #16, #17, #21

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
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!23
No description provided.