Skip to content

Doc status: Latest (rolling). See Versions.

Goals

  • Prevent unauthorized alerts, membership changes, and admin actions.
  • Minimise stored sensitive data (no exact home addresses; avoid exact lat/lng when possible).
  • Make abuse and spam harder than legitimate use.
  • Keep security controls server-side and auditable.

Threat model (practical)

Primary threats

  • Account takeover of a legitimate user (phishing, device compromise).
  • Malicious member (or ex-member) attempting spam/harassment via alerts.
  • Invite leakage leading to unauthorized joins.
  • API abuse (bot traffic, token replay, endpoint discovery).
  • Device-token abuse (registering many tokens, sending to wrong audience).

Non-goals

  • Defending against a fully compromised manager account beyond standard controls.
  • Guaranteeing critical-alert behavior on iOS without Apple entitlement approval.

Identity & authentication

Mobile users (Firebase Auth — native SDK)

  • Mobile users authenticate with @react-native-firebase/auth — the React Native Firebase native SDK (Google Sign-In on both platforms, Sign in with Apple on iOS).
  • The native SDK initializes via platform config files (google-services.json / GoogleService-Info.plist), avoiding JS-level initialization issues on Android new-arch/Hermes.
  • All mobile API calls are authenticated with a Firebase ID token: Authorization: Bearer <token>.
  • The Worker verifies the Firebase ID token server-side and derives the canonical uid.
  • Apple Sign-In uses auth.AppleAuthProvider.credential(identityToken, nonce) with a SHA-256 hashed nonce for replay protection.

Admin users (Cloudflare Zero Trust + Microsoft Entra ID)

  • The admin dashboard is protected by Cloudflare Zero Trust Access.
  • Authentication is delegated to Microsoft Entra ID (Azure AD) via OIDC SSO.
  • CF Access injects a signed JWT (CF_Authorization cookie) after successful SSO.
  • The admin app forwards this JWT to the Worker API via the cf-access-jwt-assertion header.
  • The Worker verifies the JWT against the CF Access team's JWKS endpoint (<team>.cloudflareaccess.com/cdn-cgi/access/certs).
  • The Worker maps the Entra email to a Firestore user record (auto-provisions if new).
  • External collaborators can be accommodated via Entra B2B guest accounts or CF Access one-time PIN policies.

Common

  • Never trust uid/role claims from the client; always compute authorization server-side.

Authorization & privileges

Authorization is enforced in the Worker, using Firestore membership state:

  • Member actions

    • registerDevice: only for self (uid from token)
    • joinGroupViaInvite: only for self
    • triggerIncident: only if the user is an active member of the group
  • Manager actions (group scoped)

    • pause / ban member
    • create/revoke invites
    • broadcast
    • view group members, invites, license, payments
    • initiate Stripe checkout (redirects to Stripe-hosted page)
  • Super admin actions (platform-wide)

    • view all users, groups, licenses, payments
    • set user roles (standard/manager/super_admin)
    • grant/revoke licenses

Membership status is enforced server-side:

  • active: may trigger incident
  • paused: cannot trigger incident
  • banned: cannot trigger incident or re-join (unless explicitly unbanned)

Data minimisation & PII policy

  • Do not store exact home addresses.
  • Avoid storing exact latitude/longitude when possible.
  • Prefer coarse “zones” (e.g., suburb-level or grid cell identifiers) rather than precise locations.
  • Store only what is necessary to operate:
    • user identity: uid, optional email/displayName
    • device push token + platform
    • group membership + role + status
    • incident metadata (time, group, type)

Address privacy (on-device + true E2E)

  • A user's exact home address is stored only on their phone (in AsyncStorage).
  • When an alert is triggered, the address is encrypted on-device for each recipient individually using ML-KEM-768 / AES-256-GCM.
  • The server receives and stores only opaque per-recipient envelopes — it has no key to decrypt them.
  • Only the intended recipient can decrypt their envelope using their device-local KEM secret key.
  • The backend never sees, logs, or persists plaintext addresses.

Cryptography (PQC)

Implemented using @maatara/core-pqc (v0.5.0, Apache-2.0) — a WASM-based PQC toolkit providing the locked crypto suite.

Locked crypto suite:

  • Payload: AES-256-GCM
  • Key transport (KEM): ML-KEM-768
  • Key derivation: HKDF-SHA256
  • Signatures: ML-DSA-44 (optional but recommended)

Important detail: ML-DSA is a signature scheme (authenticity/integrity), not encryption. For key transport/encryption we use a KEM (ML-KEM).

For forward compatibility, include a cryptoSuite and kid (key id) in encrypted payload metadata.

Dual PQC strategy (mobile)

The mobile app uses a dual strategy for PQC operations:

  1. Primary: @neighbourhoodwatch/pqc-native — Rust-based Expo native module (fastest, used in production builds).
  2. Fallback: @maatara/core-pqc — WASM-based (used automatically when the native module is unavailable, e.g., in Expo Go during development).

Both implementations use the same locked crypto suite and produce interoperable ciphertext.

Server-side PQC (API Worker)

The Cloudflare Worker is zero-knowledge with respect to alert payloads:

  • It stores opaque per-recipient envelopes in Firestore.
  • It does not possess a KEM secret key for decryption — PQC_KEM_SECRET_KEY is no longer used.
  • It serves envelopes back to authenticated recipients for on-device decryption.

Key management & rotation

  • Each device generates an ML-KEM-768 keypair on first sign-in.
  • The secret key is stored in the OS secure enclave via expo-secure-store (Android Keystore / iOS Keychain) — never leaves the device, never in plaintext storage.
  • The public key is uploaded to users/{uid}/crypto/{kid} at sign-in and at invite redemption.
  • Key rotation is supported by publishing a new kid and keeping the previous key for a short overlap window.

Recipient scoping

  • The Worker computes recipients using server-side membership state (group + status).
  • Only active members receive the push payload.
  • Ex-members do not receive pushes; even if they retained old keys, they cannot decrypt messages they do not receive.

Push notification safety

  • Push is sent via FCM HTTP v1 from the Worker.
  • Fan-out targets only the devices of active members in the relevant group.
  • Device tokens are treated as secrets:
    • never log full tokens
    • rotate/re-register on app reinstall

Emergency alerts

  • Android: Uses notification channel alerts with MAX importance, bypassDnd: true, public lockscreen visibility. Alerts sound even when device is on silent / DND.
  • iOS: FCM payloads include apns-priority: 10 and Critical Alert (critical: 1, volume: 1.0) so notifications sound at full volume even on silent. Requires Apple Critical Alerts entitlement.

Abuse controls

Minimum controls (server-side):

  • Rate limiting for incident triggering and admin broadcast.
  • Idempotency / dedupe to avoid accidental double sends.
  • Invite controls
    • expiry
    • max uses
    • revoke capability

Operational controls:

  • Managers can pause/ban members.
  • Maintain an incident review workflow (even if manual early).

Secrets & configuration

  • Service account credentials and FCM credentials must be stored as Cloudflare secrets/env vars.
  • Stripe keys (STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET) must be stored as Cloudflare secrets (never in wrangler.toml).
  • Never commit secrets to git.
  • Keep separate environments for dev/staging/prod where practical.

Payment security (Stripe)

  • All payment processing uses Stripe Checkout (Stripe-hosted page); no card data touches our API.
  • Stripe webhooks are verified server-side using HMAC-SHA256 with timing-safe comparison and a 5-minute timestamp tolerance.
  • License activation occurs only after webhook verification of checkout.session.completed.
  • Payment records are stored server-side in Firestore (payments collection) for audit.

Logging & monitoring

  • Log security-relevant events (without sensitive payloads):

    • auth failures
    • authorization failures
    • invite use / join
    • incident triggers (metadata only)
    • admin actions
  • Avoid logging PII and never log device tokens.

Firestore rules

Firestore rules should be treated as defense-in-depth.

  • The Worker is the primary enforcement point for privileged actions.
  • Client direct access should be restricted to the minimum required (ideally none for privileged writes).

Checklist

  • Server-side token verification on every API request
  • Group-scoped authorization on every privileged action
  • No exact addresses stored
  • Device tokens never logged
  • Rate limit trigger/broadcast
  • Invite expiry + max-uses + revocation

Neighbourhood Emergency Alert System