Appearance
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
429shape, 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.
| Vector | Threat | Proposed mitigation | Mechanism owner |
|---|---|---|---|
| Invitations | Invitation-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 flows | Token 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 access | A 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 URLs | A 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 storage | A 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 access | An 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 class | Threat controlled | Proposed limit posture | Keying |
|---|---|---|---|
| Public / unauthenticated (invitation preview, safe reads) | Enumeration, scraping, existence probing | Tight per-IP and per-token limits; no account enumeration in responses | IP + token |
| Auth (login, token, provider callback) | Credential stuffing, brute force, code-guessing | Strict per-identity and per-IP limits with backoff; Keycloak-side limits plus application-side | identity + IP |
| Invitation create/resend | Invite spam, contact harvesting | Per-workspace and per-contact caps; feeds the observability-plan invitation-abuse alert | workspace + contact |
| Mutations / idempotent commands | Double-submit, retry storms, abuse | Moderate per-user limits; idempotency keys absorb legitimate retries per api-contract-standard | user + context |
| Media upload-intent | Storage-fill abuse, scan-worker exhaustion | Per-user and per-workspace intent limits; bytes gated further by media-security fail-closed scanning | user + workspace |
| Client-diagnostics | Telemetry flooding | Best-effort, rate-limited, drop-on-limit; never fails another product operation per api-contract-standard | user + device |
| Support/admin | Operator abuse, bulk exfiltration | Grant-scoped and audited; volume anomalies are an audit and observability concern | operator + 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.
| Scan | Proposed cadence | Failure posture |
|---|---|---|
Secret scanning (security:secrets, Gitleaks) | Every PR and pre-push; scheduled full-history scan | A detected secret blocks merge and triggers rotation of the exposed credential. |
Dependency audit (security:dependencies) | Every PR and on a scheduled interval | A 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 gate | Aggregates 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
| Concern | Proposed 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. |
| Transport | HTTPS everywhere; HSTS on production web; TLS terminated at the reverse proxy per environment-runbook. |
| Provider surfaces | Only 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.
| Concern | Proposed rule |
|---|---|
| Storage | Access 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. |
| Lifetime | Short-lived access tokens with refresh; the app holds no long-lived bearer token beyond the refresh mechanism. |
| Purge | Logout, 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 logged | Tokens, refresh tokens, and authenticated session ids are never written to logs, Sentry context, or the client-diagnostic summary, per observability-plan. |
| Separation | Coached-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.
Related Docs
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.