chime is in its v1 development phase: the crate is pre-release, and wire or
storage formats may change aggressively without a compatibility window.
Security fixes land on the current development line only.
Do not open a public GitHub issue for security-sensitive bug reports.
Use one of the following private channels:
- GitHub Security Advisories — preferred. Open a draft advisory at https://github.com/arkret-org/chime/security/advisories/new. We acknowledge new advisories within 5 working days.
- Direct contact — if you cannot use GitHub Security Advisories, email the maintainers via the address listed under https://github.com/arkret.
When you report, please include:
- a description of the issue and its impact in the Arkret push surface
(
register-device,unregister-device, or localPushRegistrationStatestate); - a minimal reproduction (test fixture, JSON payload, request strand);
- whether the issue is exploitable through the public crate API, requires a malicious server response, or requires local code change;
- any disclosure deadline you intend to honor.
chime enforces a privacy boundary specific to push:
push_key(FCM/APNs/WebPush tokens) MUST NOT appear in logs, debug output, or error messages.- Register/unregister bearer and session material is sent only to the signed-in account's Station. A configured Gateway URL is request data and MUST NOT be used as a client HTTP destination.
- DIDs (
did:*) MUST NOT appear inpush_hintor any token-shaped wire field. ak.edge.push.command.notify.v1payloads default to E2EE blind wakeup; only services on the explicitplaintext_visible_servicesallowlist may receive identifying metadata.
The Circle primitive introduces an encrypted sub-boundary inside a Realm. chime's wakeup wire MUST preserve the following invariants on top of the generic blind-wakeup rules above:
- The push wakeup wire MUST NOT carry a Circle identifier, a Realm
identifier, or a scope field. Per
push-notifications.md§5.1, raw Realm ids, Circle ids andeffective_scopeMUST NOT enter the/_arkret/edge/push/notifywire under the defaultak.profile.push_gateway.blind_wakeup.v1profile; the only routing inputs areroute_tokensanddevices[].target_route_token. - The security scope of an Event is its producer-signed
scope_ref(models/circle.md§6.2). A client learns the boundary by fetching the durable Event, never from the wakeup. - The display label (
circle_name/circle_display_name) and any membership listing (circle_members/circle_member_dids/circle_member_actor_ids) are never allowed on the wakeup wire, including in the plaintext-visible mode that whitelisted destination service DIDs see. A wakeup that surfaces them MUST be rejected withPushNotifyPrivacyError::SensitiveField.
If you believe you have found a way to bypass any of those rules, treat it as a security report and use the private channels above.
- The local
chimepackage artifact (httpfeature included). - Default
ArkretPushClientHTTPS / TLS / retry / idempotency behaviour. - The
PushRegistrationStateclient-side validators.
- Vulnerabilities in upstream Arkret services (Sync Service, Principal Server, coland, floria, …) — please report those to the respective service maintainers.
- Vulnerabilities in third-party crates listed in
Cargo.toml. Report those upstream; we will pull a fixed dependency once it is available. - Misuse by callers that disable the privacy validators or feed an
attacker-controlled
plaintext_visible_servicesallowlist.
chime does not ship inbound ak.edge.push.command.notify.v1 frame
validators — inbound wire-shape, privacy, and replay checks are owned by
the host application (gateway-side payload sanitization lives in the
SDK's arkret_push_policy::blind_payload_sanitizer). The sections below
capture the remaining host-side requirements.
chime does NOT verify the signature on inbound push frames. This
is intentional:
- The signing key set lives in the host's deployment configuration
(gateway service DIDs, key rotation cadence, allowlist of accepted
kidvalues), and chime cannot know that set without becoming a service-discovery client. - Different host integrations use different transport envelopes — APNs provider tokens, FCM bearer tokens, WebPush VAPID, OEM-provider HMAC headers — and chime would have to recapitulate each one to verify.
- Keeping the verifier outside chime keeps the request builders usable as pure functions in tests and fuzzers without requiring the test to manage a keystore.
Host applications MUST:
- verify the
Signature/Signature-Inputheaders (RFC 9421) against their configured set of accepted signer DIDs BEFORE handing the decoded body to the SDK's blind-wakeup sanitizer; - treat a signature failure as
403and drop the frame silently — do not retry, do not surface to UI, do not log the body; - include the verifier's accepted
kidset in their monitoring so unexpectedkidvalues page on appearance.
Inbound frames carry a signed creation timestamp header. The host MUST enforce a freshness window on that header:
- Recommended window: 5 minutes.
- Frames older than the window MUST be dropped before the blind-payload
sanitizer (
arkret_push_policy::blind_payload_sanitizer::sanitize_blind_payload) runs. Old frames may carry signatures that were valid at signing time but reference keys that have since rotated; treating them as fresh defeats the rotation. - Clock skew tolerance MUST be symmetric and small (≤ 30 seconds). Unbounded future timestamps are a replay vector — drop them.
- Drops MUST be counted in monitoring (
chime.push.notify.rejected_total{reason="stale_timestamp"}) so a misconfigured upstream clock surfaces quickly.
Replay protection on inbound frames (a per-(device_id, nonce) seen
set covering at least the freshness window plus the host's broker
retry budget) is likewise the host relay's responsibility; chime does
not track nonces.