Skip to content

Threat model & controls

This page summarises MOQOM’s security posture for integrators and security reviewers. Report vulnerabilities to security@moqom.cloud — see Responsible disclosure.

Asset Why it matters
Media content Private calls, paid broadcasts, minors in age-gated rooms
Participant identity and IP addresses Stalking and doxxing risk, especially for creators
API keys A backend key administers a whole tenant
Client tokens Admit a device to a room with specific rights
Moderation state and audit log Safety decisions and appeals depend on them
Prepaid credit Direct financial value
Threat Controls
Eavesdropping on media TLS 1.3 on every QUIC connection. SFrame E2EE is required by default: the SDK will not publish media without a key and drops unencrypted media it receives, so relays carry only ciphertext. Only a backend can turn it off, per room, with a reason that is written to the audit log.
Silently weakening a room A room’s encryption policy is carried in the signed client token (e2ee claim); a token without the claim is treated as required. Turning encryption off needs actor.reason and is recorded as a policy_change in the moderation log.
Tampering with media in transit QUIC/TLS integrity; with SFrame, altering an object’s group, number, priority or properties fails authentication.
Stolen client token Lifetimes ≤ 15 minutes; tokens are bound to a key generated on the device; scopes limited to one tenant, room and set of grants; checked on every request.
Leaked API key mqk_ prefix for secret scanning; secrets stored only as hashes; revoke instantly with DELETE /v1/keys/{id}; separate relay keys that can only report usage.
Privilege escalation via minting A service account can never mint a token more powerful than its own ceiling; ownership is not grantable; the device-facing join endpoint never mints moderation or ownership.
Moderator abuse Capabilities are sets with a strict-superset rule; owners are immune; every action is attributed (service_account, on_behalf_of) in an append-only audit log; reading the log is a separate capability.
Evading a ban Bans by account, device or address; checked on every subscribe and publish; a ban ends the live session, not only the next join.
Replayed moderation from a retrying queue Idempotency keys on every mutating call.
Hidden observers Recorders and classifiers must be attached as watchdogs, which are disclosed to the room; rooms can prohibit recording entirely.
Cross-tenant access Every call names a tenant and must match the credential’s; resources in other tenants return NOT_FOUND, so existence cannot be probed.
Credential probing Authentication failures never say which part of a credential was wrong; comparisons are constant-time.
Abuse of public endpoints Signups limited per address; the join endpoint is rate-limited per credential and body-size capped.
IP exposure between peers Media goes through relays by default, so participants never learn each other’s addresses. The planned peer-to-peer mode is opt-in, E2EE-only, and never offered to audiences.
Denial of wallet Prepaid credit: spend is bounded by your balance; auto top-up only runs one charge at a time.
Malformed protocol input Every decoder is fuzzed continuously; strict body parsing rejects unknown fields.
  • Tokens refresh automatically. Give DeviceCredentials a renew closure that asks your backend for a new token. Two minutes before the current one expires, the SDK fetches the next and presents it to the relay inside the open session, without reconnecting, retrying every 15 seconds if your backend is briefly unreachable. Without a renew closure the session ends when its token does.
  • Every stream carries auth. The relay checks every announce, subscribe and fetch against the session’s token: tenant, room, user, device and grants. Media and data tracks use the same namespaces, so chat, game state and telemetry are authorized exactly like audio and video.
  • Renewal can’t escalate. A renewed token must name the same holder and the same permissions. Promotions and room moves arrive as control-plane directives, not as a swapped token.
  • The relay admin port is unauthenticated and off by default. If you self-host, bind it to a private interface only.
  • E2EE key management today is a shared room key you distribute yourself. MLS-based group keys are designed but not yet built. Until your app calls useEncryption, a room that requires encryption will not let it publish.
  • Server-side features vs E2EE. In end-to-end encrypted rooms, server-side recording and classification cannot run, because the servers never see plaintext; on-device taps still work. A room that needs them has to be set to e2ee optional by your backend — see Turning it off for a room.
  • Authenticate your users before minting tokens. The join endpoint is not authentication.
  • Keep backend keys on servers. Never ship one in an app.
  • Give each self-hosted relay its own relay key and rotate on staff or host changes.
  • Pass on_behalf_of on moderation calls so the audit trail names the human.
  • Disclose any media you tap and send off-device; attach a watchdog to do it.
  • Perform age verification yourself before asserting age_verified.

Each room has a residency_zone, fixed at creation, that governs where the room’s data — including recordings, inference outputs and moderation evidence — may live.