Skip to content

Keryx authenticated ntfy provisioning protocol ​

Approved: 25 August 2026

This document is the P1-02 production provisioning protocol for SafeCall-to-Keryx ntfy access. It designs TLS-only transport, separate publish and subscribe credentials, QR v2, fail-closed v1 migration, and abuse controls against the P1-01 threat model.

QR v2, publish token, and token-manager mint exist in product code. The restricted deployer/support checklist is ntfy-deployer.md (#119). Deny-anonymous on the production host remains the later cutover.

Purpose and non-goals ​

Purpose

  • Require HTTPS for every production, test, and reviewer ntfy URL.
  • Separate least-privilege subscribe credentials from publish credentials.
  • Define create, QR transfer, storage, rotation, revocation, expiry, and offboarding for those secrets.
  • Version the QR contract and specify fail-closed v1 migration.
  • Define unauthorized-publish controls and rate-limit ownership.

Non-goals

  • Implementing config schema, SafeCall publisher Authorization, token mint/revoke APIs, admin QR v2 UI, or Keryx auth wiring.
  • Changing production ntfy servers in this work item.
  • Accepting HTTP for private-network, loopback, or lab hosts.
  • Putting a publish token in any QR or ordinary administrator UI.
  • Inventing numeric SLOs for ntfy rate limits.

Approved decisions ​

  • Owner and approver: Thomas Minitsios, 25 August 2026.
  • TLS: HTTPS only for production, test, and reviewer ntfy. No private-network or loopback HTTP exception.
  • Subscribe credentials: read-only ntfy access tokens minted per QR, scoped to the selected topics, revocable and rotatable.
  • Publish credentials: server-side write token only; never in the QR.

Current gap baseline ​

SurfaceCurrent behaviorProtocol target
QRsafecall.keryx.ntfy.v1 has URL/topics/labels/debug; no credentialsafecall.keryx.ntfy.v2 with subscribe_auth
Keryx URL parseAccepts http and httpsReject non-HTTPS
Keryx subscribe/pollNo Authorization headerPass per-QR subscribe auth on plugin and poll
SafeCall publishTitle/Priority/Tags onlyAuthorization with publish token
Configserver_ntfy_server_url onlyRestricted publish and token-manager secrets
ntfy hostTopics usable without auth in typical installsDeny anonymous read/write

TLS ​

  • Require https URLs with a non-empty host.
  • Reject http, scheme-less URLs, IP literals without HTTPS, and any skip-verify / custom-trust path for launch.
  • Use system CA verification only.
  • After the implementing Keryx release, reject v1 scans and discard or refuse to run any stored HTTP or v1 config on next start.
  • Isolated reviewer and test ntfy hosts must also be HTTPS. There is no written HTTP exception.

Credentials and ntfy ACL ​

Use ntfy access tokens as Authorization: Bearer tk_… values. The client ntfy host must deny anonymous subscribe and publish.

SecretWhere storedScopeIn QR?
Publish tokenSafeCall config (restricted)write {server_id}-* production topics used by SafeCallNo
Token-manager credentialSafeCall config (restricted)mint, list, and revoke subscribe tokens on the ntfy hostNo
Per-QR subscribe tokenQR payload and Keryx local configread-only on the selected topics onlyYes

Ordinary administrators never type tokens, topics, or server URLs. An authenticated SafeCall administrator selects friendly alert categories; SafeCall mints the subscribe token when generating the QR.

mermaid
flowchart LR
  Admin[SafeCall_admin]
  Mint[Token_manager]
  QR[QR_v2]
  Keryx[Keryx_device]
  Ntfy[ntfy_HTTPS]
  Pub[SafeCall_publisher]

  Admin -->|"generate QR"| Mint
  Mint -->|"read-only token"| QR
  QR -->|"scan"| Keryx
  Keryx -->|"Authorization subscribe"| Ntfy
  Pub -->|"Authorization publish"| Ntfy

Lifecycle ​

Create ​

  • Mint a new read-only subscribe token at QR generate time, scoped exactly to the selected topic names.
  • Persist server-side metadata needed to revoke later: at least token_id, selected topics, created time, and optionally the generating admin identity.
  • Prefer not to retain the raw subscribe token on the server after the QR is shown; retention of the raw secret is an implementation smell and must be justified if required.

Transfer ​

  • Carry the subscribe credential only in QR v2 as subscribe_auth.
  • Do not print the token in ordinary UI, status text, screenshots, or debug detail views. token_id may appear in restricted support diagnostics.
  • Treat a photographed QR as disclosure of subscribe-only access until revoke.

Storage ​

  • Keryx stores subscribe_auth with the rest of local config until #98 tightens protection.
  • Documentation and support must treat the value as a secret even while it remains in SharedPreferences.

Rotation ​

  • Generating a replacement QR mints a new subscribe token.
  • Revoke the previous token for that administrator or device when a replacement is confirmed.
  • Publish and token-manager secrets rotate on a deployer schedule owned by #119; they never appear in QRs.

Revocation, offboarding, and device loss ​

  • Revoke by token_id on the ntfy host through the token-manager credential.
  • Local app reset or uninstall alone is not sufficient offboarding.
  • Device-loss and staff-offboarding runbooks in #120 and #121 must require revoke, then optional local reset.

Expiry ​

  • No default wall-clock expiry for subscribe tokens. Silent expiry would create missed-alert failures that look like delivery bugs.
  • An optional deployer max-age may be documented later in #119; it is not a launch default and must not be enabled without admin-visible renewal UX.

Unauthorized publish and rate limits ​

  • Deny anonymous publish and subscribe on the client ntfy host.
  • Only the SafeCall publish token may POST production topics.
  • Keryx must not publish alerts.
  • Keep SafeCall's daily status plane and SOS/watch/test wakes in the server; Keryx preferences may suppress the daily banner or filter Overview but never change server publishing.
  • ntfy visitor and authenticated rate limits are deployer controls recorded in #119. This protocol does not invent numeric SLOs.
  • QR disclosure of a subscribe token does not grant publish (reduces threat model A4). Residual A1 is subscribe-only until the token is revoked.

QR contract: safecall.keryx.ntfy.v2 ​

json
{
  "type": "safecall.keryx.ntfy.v2",
  "ntfy_server_url": "https://notify.example/",
  "topics": ["TST000-sos", "TST000-watches", "TST000-status"],
  "labels": {
    "TST000-sos": "Panic button alerts"
  },
  "created_at": "2026-08-25T12:00:00.000Z",
  "debug_access": false,
  "subscribe_auth": "Bearer tk_example_subscribe_only",
  "token_id": "optional-support-correlation-id",
  "status_url": "https://notify.example/keryx-status",
  "status_token": "optional-hmac-when-read-secret-is-set"
}

Required fields:

  • type must be exactly safecall.keryx.ntfy.v2
  • ntfy_server_url must be HTTPS with a host
  • topics non-empty string list
  • subscribe_auth non-empty Authorization header value
  • status_url HTTPS keryx-status origin (new mint always includes it)

Optional fields:

  • labels, created_at, debug_access (default false)
  • token_id for support/revoke correlation without printing the secret
  • status_token HMAC for GET when server_keryx_status_read_secret is set; otherwise Keryx uses subscribe_auth

Validation rules for the implementing Keryx release:

  • Reject unknown types, missing subscribe_auth, HTTP URLs, and empty topics.
  • Reject safecall.keryx.ntfy.v1 scans.
  • If status_url is absent, derive {ntfy origin}/keryx-status. Existing phones still need a new QR to subscribe to {server_id}-status.
  • Pass subscribe_auth to the vendored ntfy plugin auth parameter and to HTTP poll recovery requests, including Android FCM poll_request wake polls under the Android delivery architecture and iOS APNs poll_request wake polls plus device-token registration requests under the iOS APNs delivery architecture.
  • Never show subscribe_auth in Active/Archive, detail, or debug UI.

Alert-record compatibility ​

QR v2 and topic names stay unchanged. SafeCall messages may include the line-oriented SafeCall alert data v1 record documented in ntfy topics. Keryx accepts both that record and legacy free-form ntfy messages. Only a valid v1 record can update authoritative category/device condition state; legacy messages remain history. This allows Keryx's dual parser to ship before the server cadence cutover without creating a second QR migration.

Fail-closed v1 migration ​

RuleRequirement
ScanImplementing Keryx rejects v1 QR payloads
Stored configImplementing Keryx refuses to keep using stored v1 or HTTP config
Admin UIImplementing SafeCall version refuses to emit v1
Production ntfyNo dual-run of open anonymous topics plus authed topics on the same production host
Test/reviewerIsolated HTTPS hosts may run v2 only

Enabling ntfy ACL on a production host without shipping v2 would break every current Keryx install. Cut over with coordinated SafeCall and Keryx releases, or migrate installs through a controlled window owned by implementation and #119—not by silently accepting v1 on an open host.

An interim dual-run (auth-file on, auth-default-access: read-write) is the current operator path so the official ntfy app can keep working while SafeCall and Keryx authenticate. That does not satisfy this launch rule. How-to and later deny-all cutover: ntfy-auth-and-play-internal.md (OA-119-ntfy-auth, OA-119-ntfy-cutover).

Residual risks after protocol approval ​

ThreatResidual after this design is implementedStill open until
A1 QR disclosureSubscribe-only until revokeImplementation + revoke UX (#120/#121)
A2 topic guessingBlocked without subscribe tokenImplementation + ntfy ACL (#119)
A3 unauthorized subscribeBlocked without tokenImplementation
A4 unauthorized publishBlocked without publish tokenImplementation
A5 HTTP/MITMBlocked by HTTPS-onlyImplementation
A6 spoof/replayInjection blocked; replay still needs dedupe evidenceImplementation + P4
A7/A10 device loss / offboardingRequires revoke by token idImplementation + #98/#120/#121
A8/A9 debug/log leakageProcess and redaction#98/#99/#132

Closing #95 approves this protocol. It does not accept residual A1–A7/A10 risk and does not mark TLS or credentials as live.

Implementation handoffs ​

WorkOwner
Config fields, publisher Authorization, token mint/revoke, admin v2 QRLanded in SafeCall; deployer host packet is ntfy-deployer.md
Keryx v2 parse/store; plugin + poll AuthorizationLater implementation
Local secret storage, lock-screen, device-loss controls#98
Privacy and store disclosures matching real flows#99–#101
Safety and delivery-limitation claims#103 packet: safety-claims.md
Client ntfy TLS/ACL/token prerequisites#119 — ntfy-deployer.md
Administrator revoke, replacement, and offboarding guidance#120, #121
Versioning, QR cutover gate, stored-config migrationversioning.md (P3-05)

Review record ​

  • Status: approved as the P1-02 provisioning protocol.
  • Date: 25 August 2026.
  • Approver: Thomas Minitsios under the P0-05 RACI.
  • Depends on: P1-01 threat model.
  • Evidence sources: keryx/lib/models/keryx_config.dart, keryx/lib/services/ntfy_stream_service.dart, keryx/vendor/ntfy/lib/ntfy.dart, server/src/ntfy.ts, server/src/ntfy_transport.ts, config/schema.ts, docs/gtm/keryx/threat-model.md.