Appearance
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
| Surface | Current behavior | Protocol target |
|---|---|---|
| QR | safecall.keryx.ntfy.v1 has URL/topics/labels/debug; no credential | safecall.keryx.ntfy.v2 with subscribe_auth |
| Keryx URL parse | Accepts http and https | Reject non-HTTPS |
| Keryx subscribe/poll | No Authorization header | Pass per-QR subscribe auth on plugin and poll |
| SafeCall publish | Title/Priority/Tags only | Authorization with publish token |
| Config | server_ntfy_server_url only | Restricted publish and token-manager secrets |
| ntfy host | Topics usable without auth in typical installs | Deny anonymous read/write |
TLS
- Require
httpsURLs 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.
| Secret | Where stored | Scope | In QR? |
|---|---|---|---|
| Publish token | SafeCall config (restricted) | write {server_id}-* production topics used by SafeCall | No |
| Token-manager credential | SafeCall config (restricted) | mint, list, and revoke subscribe tokens on the ntfy host | No |
| Per-QR subscribe token | QR payload and Keryx local config | read-only on the selected topics only | Yes |
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"| NtfyLifecycle
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_idmay appear in restricted support diagnostics. - Treat a photographed QR as disclosure of subscribe-only access until revoke.
Storage
- Keryx stores
subscribe_authwith 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_idon 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:
typemust be exactlysafecall.keryx.ntfy.v2ntfy_server_urlmust be HTTPS with a hosttopicsnon-empty string listsubscribe_authnon-emptyAuthorizationheader value
Optional fields:
labels,created_at,debug_access(default false)token_idfor 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.v1scans. - Pass
subscribe_authto the vendored ntfy pluginauthparameter and to HTTP poll recovery requests, including Android FCMpoll_requestwake polls under the Android delivery architecture and iOS APNspoll_requestwake polls plus device-token registration requests under the iOS APNs delivery architecture. - Never show
subscribe_authin 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
| Rule | Requirement |
|---|---|
| Scan | Implementing Keryx rejects v1 QR payloads |
| Stored config | Implementing Keryx refuses to keep using stored v1 or HTTP config |
| Admin UI | Implementing SafeCall version refuses to emit v1 |
| Production ntfy | No dual-run of open anonymous topics plus authed topics on the same production host |
| Test/reviewer | Isolated 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
| Threat | Residual after this design is implemented | Still open until |
|---|---|---|
| A1 QR disclosure | Subscribe-only until revoke | Implementation + revoke UX (#120/#121) |
| A2 topic guessing | Blocked without subscribe token | Implementation + ntfy ACL (#119) |
| A3 unauthorized subscribe | Blocked without token | Implementation |
| A4 unauthorized publish | Blocked without publish token | Implementation |
| A5 HTTP/MITM | Blocked by HTTPS-only | Implementation |
| A6 spoof/replay | Injection blocked; replay still needs dedupe evidence | Implementation + P4 |
| A7/A10 device loss / offboarding | Requires revoke by token id | Implementation + #98/#120/#121 |
| A8/A9 debug/log leakage | Process 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
| Work | Owner |
|---|---|
| Config fields, publisher Authorization, token mint/revoke, admin v2 QR | Later implementation; #119 consumes the deployer checklist |
| Keryx v2 parse/store; plugin + poll Authorization | Later 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 migration | versioning.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.