From 2ad78edca12f1a43c566ffab9e93b3ed26426a6c Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Mon, 16 Mar 2026 20:13:39 -0400 Subject: [PATCH 1/2] Release notes --- docs/install.md | 60 ++++++++++++++++++++++++------------------------ docs/releases.md | 20 ++++++++++------ 2 files changed, 43 insertions(+), 37 deletions(-) diff --git a/docs/install.md b/docs/install.md index ed9af639..e591e6dd 100644 --- a/docs/install.md +++ b/docs/install.md @@ -30,37 +30,37 @@ deb/rpm packages. === "x86_64/amd64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_amd64.tar.gz - tar zxvf ntfy_2.19.1_linux_amd64.tar.gz - sudo cp -a ntfy_2.19.1_linux_amd64/ntfy /usr/local/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.1_linux_amd64/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_amd64.tar.gz + tar zxvf ntfy_2.19.2_linux_amd64.tar.gz + sudo cp -a ntfy_2.19.2_linux_amd64/ntfy /usr/local/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.2_linux_amd64/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` === "armv6" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_armv6.tar.gz - tar zxvf ntfy_2.19.1_linux_armv6.tar.gz - sudo cp -a ntfy_2.19.1_linux_armv6/ntfy /usr/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.1_linux_armv6/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_armv6.tar.gz + tar zxvf ntfy_2.19.2_linux_armv6.tar.gz + sudo cp -a ntfy_2.19.2_linux_armv6/ntfy /usr/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.2_linux_armv6/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` === "armv7/armhf" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_armv7.tar.gz - tar zxvf ntfy_2.19.1_linux_armv7.tar.gz - sudo cp -a ntfy_2.19.1_linux_armv7/ntfy /usr/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.1_linux_armv7/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_armv7.tar.gz + tar zxvf ntfy_2.19.2_linux_armv7.tar.gz + sudo cp -a ntfy_2.19.2_linux_armv7/ntfy /usr/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.2_linux_armv7/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` === "arm64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_arm64.tar.gz - tar zxvf ntfy_2.19.1_linux_arm64.tar.gz - sudo cp -a ntfy_2.19.1_linux_arm64/ntfy /usr/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.1_linux_arm64/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_arm64.tar.gz + tar zxvf ntfy_2.19.2_linux_arm64.tar.gz + sudo cp -a ntfy_2.19.2_linux_arm64/ntfy /usr/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.19.2_linux_arm64/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` @@ -116,7 +116,7 @@ Manually installing the .deb file: === "x86_64/amd64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_amd64.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_amd64.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -124,7 +124,7 @@ Manually installing the .deb file: === "armv6" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_armv6.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_armv6.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -132,7 +132,7 @@ Manually installing the .deb file: === "armv7/armhf" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_armv7.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_armv7.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -140,7 +140,7 @@ Manually installing the .deb file: === "arm64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_arm64.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_arm64.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -150,28 +150,28 @@ Manually installing the .deb file: === "x86_64/amd64" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_amd64.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_amd64.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` === "armv6" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_armv6.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_armv6.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` === "armv7/armhf" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_armv7.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_armv7.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` === "arm64" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_linux_arm64.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_linux_arm64.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` @@ -213,18 +213,18 @@ pkg install go-ntfy ## macOS The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well. -To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_darwin_all.tar.gz), +To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_darwin_all.tar.gz), extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`). If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at `~/Library/Application Support/ntfy/client.yml` (sample included in the tarball). ```bash -curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_darwin_all.tar.gz > ntfy_2.19.1_darwin_all.tar.gz -tar zxvf ntfy_2.19.1_darwin_all.tar.gz -sudo cp -a ntfy_2.19.1_darwin_all/ntfy /usr/local/bin/ntfy +curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_darwin_all.tar.gz > ntfy_2.19.2_darwin_all.tar.gz +tar zxvf ntfy_2.19.2_darwin_all.tar.gz +sudo cp -a ntfy_2.19.2_darwin_all/ntfy /usr/local/bin/ntfy mkdir ~/Library/Application\ Support/ntfy -cp ntfy_2.19.1_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml +cp ntfy_2.19.2_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml ntfy --help ``` @@ -245,7 +245,7 @@ brew install ntfy The ntfy server and CLI are fully supported on Windows. You can run the ntfy server directly or as a Windows service. To install, you can either -* [Download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.19.1/ntfy_2.19.1_windows_amd64.zip), +* [Download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.19.2/ntfy_2.19.2_windows_amd64.zip), extract it and place the `ntfy.exe` binary somewhere in your `%Path%`. * Or install ntfy from the [Scoop](https://scoop.sh) main repository via `scoop install ntfy` diff --git a/docs/releases.md b/docs/releases.md index eb702f4d..b8ff0ca4 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -6,12 +6,23 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release | Component | Version | Release date | |------------------|---------|--------------| -| ntfy server | v2.19.1 | Mar 15, 2026 | +| ntfy server | v2.19.2 | Mar 16, 2026 | | ntfy Android app | v1.24.0 | Mar 5, 2026 | | ntfy iOS app | v1.3 | Nov 26, 2023 | Please check out the release notes for [upcoming releases](#not-released-yet) below. +### ntfy server v2.19.2 +Released March 16, 2026 + +This is another small bugfix release for PostgreSQL, avoiding races between primary and read replica, as well as to +further reduce primary load. + +**Bug fixes + maintenance:** + +* Fix race condition in web push subscription causing FK constraint violation when concurrent requests hit the same endpoint +* Route authorization query to read-only database replica to reduce primary database load + ## ntfy server v2.19.1 Released March 15, 2026 @@ -1787,9 +1798,4 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release ## Not released yet -### ntfy server v2.20.x (UNRELEASED) - -**Bug fixes + maintenance:** - -* Fix race condition in web push subscription causing FK constraint violation when concurrent requests hit the same endpoint -* Route authorization query to read-only database replica to reduce primary database load +Nothing. \ No newline at end of file From d9efe50848189115616dd17087d8bd1c26f9be51 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Mon, 16 Mar 2026 21:03:33 -0400 Subject: [PATCH 2/2] Email validation --- docs/releases.md | 6 +++++- server/errors.go | 1 + server/server.go | 4 ++++ server/server_test.go | 24 ++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/releases.md b/docs/releases.md index b8ff0ca4..44b42243 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1798,4 +1798,8 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release ## Not released yet -Nothing. \ No newline at end of file +### ntfy server v2.20.x (UNRELEASED) + +**Bug fixes + maintenance:** + +* Reject invalid e-mail addresses (e.g. multiple comma-separated recipients) with HTTP 400 diff --git a/server/errors.go b/server/errors.go index 7a120e5b..77caf239 100644 --- a/server/errors.go +++ b/server/errors.go @@ -142,6 +142,7 @@ var ( errHTTPBadRequestTemplateFileNotFound = &errHTTP{40047, http.StatusBadRequest, "invalid request: template file not found", "https://ntfy.sh/docs/publish/#message-templating", nil} errHTTPBadRequestTemplateFileInvalid = &errHTTP{40048, http.StatusBadRequest, "invalid request: template file invalid", "https://ntfy.sh/docs/publish/#message-templating", nil} errHTTPBadRequestSequenceIDInvalid = &errHTTP{40049, http.StatusBadRequest, "invalid request: sequence ID invalid", "https://ntfy.sh/docs/publish/#updating-deleting-notifications", nil} + errHTTPBadRequestEmailAddressInvalid = &errHTTP{40050, http.StatusBadRequest, "invalid request: invalid e-mail address", "https://ntfy.sh/docs/publish/#e-mail-notifications", nil} errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil} errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil} errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil} diff --git a/server/server.go b/server/server.go index 075d3079..71f39357 100644 --- a/server/server.go +++ b/server/server.go @@ -122,6 +122,7 @@ var ( fileRegex = regexp.MustCompile(`^/file/([-_A-Za-z0-9]{1,64})(?:\.[A-Za-z0-9]{1,16})?$`) urlRegex = regexp.MustCompile(`^https?://`) phoneNumberRegex = regexp.MustCompile(`^\+\d{1,100}$`) + emailAddressRegex = regexp.MustCompile(`^[^\s,;]+@[^\s,;]+$`) //go:embed site webFs embed.FS @@ -1163,6 +1164,9 @@ func (s *Server) parsePublishParams(r *http.Request, m *model.Message) (cache bo m.Icon = icon } email = readParam(r, "x-email", "x-e-mail", "email", "e-mail", "mail", "e") + if email != "" && !emailAddressRegex.MatchString(email) { + return false, false, "", "", "", false, "", errHTTPBadRequestEmailAddressInvalid + } if s.smtpSender == nil && email != "" { return false, false, "", "", "", false, "", errHTTPBadRequestEmailDisabled } diff --git a/server/server_test.go b/server/server_test.go index 71743638..cb20cbda 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -1543,6 +1543,30 @@ func TestServer_PublishEmailNoMailer_Fail(t *testing.T) { }) } +func TestServer_PublishEmailAddressInvalid(t *testing.T) { + forEachBackend(t, func(t *testing.T, databaseURL string) { + s := newTestServer(t, newTestConfig(t, databaseURL)) + s.smtpSender = &testMailer{} + addresses := []string{ + "test@example.com, other@example.com", + "invalidaddress", + "@nope", + "nope@", + } + for _, email := range addresses { + response := request(t, s, "PUT", "/mytopic", "fail", map[string]string{ + "E-Mail": email, + }) + require.Equal(t, 400, response.Code, "expected 400 for email: %s", email) + } + // Valid address should succeed + response := request(t, s, "PUT", "/mytopic", "success", map[string]string{ + "E-Mail": "test@example.com", + }) + require.Equal(t, 200, response.Code) + }) +} + func TestServer_PublishAndExpungeTopicAfter16Hours(t *testing.T) { forEachBackend(t, func(t *testing.T, databaseURL string) { t.Parallel()