iOS/Safari Web Push fails: APNs rejects hard-coded VAPID sub (.local) #208

Closed
opened 2026-10-05 02:03:35 +02:00 by adam · 0 comments
Owner

Summary
Web Push never arrives on iOS/Safari (installed PWA) while Chrome/Android work.

Root cause
The VAPID sub claim is hard-coded to mailto:noreply@meshcore.local (app/push/manager.py, app/routers/push.py). Apple's APNs rejects that JWT with 403 {"reason":"BadJwtToken"} because .local is a reserved TLD. Google FCM doesn't validate sub strictly, so Chrome/Android are unaffected — which makes the bug look platform-specific.

Verified against a live iOS subscription:

sub = mailto:noreply@meshcore.local  -> 403 BadJwtToken
sub = mailto:noreply@example.com     -> 201 Created
sub = mailto:<real address>          -> 201 Created (delivered)

Server, VAPID keys, outbound networking, and clock are all fine.

Proposed fix
I'm opening this issue for discussion per CONTRIBUTING. Since this is a small, additive fix rather than a large feature, I've gone ahead and submitted the PR as well so it's ready to review.

The simplest, lowest-risk fix is to add a MESHCORE_VAPID_SUBJECT env var and keep the current default unchanged — purely additive, no behavior change unless an operator sets it. iOS operators would set it to a real mailto:.

That said, I'm open to your preference on the default. We could change it to a real address so push works on iOS out of the box without configuration — either something generic like mailto:noreply@example.com, or mailto:noreply@meshcore.io. The meshcore.io option isn't really my call to make, so I'll defer to you on whether that's appropriate. Happy to adjust the PR to whatever you decide.

**Summary** Web Push never arrives on iOS/Safari (installed PWA) while Chrome/Android work. **Root cause** The VAPID `sub` claim is hard-coded to `mailto:noreply@meshcore.local` (`app/push/manager.py`, `app/routers/push.py`). Apple's APNs rejects that JWT with `403 {"reason":"BadJwtToken"}` because `.local` is a reserved TLD. Google FCM doesn't validate `sub` strictly, so Chrome/Android are unaffected — which makes the bug look platform-specific. Verified against a live iOS subscription: ``` sub = mailto:noreply@meshcore.local -> 403 BadJwtToken sub = mailto:noreply@example.com -> 201 Created sub = mailto:<real address> -> 201 Created (delivered) ``` Server, VAPID keys, outbound networking, and clock are all fine. **Proposed fix** I'm opening this issue for discussion per CONTRIBUTING. Since this is a small, additive fix rather than a large feature, I've gone ahead and submitted the PR as well so it's ready to review. The simplest, lowest-risk fix is to add a `MESHCORE_VAPID_SUBJECT` env var and keep the current default unchanged — purely additive, no behavior change unless an operator sets it. iOS operators would set it to a real `mailto:`. That said, I'm open to your preference on the default. We could change it to a real address so push works on iOS out of the box without configuration — either something generic like `mailto:noreply@example.com`, or `mailto:noreply@meshcore.io`. The meshcore.io option isn't really my call to make, so I'll defer to you on whether that's appropriate. Happy to adjust the PR to whatever you decide. <!-- github-sync-id:4670094235 -->
adam closed this issue 2026-10-05 02:03:35 +02:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/Remote-Terminal-for-MeshCore#208