I started using this for various notifs on my home server and it works great!
One thing about the “URL is the secret” design that I ran into though was that the URL paths have a fixed max length. I was trying to generate a 512-bit hex-encoded prefix for my topics for security, but that caused HTTP errors (4xx or 5xx, I forget which) when posting to the topics. In the end I went for a 32-bit prefix instead, which is probably fine but feels a little uncomfortable.
Topics can be 64 bytes long, which is more than enough to make them unguessable. Plus, you get rate limited and then banned very quickly if you do that.
I suggest not using just hex characters, but instead use the full set of allowed characters: -_A-Za-z0-9
My apologies. I am an active reader but not a very active poster. But you are correct. Do you want me to delete the post?
No sweat, just try to engage otherwise–submit neat stories and whatnot. We don’t want to become a dumping ground for growthhackers.
Love this, absolutely something that could fit in my workflow. One issue I’ve found on an Android Phone (One Plus 7 Pro), is that in notification settings there is nothing for default notifications. I only have Max, High, Low and Minimum. This result in no proper notification (i.e no pop up or vibration) for default notifications. I’m not sure if this is an issue with my phone or the app. I downloaded it from F-Droid.
Edit: Using the Google Play version has solved the issue for me. I know there can be some funk with notifications and apps installed outside of Google Play, so not totally unexpected for the F-Droid version to be a little funky. I’ll stick to this version now.
That’s not something I have heard before, and there is no difference between F-Droid and Play in this regard. Odd. Feel free to file an issue if it’s a big issue.
I was under the impression that for proper push notifications, like your service is offering, that there was a dependency on Google Play Services? I’ve not developed for Android, so apologies if that is not correct. I seem to remember there being issues with Proton Mail on F-Droid that seemed to cite that reason before they fixed their notification issues.
Either way, since using the Google Play version it has all worked flawlessly. I will try the F-Droid version again at some point and just make sure it wasn’t something I’d done to stop it working properly.
How quickly we forget @jcs and https://pushover.net/
What do you mean? Pushover is nice, but IIRC it is neither open-source nor self-hostable so there is place for both.
A very happy user of pushover.net here…. it’s cheap, it works fine without having to keep extra connections open and the API is nice to interface with. I have various nagios/icinga instances pushing messages, together with various local apps sending out status messages. Great client-side/mobile app.
For what it’s worth, ntfy doesn’t need any extra connections either in it’s default configuration since it’s using Firebase (like Pushover too). :-)
How do I ensure that only authorized publishers are able to submit notifications to ntfy.sh/mytopic
?
Even if you do that, can’t any middle-box or intermediating agent see mytopic
as part of a request URL?
Ultimately I think this is fine! It just means that ntfy.sh
is basically a demo server, that topic names underneath that domain provide no form of access control or delivery guarantees, and that any actual user will need to self-host with appropriate security measures. Which is, I guess, important to make clear in the docs. Specifically, there is no way for users to “choose a unique topic name” in a way that “keeps messages private”.
an intermediating agent shouldn’t be able to see anything w/ https
you could think of that URL as being analogous to an API key
It would be incorrect to think of any URL as equivalent (in the security sense) to a secret like an API key.
i think any webhook works the same way 😅, as does many cloud file providers that have things like “Anyone with this link” on things like google docs or dropbox… invitations to chats on systems like whatsapp for anyone with the link (or QR code)…
it really all depends on what you do with the URL, and the administrative practices of the people running the site that utilizes this method of security
as long as you don’t misuse it, and it’s using https, and the people running the site do it knowing this is how the security works, it is absolutely secure… and, as long as everyone is aware, as secure as using an unencrypted API key…
The consequence of an unauthorized POST to a Discord webhook is an unauthorized message in an e.g. Discord channel. No downstream consumer would assume that an arbitrary message, whether submitted via webhook or otherwise, is actionable without additional authn/authz. So I don’t think this, or any other kind of webhook, is directly comparable. I could be wrong! If ntfy.sh
topic notifications are understood by consumers to be un-trusted, then no problem, and mea culpa! But that’s not what I took away from the docs.
You seem to be dead set to find a fatal flaw in ntfy, with quite the dedication. :-) I disagree with your assessment that the security of an API key and a secret URL are fundamentally different. And with that fundamental disagreement, our argument comes to an end.
On the wire, a HTTP request with an API key looks like this:
POST /api HTTP/1.1
Authorization: Bearer this-is-a-secret
some message
A request against the ntfy API looks like this (excluding the JSON endpoint, which is more like the above):
POST /this-is-a-secret HTTP/1.1
some message
The only difference is that the secret is in a different spot in the HTTP request.
You made an argument that you cannot rely on TLS: That is completely flawed, because if you cannot trust TLS, then your header-based auth also falls apart.
You also made an argument saying that you cannot rely on people making HTTPS requests. That also applies to the traditional Bearer/Basic/whatever auth.
IMHO, the only valid argument to be made is the one that the HTTP path is cached and prominently displayed by browsers. That’s correct. That makes it less secure.
ntfy is a tool usually used for casual notifications such as “backup done” or “user xyz logged in”. It is geared towards simplicity: simple simple simple. It doesn’t do end-to-end encryption, and the examples are (partially at least) suggesting the use of HTTP over HTTPS (for curl). So yes, it’s not a fort-knox type tool. It’s a handy tool that makes notifying super simple, and if used right, is just as secure as you’d like. But yes, it can also be used in a way that is less secure and that’s okay (for me, and for many users).
I really didn’t want to get into such a (what it feels like) heated discussion. I just wanted to show off a cool thing I did …
Technically, I agree with you that secret links and API keys are the same. I also agree that secret links are a simple, adequate solution for a simple service like ntfy.
When reasoning about the security of secret links, I’d encourage you to also think about the practicalities of how people tend to use links: It’s extremely easy to share them and people see them more as public information. This can be seen in the behavior of some tools that automatically upload and store them elsewhere without encryption, e.g. browser history sync. IIRC this also lead to leaked password reset links when outlook automatically scanned users’ emails for links and added them to the bing index.
I just wanted to show off a cool thing I did …
Sorry! My intent is definitely not to find some fatal flaw. I’m providing feedback as requested:
I’d love feedback to the tool
The consequence of an unauthorized POST to a Discord webhook is an unauthorized message in an e.g. Discord channel.
Which can be catastrophic. I’ve heard many stories of crypto scams that were fueled by a hacked “official” project Discord account sending out a scam phishing link or promoting a pump-and-dump scheme.
Or a user is sending notifications like “The dishwasher is done” or “The Mets lost” and there’s no need for security.
Sending notifications to what? If you can
curl -XPOST -d 'The Mets lost' ntfy.sh/fly
then I can
curl -XPOST -d 'Spam' ntfy.sh/fly
right?
Sure. But who cares? Then I abandon my channel and switch to a new one. They can’t find it, because I’m using https and they can’t MITM anything useful.
If your use case allows you to abandon one topic and switch to a new topic on an ad-hoc basis, that’s great, but it’s not something that most applications are able to do, or really even reliably detect. This is all fine! It just means that any domain that provides unauthenticated write access to topics is necessarily offering a relatively weak form of access control, and can’t be assumed to be trusted by consumer applications. No problem, as long as it’s documented.
I should really write this up as a FAQ, because it comes up so much. :-) First of, thanks for the vivid discussion on ntfy. I love reading feedback on it. Much appreciated.
The original premise of ntfy is that topics are your secret, so if you pick a dumb secret, you cannot expect it to be or remain private. so ntfy.sh/mytopic
is obviously just a demo. Most people in real life pick a unique-ish ID, something like a UUID or another password-like string. Assuming your transport is encrypted, This is no less or more secure than using a an Authorization
header with a bearer token (other than the notable difference that it’s in the server logs and such).
If you want your topics to be password-protected in the traditional sense (username/password or a token), you can do so by using the fine grained access control features (assuming a selfhosted instance), or by reserving a topic (screenshots). This can also be on the official instance, assuming you pay for a plan.
Most people in real life pick a unique-ish ID, something like a UUID or another password-like string. Assuming your transport is encrypted, This is no less or more secure than using a an Authorization header with a bearer token (other than the notable difference that it’s in the server logs and such).
It simply is not true that a URL containing a “unique-ish ID” like a UUID is “no more or less secure” than using an authorization header, or any other form of client auth. URLs are not secrets! Even if you ensure they’re only requested over HTTPS – which you can’t actually do, as you can’t prevent clients from making plain HTTP requests – it’s practically impossible to ensure that HTTPS termination occurs within your domain of control – see e.g. Cloudflare – and in any case absolutely impossible to ensure that middleboxes won’t transform those requests – see e.g. Iran. There are use cases that leverage unique URLs, sure, like, login resets or whatever, but they’re always time-bounded.
If you want your topics to be password-protected in the traditional sense (username/password or a token), you can do so by using the fine grained access control features (assuming a selfhosted instance), or by reserving a topic (screenshots). This can also be on the official instance, assuming you pay for a plan.
If you pay to “reserve” a topic foo, does that mean that clients can only send notifications to ntfy.sh/foo
with specific auth credentials? If so, all good! 👍
, as you can’t prevent clients from making plain HTTP requests
Well, that’s the clients fault? The client leaking their secrets is just as possible with an authorization header.
it’s practically impossible to ensure that HTTPS termination occurs within your domain of control
It’s trivial to do this. I don’t understand and I don’t see how an authorization header is different.
but they’re always time-bounded.
No they aren’t. Unique URLs are used all the time. Like every time you click “Share” on a document in Paper/Drive and it gives you some really long url.
We’re discussing “Capability URLs” as defined by this W3C doc which says that
The use of capability URLs should not be the default choice in the design of a web application because they are only secure in tightly controlled circumstances. However, in section 3. Reasons to Use Capabilty URLs we outlined three situations in which capability URLs are useful:
- To avoid the need for users to log in to perform an action.
- To make it easy for those with whom you share URLs to share them with others.
- To avoid authentication overheads in APIs.
and further dictates (among other constraints) that
Capability URLs should expire
I don’t really care about that doc tbh
edit: To elaborate slightly, I’m extremely familiar with capabilities
Sure, it’s not the most massively secure thing in the world, but anyone using this service can be confident their client isn’t making plain HTTP requests else they’d pick something normal. I don’t know why my HTTPS termination would be at CloudFlare unless I’d set it up (or ntfy started using it), and even if it were of all people I trust CloudFlare to not-spam me the most. It’s not that big a deal.
anyone using this service can be confident their client isn’t making plain HTTP requests
$ curl -vv -XPOST -d 'foo' ntfy.sh/bar
* Trying 159.203.148.75:80...
* Connected to ntfy.sh (159.203.148.75) port 80 (#0)
> POST /bar HTTP/1.1
> Host: ntfy.sh
...
< HTTP/1.1 200 OK
< Server: nginx
...
{"id":"P5DjRcNMBt3l","time":1682933054,"expires":1682889854,"event":"message","topic":"bar","message":"foo"}
Looks like plain HTTP to me.
To clarify, an application developer using this service, being the type of developer to use a service like this, would be able to feel confident an application request to this web service is via HTTPS.
you can’t prevent clients from making plain HTTP requests
You can either refuse to listen on port 80, or you can detect they’ve transmitted it in the clear and roll that key.
I’ve got ntfy wired into a project and I love it!
Thoughts on the website:
carousel swipes with momentum
I hadn’t thought of that. I’ll check it out.
If that feature is valuable, does that mean high-enhropy random topics aren’t all that secure?
High-entropy topics are secure, but some people want to reserve a short name (e.g. ntfy.sh/phil) and use a user/pass or tokens to access topics. It’s a matter of preference I suppose. Do you have suggestions for a better phrasing?
Maybe a term roughly like “vanity topics” (akin to vanity license plates) would make the intended use more clear.
On the website, I’d suggest putting reserved topics in a separate paragraph reading something like “The Pro plan lets you have topics with short, convenient names, which are secure without having to be kept secret.”
I spent some time over the weekend trying to get self-hosted ntfy.sh going, but ran into an issue with iOS clients not receiving push notifications. I ensured I had followed the docs with an upstream and local base configured properly, but sadly no luck. Since iOS is a requirement for my use, I’ve shelved the effort for now.
Based on this (poorly titled/described) ticket, I’m not the only one. https://github.com/binwiederhier/ntfy/issues/402
Any chance we can get better or more reliable iOS support?
New website looks great and I would love to explore the tool in more depth!
iOS support is quite limited, but the basics should still work. I am more than happy to help troubleshoot, but I suggest looking at the known issues and the troubleshooting page.
Most notable, the most common mistake, if you receive nothing is this (I suggest you read the entire page though, since it’s relevant for iOS):
Ensure that the URL you set in base-url matches exactly what you set the Default Server in iOS to
Sorry that iOS is so limited and buggy. I am just one dude, and I don’t use iOS as my daily driver. I have tried to make the best I could with the time I had. I’ll eventually get around to fixing it, or I’m happy to accept help with the iOS development. The code is also open source if you want to take a look: https://github.com/binwiederhier/ntfy-ios
This is the most random comment I have ever received. Thanks for that :-D – It took me a looooong time to figure out that you’re referring to the travel map on my blog. Hehe.
Hi - this looks nice.
What is the security story here? Is there a document that shows the flow from curl all the way to the notification on the phone?
Hi. There’s no documentation page (yet) that describes architecture and flow, though just judging by how often I have ASCII-drawn it, there really should be one :-)
From the very start, ntfy was designed as a convenience-first app (as simple as possible), which you can see by how simple the curl and POST/PUT requests are. That’s not an excuse, it’s just a conscious choice I made. Because of that, nothing is encrypted at rest (only transport encryption if TLS is used).
Flow 1 (with Firebase):
client (e.g. curl) ---[HTTP(S)]---> ntfy server [store in cache] ---> Firebase ---> Android app
Flow 2 (without Firebase):
client (e.g. curl) ---[HTTP(S)]---> ntfy server [store in cache] ---[HTTP(S) JSON/WS]--> Android app
Flow 3 (iOS):
client (e.g. curl) ---[HTTP(S)]---> ntfy server [store in cache] ---> Firebase ---> APNS ---> iOS
Messages are stored in plaintext in a SQLite database on the server, unless the X-Cache: no
header is passed. All messages are forwarded to Firebase, unless the X-Firebase: no
header is passed.
If you want private messages, you can either wait for the E2E feature (https://github.com/binwiederhier/ntfy/issues/69), which I have already begun developing, and which sadly destroy the ease of use.
Or you can run your own selfhosted server and add basic auth and ACLs (https://ntfy.sh/docs/config/#access-control).
For more complex cases, it’s worth looking at what Signal does. They basically treat the notification services as a 1bit signal that there is something pending (I think that they may also send occasional spurious ones to make traffic correlation harder). Once the app receives the notification, it wakes up and polls the real service.
I couldn’t see anything about efficiency though. The reason most apps use a notification service is to allow a single background service that consumes a tiny amount of RAM to have a single network connection with a very long timeout and then wake up when a notification arrives and prod the system to either forward it to the running app or start the app and forward it if necessary. From the examples, it wasn’t clear how you achieve anything like this, it looked as if the apps were running in the foreground and received the notification directly. I guess you are doing this because. I believe, iOS doesn’t allow background apps to maintain persistent network connections.
They basically treat the notification services as a 1bit signal that there is something pending
This is what ntfy does for iOS for selfhosted servers: It sends a poll_request
via APNS, and then the app will poll the original selfhosted server. There’s a description of this here: https://ntfy.sh/docs/config/#ios-instant-notifications – iOS is veeeeery limited in what you can do. Everything has to go through a central server, so selfhosted servers are technically not really possible at all. It’s quite sad.
I couldn’t see anything about efficiency though.
I responded a bit about this here: https://lobste.rs/s/41dq13/zero_cost_push_notifications_your_phone#c_b6qfnd – Bottom line is that for Android, it’s either Firebase (FCM) or a long-standing JSON stream or WebSocket connection. FCM consumes no battery, and the foreground service consumes 0-1% on my phone for the entire day. If used heavily obviously more
I see. I was hoping that it was possible to use this stand alone, but I guess that’s just not permitted on iOS. It would be nice if there were an Android service that de-Google’d devices could use as a single thing maintaining a service and multiplex the waiting so that apps using an individual server can still exit and be restarted when a notification aimed at them arrives.
It would be nice if there were an Android service that de-Google’d devices could use as a single thing maintaining a service and multiplex the waiting so that apps using an individual server can still exit and be restarted when a notification aimed at them arrives.
I think you are talking about what https://unifiedpush.org/ is trying to be. ntfy is a distributor for UnifiedPush-enabled apps.
Great docs and cool concept. Well done!
Not a question, but I assume you’re accepting compliments as well :)
[edit]
I actually have a suggestion for the mobile apps. Support deep links to reconfigure the notification server, e.g. https://ntfy.sh/configure?base_url=<NEW NTFY SERVER URL>
. When accessed, the user is prompted to allow the app to be reconfigured with the new notify server URL. This would allow self-hosters to more easily roll out push notifications using self-hosted instances. Maybe not a priority for you as a fun open source project with no profit motive, but possibly worth considering.
I always try to write the docs the way I’d want them from other projects: Lots of examples and pictures :D – Thank you for the kind words.
[edit]
Support deep links to reconfigure the notification server
Surprisingly, this has been suggested recently (https://github.com/binwiederhier/ntfy/issues/440). It’s surprising to me, because I don’t quite understand the use case. If you have a self-hosted server, why would you need a shortcut to configure the app that way? Why not just go in the settings and configure it yourself. It’s a step you have to do only once, so it surely can’t be a huge hassle, right?
I’m genuinely asking, because maybe I don’t quite understand the case. Feel free to answer or +1 the GitHub ticket.
Of course. It’s just an API to publish and subscribe messages. You can publish messages via HTTP PUT/POST (https://ntfy.sh/docs/publish/), and subscribe via WebSockets or JSON stream (https://ntfy.sh/docs/subscribe/api/). As for the Android app, you can integrate it via Android intents (https://ntfy.sh/docs/subscribe/phone/#automation-apps) or UnifiedPush (https://ntfy.sh/docs/subscribe/phone/#unifiedpush). You cannot embed the Android application yet, though I’d not be opposed to making that possible if somebody was willing to do the work for it.
That looks pretty cool. How does the power consumption look like? Many years ago I checked how google was handling its notification, and it was pretty involved, with some help from the carriers to minimize the power consumption. Is it still a concern?
If I may quote from the FAQ (https://ntfy.sh/docs/faq/#how-much-battery-does-the-android-app-use):
If you use the ntfy.sh server, and you don’t use the instant delivery feature, the Android/iOS app uses no additional battery, since Firebase Cloud Messaging (FCM) is used. If you use your own server, or you use instant delivery (Android only), the app has to maintain a constant connection to the server, which consumes about 0-1% of battery in 17h of use (on my phone). There has been a ton of testing and improvement around this. I think it’s pretty decent now.
Happy to answer other questions you have.
Oh didn’t see the faq, thanks, that’s pretty good to see that. Even the version not using fcm is good.
This feels like something that ought to be a service provided by a compositing window manager, rather than externally. A compositing window manager knows where the windows are and has an X11 Picture associated with the backing store for each one. It should be able to stream those directly into a video. If it’s using OpenGL for compositing then it probably has the window’s Picture on the GPU as a texture already and so should be able to shove it into a hardware video CODEC without any additional bus round trips.
And indeed GNOME Shell (and I think other Mutter-based compositors) can do exactly this, via PipeWire. It took 5 clicks (+ → Window Capture (Pipewire) → OK → [pick a window] → OK) to tell OBS Studio to capture my terminal window. I believe DMA-BUF sharing is used to avoid copies.
Not sure if there is better documentation around than the documentation for the ScreenCast portal.
OP uses OpenBSD so I don’t think pipewire or OBS would be an option for him at the moment.
(I know there is a porting effort for OBS underway though)
I’ve been using Kazam for years for recording the entire screen, individual windows, or sections of the screen. It works wonderfully, similar to what you are describing. It’s available in the upstream repos, though not sure if it’s available on OpenBSD.
Ordinary X has all the capabilities needed to do it, just ffmpeg only allows capturing from the root window with a bounding box, hence the xwininfo extra step. There’s actually no need for any of this if you’re writing your own program (notice that things like browser screen share can pick individual windows without requiring a compositor at all), just if you are trying to make it work with only the ffmpeg command line options.
IMHO this article (and the flow chart) is flawed, since there is no “do not use a router” option. Many (even more complex) use cases do not need a router. Adding a dependency is almost always worse than adding a few lines of manual code to do something simple yourself. A router is just a helper that facilitates “if-then-else” flow. If you have a tiny web server, don’t use a router. That’s just my opinion though.
There’s a large chunk near the beginning of the article which starts with “I’ll start by saying that if you can use http.ServeMux, you probably should.” and goes on to say “In fact, the only time I’d recommend not using [http.ServeMux] is when you need support for variables in URL paths or custom routing rules.”