# Security Overview

StratoFusion uses layered controls across the Next.js app, the VM-hosted rclone
worker, and the PostgreSQL data layer. The previous Fly.io deployment is
retained as a stopped recovery resource and shares the worker security
contract. This document reflects controls currently implemented in the repo.

## Authentication

- User authentication is handled by Clerk in `src/middleware.ts`.
- Public API matchers in middleware are backed by route-level auth checks for sensitive provider and rclone routes.
- Rclone endpoints enforce service-to-service authentication in `fly-rclone/src/middleware/auth.js` in both current VM and legacy recovery deployments.
- Cron routes validate `Authorization: Bearer <CRON_SECRET>` and use `guardProductionCronRequest()` where applicable.

## Authorization

- Operation polling and cancellation routes require an authenticated user and verify persisted ownership before returning data.
- Service-account and token queries are scoped by `userId` in `src/lib/database/service-accounts.ts`.
- Two-user negative tests cover service-account/token access, scheduled jobs,
  transfer operations, and AI indexing jobs. Foreign identifiers do not cause
  provider, worker, or mutation calls.
- Admin-only audit-log access is enforced in `src/app/api/logs/audit/route.ts`.
- Non-production dev, debug, demo, rclone-test, and popup-test paths are
  disabled in production and loopback-only in other runtimes. Other requests
  receive a private, non-cacheable `404` before authentication; route-level
  dev/admin checks remain defense in depth locally.
- Public documentation renders under `/docs`; `/dev/docs` is available only in
  a non-production runtime on a loopback host.

## Request Integrity

- `src/lib/auth/request-integrity.ts` is the centralized CSRF boundary for all
  mutating `/api/*` requests. The security scan currently inventories 71 route
  modules exporting `POST`, `PUT`, `PATCH`, or `DELETE`.
- Exact same-origin `Origin` or `Sec-Fetch-Site: same-origin` evidence is
  required for browser mutations. Cross-site, sibling-origin (`same-site`),
  opaque/malformed origins, and cookie-bearing requests without browser origin
  evidence fail closed with a non-cacheable `403`.
- Credentialless Stripe webhooks, cron invocations, and internal service calls
  can reach their handlers without browser headers; their existing signature,
  bearer, or service-authentication checks remain mandatory.

## Token Protection

- OAuth tokens are encrypted at rest through `src/lib/security/token-encryption.ts`.
- Service-token writes in `src/lib/database/service-accounts.ts` use `encryptTokenValue()`.
- Token reads decrypt through the shared helper rather than exposing raw encrypted values.
- Operational logging has been scrubbed to record token presence and length instead of token prefixes in the main transfer and refresh flows.

## Rclone Service

- `helmet` is enabled in `fly-rclone/server.js`.
- Service authentication is enforced before route handlers.
- Current production TLS terminates at Caddy. The retained Fly production
  config also enforces HTTPS for approved recovery.
- rclone execution paths use argument arrays with `spawn(...)`; no `shell: true` execution path is allowed by the security audit.
- Worker paths are decoded repeatedly, Unicode-normalized, and canonicalized
  across slash variants before traversal, drive-path, UNC-path, and system-path
  checks. Encoded and mixed-separator traversal attempts fail before rclone is
  invoked.

## Untrusted Files and URLs

- DOCX, XLSX, and PPTX extraction validates ZIP central-directory metadata
  before parser dispatch. Entry count, individual and aggregate expanded size,
  compression ratio, encryption, ZIP64 size markers, and unsafe entry paths are
  bounded or rejected.
- The configured rclone service URL permits the fixed loopback development
  endpoint or a pathless public HTTPS origin. Credentials, private/metadata
  authorities, URL paths, query strings, fragments, and remote HTTP are denied.
- The web download proxy validates the token shape and size before making an
  upstream request, refuses redirects, and returns a generic upstream failure
  instead of disclosing raw network details.
- Provider-supplied SharePoint links render only after validation as an HTTPS
  `sharepoint.com` origin without credentials or a custom port.

## VM Administrative Access

- The self-hosted VM remains headless; no desktop, RDP, VNC, browser IDE, or
  public container-management UI is part of the supported deployment.
- VS Code Remote SSH reuses the key-only `deploy` account. Password SSH and root
  SSH remain disabled.
- Tailscale provides the private management network without replacing the
  existing CI SSH path during the current migration phase.
- Optional Cockpit binds only to `127.0.0.1:9090` and is published to approved
  tailnet administrators through Tailscale Serve on the private tailnet.
- Cockpit uses a separate human operator account with password-protected sudo;
  the account is not a Docker-group member and the `deploy` account remains
  passwordless.
- No Cockpit route is added to public Caddy and no public UFW rule is added for
  its backend or tailnet HTTPS port.

See [Private VM Administration](./operations/VM_ADMIN_ACCESS.md) for the exact
trust boundary and rollback sequence.

## Web Security Headers

- `next.config.js` and `vercel.json` configure HSTS, CSP frame/object/base restrictions, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Referrer-Policy`, and `Permissions-Policy`.
- Next.js framework disclosure through `X-Powered-By` is disabled. A
  production-mode loopback Playwright regression verifies the header policy,
  non-production route isolation, and cross-site mutation rejection.
- `Cross-Origin-Opener-Policy` remains `unsafe-none` for OAuth popup compatibility. OAuth state and same-origin BroadcastChannel handling are the compensating controls.
- Direct Google, OneDrive, and Dropbox authorization-code flows use an opaque
  HMAC-SHA256-signed `state` value with a cryptographically random nonce and a
  ten-minute expiry. Callback routes reject missing, expired, malformed,
  legacy unsigned, or tampered state before exchanging the authorization code.
  Popup state is issued only for an authenticated Clerk user. The signing key
  uses `OAUTH_STATE_SECRET` when configured and otherwise the server-only token
  key or Clerk secret hierarchy. Provider-token encryption itself requires an
  independent 32+ byte `TOKEN_ENCRYPTION_KEY`, identifies ciphertext keys in
  authenticated v2 envelopes, and accepts earlier keys only through the
  explicit server-side rotation ring. Plaintext token reads fail closed.

## Database and Auditability

- `src/lib/database/client.ts` normalizes remote database URLs to require TLS via `sslmode=require`.
- Transfer operations persist ownership metadata in `transfer_operations`.
- Structured system audit logs already exist in `system_audit_logs`.
- Admin audit-log reads are served through `src/app/api/logs/audit/route.ts`.

## Administrative MFA

- Every `/admin` page is guarded by the server layout, every `/api/admin`
  handler applies the same resource-level policy, and middleware provides an
  additional early boundary.
- An administrator is allowed only when the signed Clerk v2 session token has
  a finite, non-negative second-factor age in `fva[1]`. Missing or malformed
  claims and Clerk's `-1` no-second-factor marker fail closed.
- Code enforcement does not configure the Clerk instance. An authorized Clerk
  administrator must enable supported MFA strategies, require enrollment, and
  retain redacted production evidence for every configured StratoFusion admin.

## Current Follow-up Items

- Add explicit request rate limiting for the rclone worker if service exposure expands.
- Capture mandatory Clerk MFA configuration and successful second-factor
  evidence for every exposed production admin before assessment.
- Capture production session-cookie, expiry, rotation, and revocation evidence.
- Capture authenticated browser traces proving same-origin mutations succeed
  and assessor-generated cross-origin mutation attempts are rejected.

## Session Timeout Safety

- Auto-logout is operation-aware for foreground copy, move, upload, and download work. Active rclone operation IDs registered by the transfer dialog, direct uploads, and browser downloads keep the inactivity timer alive until every tracked operation reaches a terminal state.
- Logout clears in-memory account/search state plus all `localStorage` and
  `sessionStorage` data before Clerk sign-out. The explicit app logout and
  reauthentication flows first attempt authenticated server-side provider-token
  cleanup; Clerk sign-out still proceeds if that cleanup is unavailable.
- `AuthWrapper` also clears both browser stores when Clerk transitions from a
  signed-in to a signed-out state, covering Clerk-hosted sign-out controls.

## Reporting

- The repo-aware audit scripts live in `scripts/security-audit/`.
- In PowerShell, run the audit scripts with `pnpm exec tsx scripts/security-audit/<script>.ts`.
- If using `scripts/security-audit/run-all.sh`, invoke it explicitly with Git Bash: `C:\Program Files\Git\bin\bash.exe scripts/security-audit/run-all.sh`.
- Run `pnpm casa:security` for the deterministic local security gate. It checks
  both production dependency graphs, invokes all six scans, generates the
  current report and CycloneDX SBOMs, verifies immutable workflow/action/image
  and non-production-route policy, validates the 48-control assessor handoff,
  and checks changed files for high-confidence secret signatures, diff errors,
  and the 500 LOC limit.
- `pnpm exec tsx scripts/security-audit/generate-report.ts` writes current
  scanner results to the private `audits/casa/evidence/` tree. It intentionally
  does not infer historical fixes or test results.
- `pnpm casa:sbom` refreshes the web and worker CycloneDX inventories under
  `audits/casa/evidence/phase-1/`.
- CASA readiness evidence is tracked in `public/docs/security/CASA_TIER_2_READINESS.md`; private engineering evidence remains in the repository and is not a public production route.

## Active Security Test Safety

- Playwright defaults to a self-started production build on loopback. The
  credential-free anonymous security suite uses a narrow two-flag test runtime
  (`SKIP_ENV_VALIDATION` plus `E2E_ANONYMOUS_RUNTIME`) that is set only by the
  local Playwright server command and is not a supported deployment mode.
- `pnpm casa:e2e:preflight` reports each capability without printing secret
  values. Authenticated suites require an explicit storage-state file;
  identity, billing, and provider mutations also require separate opt-ins and
  isolated test credentials.
- Remote origins require an explicit read-only opt-in. Anonymous active tests
  and mutation suites fail closed for every remote application origin,
  including production, so a test command cannot probe mutations, create,
  delete, bill, or change identity state there.
