Skip to content

Authority Doc / Public V1 Security

The Security Plan defines CoachMe's threat model, rate limits, secret posture, scanning, and client-side hardening for Public V1. ​

This page is the proposed Public V1 authority for the application-security posture: the threat model across invitations, auth and token flows, relationship access, media URLs, offline and mobile storage, and support access; the rate-limit strategy per endpoint class; secret management across environments; dependency and secret scanning cadence; CSP and CORS posture; mobile token storage; and the scope of the security audit log. It sets posture and strategy; it does not re-decide the access model, the secret store, or the privacy incident workflow.

DraftThreat modelRate limitsFail closedPublic V1

Decision status: Draft 2026-07-18 — pending review

Nothing on this page is approved yet. Every control marked Proposed is a recommendation for review, not a settled decision. This page references the support/admin grant model, the secret store, and the privacy incident workflow owned by adjacent authorities rather than restating or resolving them. The dependency and secret scanners it proposes a cadence for are currently deferred placeholders in the app repo (see below); this page does not claim they are implemented.

Note

This page does not own the permission model, the secret storage mechanism, privacy incident response, or auth-provider selection. roles-and-permissions owns the support/admin grant model and permission gates; environment-runbook owns the secret store, rotation mechanics, and CORS-origin config; privacy-and-retention owns the incident workflow and data classes; integration-register owns Keycloak and the external identity providers. This page owns only the security posture and threat treatment those contracts operate inside.

Scope ​

This page owns the security strategy. Adjacent authorities own the mechanisms it hardens.

This Page Owns ​

  • The Public V1 threat model and per-vector mitigation posture.
  • The rate-limit strategy by endpoint class.
  • The application-side secret-handling posture across environments.
  • The dependency-scanning and secret-scanning cadence.
  • CSP and CORS posture for coach web and the API.
  • Mobile access/refresh token storage requirements.
  • The scope of security-relevant audit events.
  • Required security evidence.

This Page Does Not Own ​

  • Roles, operating contexts, permission gates, and the support/admin grant shape, owned by roles-and-permissions. This page references that model and does not restate it.
  • The secret store, rotation mechanics, environment isolation, and the CORS-origin config values, owned by environment-runbook.
  • Privacy classification, the incident-response workflow, breach records, and legal hold, owned by privacy-and-retention.
  • Auth-provider and identity-broker selection (Keycloak, Google, Apple, Telegram OIDC), owned by integration-register.
  • The tenant fence and cross-tenant integrity scan, owned by multi-tenancy.
  • Signed-URL scoping and TTLs, malware scanning, and metadata stripping for media, owned by media-security.
  • REST error envelopes, the 429 shape, request-id validation, and idempotency, owned by api-contract-standard.
  • Telemetry redaction, incident detection, and the security-alert delivery, owned by observability-plan.
  • Dependency version pins, scanner package selection, and CI command shape, owned by toolchain-contract and gated by test-and-release-gate.

Threat Model ​

Each row names an attack vector, the concrete Public V1 threat, the proposed mitigation, and the authority that owns the mechanism. The mitigation is posture; the owning doc holds the rule.

VectorThreatProposed mitigationMechanism owner
InvitationsInvitation-link guessing, enumeration, reuse, or delivery to the wrong contact; abuse to spam invites.High-entropy opaque tokens; single active binding to one verified contact; expiry, revoke, and consume states; safe public preview that leaks no existence signal; rate and abuse limits on create/resend.workflow-state-map, roles-and-permissions, api-contract-standard
Auth / token flowsToken theft, replay, forged issuer/audience, privilege escalation via a mis-scoped token, account creation from an unverified provider token.Server-side token validation with issuer/audience checks; short-lived access tokens with refresh; never create an account from an unverified claim; email/password fail-closed; Keycloak as the token authority.integration-register (Keycloak/providers), roles-and-permissions
Relationship accessA coached client's request is bound to the coach's workspace, so a weak check exposes the whole workspace or another client's records.The tenant fence gates the workspace half; relationship, role, lifecycle, and data rules gate the rest; backend authorization is final on every read, mutation, upload, playback, export, and sync acceptance.multi-tenancy, roles-and-permissions
Media URLsA leaked or long-lived signed URL exposes health-adjacent media; a guessable object path bypasses permission.Private buckets; single-key, method-scoped, short-TTL signed URLs re-checked at issuance; no public objects; no URL persistence. Owned in full by media-security.media-security
Offline / mobile storageA lost or compromised device exposes cached client data, tokens, or rejected-operation payloads.Access/refresh tokens in platform secure storage (below); caches scoped by user/device/workspace/relationship; cache purge on logout, relationship end, revocation, and deletion; raw diagnostics never in normal caches.offline-sync-protocol, roles-and-permissions, privacy-and-retention
Support accessAn operator reads or mutates user data without cause, or a telemetry lookup silently becomes product-data access.Target-scoped, reason-coded, time-limited, audited support/admin grants; telemetry lookup is not authorization; support/admin cannot silently mutate user-owned values. Grant model owned by roles-and-permissions, not restated here.roles-and-permissions

Backend authorization is final

This page hardens the paths into the application; it never replaces the permission formula. Every mitigation above composes with the authenticated user + selected operating context + role + workspace/relationship ownership + lifecycle state + app surface + data/source rule gate owned by roles-and-permissions. Rate limits, CSP, and token hardening reduce attack surface; they do not decide who may see what.

Rate-Limit Strategy ​

Rate limits are proposed per endpoint class, not per route, so a feature endpoint inherits its class limit rather than inventing one. The 429 envelope and retry metadata are owned by api-contract-standard; the invitation-abuse alert is owned by observability-plan. Values below are Proposed starting points, tuned against real traffic.

Endpoint classThreat controlledProposed limit postureKeying
Public / unauthenticated (invitation preview, safe reads)Enumeration, scraping, existence probingTight per-IP and per-token limits; no account enumeration in responsesIP + token
Auth (login, token, provider callback)Credential stuffing, brute force, code-guessingStrict per-identity and per-IP limits with backoff; Keycloak-side limits plus application-sideidentity + IP
Invitation create/resendInvite spam, contact harvestingPer-workspace and per-contact caps; feeds the observability-plan invitation-abuse alertworkspace + contact
Mutations / idempotent commandsDouble-submit, retry storms, abuseModerate per-user limits; idempotency keys absorb legitimate retries per api-contract-standarduser + context
Media upload-intentStorage-fill abuse, scan-worker exhaustionPer-user and per-workspace intent limits; bytes gated further by media-security fail-closed scanninguser + workspace
Client-diagnosticsTelemetry floodingBest-effort, rate-limited, drop-on-limit; never fails another product operation per api-contract-standarduser + device
Support/adminOperator abuse, bulk exfiltrationGrant-scoped and audited; volume anomalies are an audit and observability concernoperator + case

Redis owns the short-lived rate-limit counters per environment-runbook. Limits fail safe: when the limiter itself is unavailable, security-sensitive classes (auth, invitation) fail closed rather than open.

Secret Management Posture ​

The secret store and rotation mechanics are owned by environment-runbook. This page owns the application-side posture that keeps secrets out of the wrong places, and it does not restate the config classes.

  • No secrets in Git, in docs examples, in frontend or mobile bundles, or in CI logs — enforced by secret scanning below, not by convention alone.
  • Production secrets never run in local or CI; local uses safe sample credentials only.
  • Provider credentials live in Keycloak, the deployment secret mechanism, or approved backend-only config; the app reads secret names, not values, in runtime config.
  • Support/admin diagnostics redact secret values.
  • Rotation is a reviewed deployment change with audit evidence, per environment-runbook.

Dependency And Secret Scanning ​

Automated scanning is a required cadence, not a one-time check. The scanners are currently deferred placeholders in the app repo: docs/foundation-todo.md records that real pnpm security:secrets, pnpm security:dependencies, and pnpm security:check implementations — with a pinned Gitleaks and a dependency-audit policy — are intentionally not yet wired, because placeholder commands would provide misleading merge evidence. This page proposes the cadence they run at once implemented; the package selection and pinning are owned by toolchain-contract and the merge/release gating by test-and-release-gate.

ScanProposed cadenceFailure posture
Secret scanning (security:secrets, Gitleaks)Every PR and pre-push; scheduled full-history scanA detected secret blocks merge and triggers rotation of the exposed credential.
Dependency audit (security:dependencies)Every PR and on a scheduled intervalA known-vulnerable dependency at or above the policy severity blocks merge until patched, isolated, or explicitly waived with a recorded reason.
Aggregate check (security:check)Release-candidate gateAggregates the above plus configuration checks into one release-blocking signal.

This cadence aligns with the integration-register change-control trigger that a security advisory or unsupported version must be patched, isolated, replaced, or blocked before release.

CSP And CORS Posture ​

ConcernProposed rule
CORS (API)Allowlist the known coach-web and app origins from the environment-runbook backend config; no wildcard origin; credentials allowed only for allowlisted origins. Origin values are Environment-owned config, not set here.
CSP (coach web)A strict Content-Security-Policy: default-src 'self', explicit allowlists for the API and approved provider origins (Sentry ingest, GIPHY where a client call is approved), no unrestricted inline script, frame-ancestors locked down.
TransportHTTPS everywhere; HSTS on production web; TLS terminated at the reverse proxy per environment-runbook.
Provider surfacesOnly providers the integration-register approves for direct client access (Keycloak redirects, Expo token acquisition, GIPHY client search) appear in the CSP allowlist; everything else routes through the CoachMe backend.

Mobile Token Storage ​

Access and refresh tokens are the highest-value secret on a client device and get the strongest client-side control.

ConcernProposed rule
StorageAccess and refresh tokens live in platform secure storage — iOS Keychain and Android Keystore (via the Expo secure-store boundary) — never in AsyncStorage, the offline SQLite cache, or any plaintext store.
LifetimeShort-lived access tokens with refresh; the app holds no long-lived bearer token beyond the refresh mechanism.
PurgeLogout, account switch, device revocation, relationship end, permission revocation, and account deletion purge tokens and scoped caches — the same triggers privacy-and-retention and roles-and-permissions require for cache purge.
Never loggedTokens, refresh tokens, and authenticated session ids are never written to logs, Sentry context, or the client-diagnostic summary, per observability-plan.
SeparationCoached-client and personal-workspace caches — and their tokens/session scope — remain separate even for one account, per roles-and-permissions.

Audit-Log Scope ​

The audit-record schema, immutability, and access rules are owned by privacy-and-retention (audit data class) and roles-and-permissions (which actions require an audit event). This page names the security-relevant events that must be in scope, without redefining the record.

Security-relevant audit events include: support/admin grant issuance and every sensitive read, playback, download, export, delete, or redaction under it; invitation resend, revoke, and claim conflicts; authentication and provider-linking anomalies escalated for review; permission and role changes; secret rotation; cross-tenant integrity-scan violations (a privacy incident per multi-tenancy); and audit-log access itself, logged with operator, reason code, scope, and retention reference.

An operational log is not an audit record: it may carry an audit-event id for lookup but never replaces the append-only fact, per observability-plan and privacy-and-retention. Suspected exposure hands off immediately to the privacy-and-retention incident workflow; this page introduces no competing legal or notification process.

Required Evidence ​

Security posture is proven by negative tests, not by inspection.

  • Invitation tokens resist enumeration; expired, revoked, and consumed tokens fail closed with no existence leak.
  • Auth endpoints throttle credential stuffing and code-guessing; an unverified provider claim never creates an account.
  • A coached client cannot read another client's records or workspace internals; backend authorization refuses even when a client-side guard was bypassed.
  • Rate limits engage per class and fail closed for auth and invitation when the limiter is unavailable.
  • Secret scanning blocks a planted secret in a PR; dependency audit blocks a known-vulnerable package at policy severity.
  • Coach web serves the strict CSP and rejects a disallowed CORS origin.
  • Mobile tokens reside only in platform secure storage and are purged on every required trigger; a device backup or cache dump exposes no token or session id.
  • Every security-relevant action emits its audit event; audit-log access is itself audited.

Proposed Locks And Open Questions ​

Because this is a draft, nothing here is locked. On approval, the first group is intended to become posture rules; the second stays open.

Proposed To Lock On Approval ​

  • The per-vector threat model and fail-closed posture across the six vectors.
  • Rate limits by endpoint class, failing closed for auth and invitation.
  • Secure-storage-only mobile tokens, purged on every privacy/permission trigger.
  • Strict CSP and allowlisted CORS for coach web and the API.
  • Secret and dependency scanning as a required PR-plus-scheduled cadence once the deferred scanners are implemented.
  • The security-relevant audit-event scope, layered on the privacy-owned audit record.

Deliberately Open ​

  • Exact rate-limit numbers, tuned against real traffic.
  • Scanner package selection, pins, and severity policy, owned by toolchain-contract; this page owns only the cadence.
  • Production topology and data region, which shape TLS, WAF, and network posture and remain open in the development readiness review until real data is near.
  • Telegram OIDC login, still a conditional provider pending the BotFather → Keycloak spike in the integration-register.

These pages own adjacent rules this page references but does not redefine.

Roles And Permissions ​

Owns the permission formula, operating contexts, and the support/admin grant and audit-requirement model this page's threat mitigations compose with.

Multi-tenancy ​

Owns the tenant fence and the cross-tenant integrity scan that back the relationship-access threat mitigation.

Privacy And Retention ​

Owns data classes, the audit record, the incident-response workflow, and the cache-purge triggers this page's mobile hardening depends on.

Environment Runbook ​

Owns the secret store, rotation, environment isolation, CORS-origin config, and TLS termination.

Integration Register ​

Owns Keycloak and the external identity providers, and the security-advisory change-control trigger.

API Contract Standard ​

Owns the 429 and error envelopes, request-id validation, idempotency, and the client-diagnostic transport this page rate-limits.

Media Security ​

Owns the media-URL and byte-path controls named in the threat model.

Offline Sync Protocol ​

Owns mobile cache scoping, receipts, and purge that the offline-storage threat mitigation relies on.

Observability Plan ​

Owns telemetry redaction, security-alert detection and delivery, and the boundary between diagnosis and sensitive recovery.

Toolchain Contract ​

Owns scanner package selection, version pins, and CI command shape.

Test And Release Gate ​

Owns which security checks block merge and release.

Development Readiness Review ​

Tracks this page as one of the three architecture docs required before implementation commits to schemas and APIs, and holds the open production-topology and residency items.

CoachMe internal planning documentation.