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.

It does not claim that ntfy ACL, tokens, config fields, publisher Authorization headers, or QR v2 exist in code. Implementation remains later work; deployer checklists belong to #119.

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 policy-specific cooldown and durable digest controls in the server; Keryx preferences may suppress local display 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-batteries"],
  "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"
}

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

Optional fields:

  • labels, created_at, debug_access (default false)
  • token_id for support/revoke correlation without printing the secret

Validation rules for the implementing Keryx release:

  • Reject unknown types, missing subscribe_auth, HTTP URLs, and empty topics.
  • Reject safecall.keryx.ntfy.v1 scans.
  • 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 QRLater implementation; #119 consumes the deployer checklist
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
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.