CapacityLens threat model
Version: 2026-08-18. Review this model after changes to authentication, tenancy, imports, offline storage, deployment topology or external services.
Security objectives
- A user can read or change only accounts, operations, records and protected fields allowed by their current server-side membership and role.
- Passwords, session tokens, reset/invite tokens, MFA seeds/recovery codes and provider secrets are not disclosed or stored in recoverable form where a safer representation is possible.
- Tenant writes remain valid, atomic and attributable; corrupted relational state prevents startup.
- Browser-delivered code cannot silently turn an authenticated browser into a cross-site write primitive, and sensitive API responses are not cached.
- Operators can contain a compromised identity, restore authoritative data and investigate typed security/audit events without relying on the compromised host alone.
CapacityLens is not a safety-critical, payments, classified-data, real-time media or anonymous public-upload system. Availability is important but confidentiality and tenant integrity take priority over keeping a misconfigured production process running.
Assets and trust boundaries
| Asset | Primary protection | Boundary |
|---|---|---|
| Account schedule and private client/project names | Server membership, action/field authorization, SQLite constraints | Browser/API and tenant boundary |
| Identity, password, MFA and provider-link state | Better Auth, versioned scrypt, encrypted recovery/tokens, explicit verified linking | Auth/provider/API and database boundary |
| Session, reset and invite bearer values | HttpOnly cookies or one-time values; hashes where supported; expiry/revocation | Browser/API and operator delivery boundary |
| Offline snapshot | Opt-in, role-filtered, AES-256-GCM, seven-day expiry, viewer-only | Browser-origin/device boundary |
| Database, WAL, audit and snapshots | 0600 files, 0700 backup directory, optional encrypted-volume attestation | Process/host boundary |
| Audit and security events | Data-minimised JSON, local restrictive file plus optional separately forwarded stream | Process/log-collector boundary |
| Build and release inputs | Lockfile, pinned images/actions, dependency review, SBOM, scans and provenance | Contributor/CI/registry boundary |
The packaged production flow is browser → public TLS proxy → unprivileged web container → verified per-install internal TLS → unprivileged HTTPS API container → local SQLite/backup volumes. A bare-metal deployment may instead use HTTP only over a same-host loopback hop. In both shapes the API must not be publicly reachable and the proxy must overwrite rather than append forwarding headers.
Actors
- Viewer, editor, admin and owner, all potentially malicious within their legitimate account.
- An authenticated user attempting cross-account or higher-role access.
- An unauthenticated internet attacker, automated credential attacker or cross-site origin.
- A compromised browser profile or device.
- A malicious/compromised identity provider, dependency, build input or container base.
- A self-hosting operator who makes an accidental or unsafe configuration choice.
- A host operator misusing stopped-server SSO repair or sole-Owner recovery authority.
- A host-level attacker. Host compromise is not fully preventable in-process; encrypted storage, secret management, isolation and off-host logs/backups limit consequences.
Abuse cases and controls
| Threat | Principal controls | Verification |
|---|---|---|
| BOLA/IDOR or cross-tenant mutation | Membership fetched server-side for each operation; every scoped entity has accountId; row-addressed generic writes make absent and foreign ids response-indistinguishable; row/reference validation fails closed when a project-bound allocation cannot resolve its project in the same account; cross-account tests | app.authz, app.members, tenant-store, route and shared mutation tests |
| Function/field privilege escalation | Central action matrix; protected-name projection/preservation; owner-only import; fresh session for privileged actions | access, privacy and route tests |
| Credential stuffing/password cracking | Positive global/API throttling; five-attempt MFA lock; 15–128 Unicode code points; HIBP range check; scrypt N=2^17,r=8,p=1; no default password | password/auth/rate-limit tests |
| Password-only account takeover | Opt-in required TOTP wall before tenant data; otherwise long passwords, HIBP by default, scrypt, throttling and bounded/revocable sessions; one-time MFA recovery codes | real auth integration and UI tests |
| Session theft/fixation | Secure HttpOnly SameSite __Host- cookies; new token on auth; fixed 12-hour and 30-minute idle limits; revocation/reset invalidation; session inventory | auth and member revocation tests |
| CSRF and cross-origin data use | Unsafe-method Origin/Sec-Fetch-Site rejection; exact configured or trusted-proxy-derived same origin; SameSite cookie; safe HTTP methods | CSRF/CORS and packaged-proxy tests |
| Injection/XSS/mass assignment | React text rendering; no untrusted HTML; parameterized SQLite; explicit table/column codecs; sanitisation and structural limits; CSP | server/shared/CSP tests |
| Malicious, stale or oversized import | JSON-only, 5 MiB/200,000-record caps; Owner-only access; bounded/cancellable worker preparation; schema migration/sanitisation; tenant remap; reference validation; exact-snapshot recheck and atomic replacement | import, worker, transaction and mutation tests |
| Offline cache disclosure/tampering | Role-filtered input; non-extractable device key; AES-GCM with random IV/AAD; tamper/expiry deletion; viewer-only | offline cache tests |
| Database corruption/partial write | Startup foreign-key check; WAL; transactions; optimistic concurrency; sync-session ordering/provenance; atomic imports/backups | migration, ordering, transaction and restore-drill tests |
| Log erasure/injection or invisible attack | Structured serialization, no values/credentials, restrictive modes, health degradation latch and optional separate JSON forwarding | audit/log/production-guard tests |
| SSRF/provider substitution | Operator-only exact issuer/discovery; endpoints validated before redirect or secret use; HTTPS outside loopback; no credentials/redirects; 10-second and 1 MiB provider-response bounds; signed ID-token issuer/audience/timestamp verification; JWKS rotation and user-info subject binding | strict OIDC crypto/exchange tests and pinned Dex browser conformance |
| Federated identity takeover or unsafe cutover | Verified matching email for explicit links; implicit linking disabled; provider/subject uniqueness; durable admission evidence; preflight/repair; SSO-only startup interlock and atomic incompatible-state revocation | identity-port, cutover, migration and strict-OIDC tests |
| Operator recovery misuse | Stopped server and exclusive SQLite lock; unique sole-Owner eligibility; ordinary expiring single-use reset; rollback on partial failure; token-free audit record | Owner-recovery CLI and audit tests |
| Resource exhaustion | 512 accepted-socket ceiling; per-IP application/CSP rate limit with constant-work health exempt; bounded scrypt, HIBP and import queues; 5,000-operation batch and 200,000-record import caps; request/queue/provider timeouts | resource-queue/rate-limit/health/import/CSP tests |
| Supply-chain compromise | Exact lockfile, pinned action/base-image commits/digests, Dependabot, CodeQL, Gitleaks, dependency review, SBOM, Trivy, ZAP and tagged provenance | local gates and public/manual workflows |
Residual and accepted risks
- Strict OIDC is first-class. Better Auth owns state, PKCE and cookies; the account adapter owns issuer-pinned endpoint selection, the bounded no-redirect code exchange, ID-token signature/audience/timestamp checks, JWKS rotation and user-info subject equality. Named social providers remain experimental and every IdP still needs staging interoperability, MFA-policy and logout/session-lifetime testing.
- IdP disablement does not revoke already-issued local sessions. The accepted maximum is the remaining twelve-hour absolute lifetime or thirty minutes inactivity; local revocation is an incident-response requirement and back-channel logout must be reconsidered before hosted GA.
- Identity masquerade is a session-scoped, account-confined read projection for Owners and Admins. A root unsafe-method guard and a separate Better Auth proxy guard reject changes while it is active; actor-dependent reads use the target member's role and redaction. The process-local registry deliberately disappears on restart, which ends access rather than restoring uncertain state. Start and end events are audited, with the start event carrying the session expiry that bounds a record if the process stops before an end event can be written.
- SSO-only cutover retains dormant password credentials for the documented mixed-mode rollback. Protecting the host/database and using the stopped-server repair tooling carefully remain operator responsibilities.
- The sole-Owner recovery command is deliberate host-operator authority. It cannot be contained from an attacker who already controls the application database and process environment.
- Required TOTP is optional. Password-only deployments do not meet ASVS 5.0 Level 2 requirement V6.3.3; when enabled, TOTP meets L2 but remains phishable and insufficient for L3.
- Existing legacy Better Auth scrypt hashes use the former weaker profile until the user changes or resets the password. They are verify-only; new material never uses that format.
- The application has no IP/device-risk engine, anomalous-login user notification, global administrator “revoke everyone” control or HSM/full-memory encryption.
- Public TLS, encrypted host volumes, secret-manager/HSM use, clock synchronization, log retention and off-host collection are deployment controls. Internal service TLS is application-packaged for Compose and optional for a same-host bare-metal hop; startup warnings cannot verify external controls.
- An unlocked or compromised application origin can invoke its non-extractable offline key. Device encryption, patching and profile access control remain necessary.
- A single-process SQLite service can still be denied service by sufficient network or tenant-valid load. Edge rate limiting, connection limits and resource monitoring remain operator controls.