App integrations

# Notifications Plugin

Private previews from Hermes to your paired devices.

## [Setup and delivery](https://cadu.bot/docs/notifications#setup)

The bundled `hermes-push` platform plugin sends notifications through an APNs relay to devices paired with a Hermes installation. It is send-only: the app connects to Hermes separately for conversations and actions. The audited bundled manifest is version 1.3.4 and declares `cryptography>=50,<51`.

1.  Open notification setup in Cadu and grant iOS notification permission. Setup obtains a device token and pairs it with the relay.
2.  Setup reuses an existing install capability when available, writes the plugin and gateway hook to Hermes, and configures the relay URL, install key, and home channel.
3.  The app restarts gateways, checks the push platform, and retrieves the private-preview key directly from Hermes. Other devices join the same installation rather than receiving separate preview-encryption keys.

Registered plugin hooks cover completed turns and approval requests; a companion `agent:end` hook covers gateway completions. Cron and other addressed deliveries can use the `push` platform. The baseline plugin does not register its newer `gateway_client_event` handler, so the presence of code for an event is not a guarantee that your runtime emits notifications for it.

## [End-to-end preview encryption](https://cadu.bot/docs/notifications#encryption)

The encrypted endpoints are the Hermes runtime and paired Cadu devices. Hermes sees the original notification content before encryption. The relay and APNs receive the encrypted preview and a generic alert, not its readable title, subtitle, body, or private routing fields, when sent through this private-preview path.

1.  Hermes creates a random 32-byte symmetric key with `os.urandom(32)`. It is installation-wide and stored as Base64 in the installation's private-preview key file, created with mode `0600`. Key creation is serialized; an existing damaged key is not silently rotated.
2.  The plugin serializes a bounded preview containing title, subtitle, body, thread ID, permitted routing fields, and an issue timestamp. The body begins with a 600-character limit and may be shortened further to keep plaintext JSON at or below 2,000 bytes.
3.  `AESGCM` encrypts it with a fresh random 12-byte nonce. Authenticated additional data is `cadu-preview-v1:` followed by the SHA-256 hash of the install capability. This binds successful decryption to that installation identifier.
4.  The version-1 envelope contains Base64 of the nonce, ciphertext, and authentication tag. On-device CryptoKit authenticates and decrypts it with the same key and additional data.

The device rejects unsupported envelopes, invalid sizes or authentication, timestamps more than five minutes in the future, and previews older than seven days. This age check is not a permanent replay ledger. A keyed event fingerprint supports the relay's short-window duplicate suppression.

This is shared-key preview encryption, not a per-device public-key protocol. The implementation has no per-message forward-secrecy ratchet. A party with the preview key and captured envelopes can decrypt messages covered by that key.

## [Pairing and key storage](https://cadu.bot/docs/notifications#keys)

The relay install capability and the preview-encryption key are separate. The capability pairs devices and authorizes relay requests; possession of that capability alone does not supply the AES key.

Cadu retrieves the preview key through the selected Hermes connection's file-read API, restricted to saved primary or fallback base URLs. This request uses an ephemeral session, no response cache, and no redirects. It follows the saved connection trust policy.

**Pairing does not enforce HTTPS.** The transport accepts both HTTP and HTTPS under that policy. We strongly recommend HTTPS with a trusted certificate. Use HTTP only over a secure tunnel or another connection that provides equivalent transport protection: unprotected HTTP can expose both dashboard credentials and the installation-wide preview key to someone observing the connection. Possession of that key allows decryption of captured previews encrypted with it. Preview encryption cannot compensate for an exposed pairing connection or a compromised Hermes host.

The device stores the key in a dedicated Keychain access group shared with the notification service extension. It uses `AfterFirstUnlockThisDeviceOnly` and disables synchronization. This permits access while locked after the first unlock, but does not synchronize the key through iCloud or migrate it to another device.

Disabling this device's notifications unregisters it from the relay and removes its local preview key. That is not cryptographic rotation of the installation's shared key, and does not revoke a copy previously extracted from a device or server.

## [What the relay can see](https://cadu.bot/docs/notifications#metadata)

The relay must route and filter notifications. It sees the install capability used in the request, registered device identifiers and APNs tokens, notification kind, timing and payload size, and exposed routing metadata. That metadata can include a session identifier, an allowed source class, and whether work came from a subagent.

The event fingerprint and thread-grouping token are keyed HMAC-SHA-256 values. They conceal their input text but still reveal equality. Profile names, request IDs, and the other allowlisted private routing fields travel inside the encrypted preview. The relay adds the non-secret install identifier to the APNs payload.

The relay's SQLite models persist device registration, notification preferences, presence expiry, and muted-session records. Its notification handler logs delivery counts, kind, category, and title; the private path replaces the title with the generic Cadu title. This does not establish a no-logs policy for a deployment or its proxies.

The relay also accepts legacy unsealed notifications. Its generic-alert enforcement applies when a sealed envelope or private-preview-unavailable marker is present. Do not assume arbitrary clients posting plaintext to the relay receive end-to-end encryption.

## [Decryption and notification actions](https://cadu.bot/docs/notifications#device)

The iOS notification service extension reads the local preview key and decrypts without fetching content from a server. After successful authentication it restores the preview, derives supported action routes, and can use cached profile information for communication-notification styling. The readable result is handed to iOS and may appear on the lock screen according to your notification settings.

Missing keys, invalid ciphertext, expired previews, or extension timeout leave a generic notification: “New activity. Open Cadu to view.” Failed private-preview enrichment clears attachments, action category, thread identifier, and routing metadata. The server's encryption failure path also sends a generic alert rather than retrying with the original plaintext.

When supported routing fields are available, replies and approval decisions use the saved Hermes connection directly, not the push relay. The services recheck local installation pairing and authenticate with Hermes. SSH-backed instances require opening the app because the background action does not own the foreground SSH tunnel.

Action availability depends on the emitted metadata and compatible Hermes methods. Lost acknowledgements are reported as unconfirmed rather than automatically resubmitted; duplicate suppression in these action services is bounded and in memory. A delivered preview alone is not proof that an action succeeded.

## [Filtering and delivery limits](https://cadu.bot/docs/notifications#delivery)

Per-device preferences control replies, attention events, addressed deliveries, system events, desktop sources, and subagent completions. Foreground presence suppresses ordinary notifications on that device; attention events bypass presence. Muting a session applies across the installation's paired devices, including attention events.

The plugin's background delivery queue holds up to 64 entries and drops the oldest when full. Failed background posts are logged and dropped. The relay checks APNs' 4,096-byte payload limit and removes registrations rejected as unregistered or invalid.

HTTP success from the relay is not a display or read receipt. Its response can report filtered, muted, present, skipped, or failed delivery outcomes. Operating-system notification settings and APNs delivery still affect what appears. Check pairing, gateway status, per-device preferences, session mutes, and preview-key setup when diagnosing a missing notification.
