security.md 2.4 KB

Security notes

Authentication & authorisation

  • Okta OIDC (Authlib) validates state, nonce, and the id_token signature (JWKS from discovery). The app trusts the email claim and checks it against its own allowed-users list. Roles are re-resolved per request, so removal/demotion is immediate.
  • Static mode is development-only: loud startup warning, hmac.compare_digest credential check, and it refuses to bind a non-loopback interface without PPSQ_ALLOW_INSECURE_AUTH=1.

Sessions & CSRF

  • Cookies: HttpOnly, SameSite=Lax (required for the OIDC callback), Secure when cookie_secure=true. Browser-session only — no persistent cookie, no idle-timeout cookie; closing the browser ends the session.
  • CSRF: a per-session token from /api/config must be echoed as X-CSRF-Token on every state-changing /api/* request. Enforced in before_request.
  • The post-login next target is sanitised to a local path (safe_next) — no open redirect.

Secrets

  • pps.password, okta.client_secret, auth.static.password, app.secret_key are write-only: never returned by any endpoint (redacted() emits a *_set boolean). A blank value on save means "unchanged".
  • config.toml is written 0600. It is plaintext on disk — see "Known limitations".

Input validation

  • Config writes are validated before the atomic write (URLs, ranges, folder membership, pipeline step order, emails/roles). Folder names may not contain commas (they'd corrupt the localguid join in the PPS POST payload).
  • POST /api/actions validates targetfolder against the configured folder list — a user cannot move mail to an arbitrary folder.
  • The log viewer's source is an enum key resolved to a path server-side; there is no client-supplied path (no arbitrary file read).

Transport

  • Put a TLS-terminating reverse proxy in front; bind the app to loopback.
  • verify_tls controls whether the app verifies the PPS certificate. false (self-signed) is common for PPS but should be a CA-bundle path where possible; the client logs a loud warning when verification is disabled.

Known limitations (accepted for internal use)

  • config.toml holds secrets in plaintext (0600, never readable via the app). Real secret management (Vault, env injection) is a future step.
  • Roles come from the app's own list, not Okta groups.
  • Single-process by design; no HA/failover.