Anonymous public presence. There is no identity, account, or auth — the product is "who's here, live" and "how much collective prayer happened". The security work is therefore: (1) keep nothing personal or secret in scope, (2) bound abuse of the public write path, and (3) make the wire format the privacy boundary.
| control | stops | does NOT stop |
|---|---|---|
| Origin allow-list (or same-origin default) | cross-site WS hijacking from a browser | a native client that omits Origin |
| per-connection message budget | floods from one socket | multi-socket floods alone |
| upgrade throttle (hashed IP, per-isolate) | fast socket churn per isolate | a distributed attacker across locations |
| prayer-start gate | durable-totals inflation by one socket | a large coordinated botnet |
| day-string validation | usersToday/usersWeek spoof (e.g. 9999-12-31) |
— |
| cell normalization (1° grid) | precise-location leakage from a malicious cell | — |
sanitizeStats |
prototype-pollution keys into storage | — |
Because it is anonymous-by-design, a determined attacker with many IPs can
still distort live counts (e.g. people, prayers). These self-correct when
sockets close; the durable numbers are rate-gated and debounced. That is
the honest ceiling of a no-auth design.
- Only coarse 1° grid cells and anonymous counters ever circulate.
anonIdis random and opaque; it is never broadcast — it only keys a per-device stats blob in storage.- Raw IPs are never logged or stored; the only derived value is a SHA-256 hash used by the upgrade throttle.
- Storage keys:
schema,totals,totalPrayerSeconds,anonSeen,['people', anonId],counts— all anonymous.
- anonId state-poisoning (low):
syncis a max-merge, so anyone who knows an anonId can only raise that user's counters (they cannot lower or steal). anonIds are unguessable random UUIDs. Full protection would require auth, which conflicts with the anonymous design. MAX_UPGRADES_PER_IPis per-isolate best-effort, not global.- The Origin check is a browser control, not an auth boundary.
- Feed
nameis JSON only; the app must never inject it as HTML (app-side XSS responsibility). - Tenant isolation is a deployment rule (see
DEPLOYMENT.md): DO storage is scoped by account + Worker name; each tenant deploys separately.
-
ALLOWED_ORIGINSset to the deployable domains. -
MAX_UPGRADES_PER_IPenabled (e.g. 10). -
ADMIN_KEYset viawrangler secret put(for?fresh=1). -
wranglerpinned;npm auditreviewed before release. - No raw IPs anywhere; snapshots of
/statsare the only external copy. -
PROTOCOL_VERSIONbumped if the wire shape ever changes; drift test kept green (bothprotocol.jscopies byte-identical).