# Security, Privacy, and Threat Model

## Current posture

Core processing remains local browser code and needs no account. The dedicated
public Pages deployment optionally provides passkey accounts, explicit cloud
backup, revision history, and revocable player links. Its API uses a separate
D1 data plane, same-origin CSRF checks, owner authorization, strict server-side
player projection, rate limits, bounded retention, and `no-store` responses.
Analytics and production purchasing are disabled. The separate Cactus Club
education host remains protected by Cloudflare Access and does not host this
public service's authenticated API.

## Protected assets and boundaries

The primary sensitive asset is private GM data: names, notes, encounters,
undiscovered content, assets, routes, and metadata. Other assets include project
integrity, local storage, imported files, and the Access session. Boundaries are
project import, all text rendering, player projection/export, browser storage,
downloaded files, passkey sessions, cloud API requests, and public share links.

## Threats and controls

- **Player-data disclosure:** construct a reduced copy before rendering or
  serialization; do not ship GM data and hide it with CSS. Test forbidden field
  names and secret sentinel values in every player artifact.
- **Stored/reflected XSS:** render plain text by default. If Markdown is added,
  use a locally bundled, allowlist-based sanitizer; prohibit raw HTML, scripts,
  event handlers, and unsafe URLs.
- **Malicious imports:** enforce the configured byte limit before parsing,
  validate structure and bounds, cap collections/assets/text, reject unsupported
  versions, and keep the prior valid project intact. Archive/SVG import is not
  trusted merely because it is local.
- **Denial of service:** cap maps at supported dimensions, move expensive work
  off the UI thread, avoid recursive parsing, and fail exports without altering
  the project.
- **Cache/session leakage:** retain `no-store` and network-only behavior for the
  protected host. Never cache private cloud responses. Shared-device users must
  sign out of Access and remove browser data/downloads when appropriate.
- **Supply chain:** avoid remote dependencies; pin reviewed dependencies, retain
  notices, and run repository secret/dependency/static scans.
- **Logs:** never log full projects, GM notes, share tokens, credentials, billing
  events, or imported file bodies.

## Cloud and billing boundary

Cloud functionality is capability-gated and fails closed without its D1
binding. Optimistic revision checks prevent a device from silently replacing a
newer cloud copy. Recovery sessions can only add a replacement passkey; they
cannot read projects. Share tokens are stored as hashes and can be revoked.
The optional Stripe test-mode integration verifies signed webhooks, rejects
replays, uses idempotency keys, and stores no card data. It remains unavailable
unless every required test-mode secret and the distinct Creator, monthly Cloud,
and annual Cloud price identifiers are configured.

## Privacy choices

No analytics or telemetry is enabled by default. User-supplied author metadata
is optional and appears in files only when entered. Complete project exports may
contain all private content; player exports are intentionally reduced. See the
plain-language privacy notice for user-facing details.
