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.