Security Invariants
SPAPS is security-sensitive because it touches identity, payment state, wallet signatures, email, webhooks, and encrypted PII. The code uses startup validation and dependency checks to fail closed before unsafe configuration can receive traffic.
Startup Invariants
| Invariant | Why it exists |
|---|---|
| JWT and refresh secrets must be present for SPAPS settings | Tokens cannot be trusted without signing material |
| Legacy API-key auth must be disabled in production | Plaintext legacy key paths are migration-only |
| Wildcard CORS with credentials is rejected in production | Browser credentials must not be exposed to arbitrary origins |
| Webhook destination governance must be enabled in production | Webhook delivery should not target arbitrary domains |
| Self-service endpoints need network boundaries in production | Operator tools should not be internet-open |
PII-enabled applications require SPAPS_PII_MASTER_KEY | Encrypted fields must not fail open |
| Production email overrides are rejected | Test recipient overrides must not leak into production |
Runnable Checklist
| Check | Command or signal | Expected result | Failure mode |
|---|---|---|---|
| Local persona mode is explicit | curl -fsS http://localhost:3301/health/local-mode | Response names local-mode state and hints | Local-mode docs or runtime mode are stale |
| Readiness fails closed on dependencies | curl -fsS http://localhost:3301/health/ready | Database, Redis, and PII checks are named | PII or dependency startup invariant is unresolved |
| Browser origins are allowlisted | Inspect the owning app row and CORS_ALLOW_ORIGINS | No wildcard credentialed production origin | Publishable-key traffic could cross app boundaries |
| Webhook destinations are governed | Check WEBHOOK_ALLOWED_DOMAINS before production | Destination policy exists for outbound webhooks | Webhook delivery can target arbitrary domains |
| PII key exists when needed | Check SPAPS_PII_MASTER_KEY in the runtime secret source | PII-enabled apps have encryption material | Encrypted fields cannot be safely read or written |
Request-time Invariants
Common Mistakes
A local environment containing production credentials is not a safe local environment. Startup safety exists to catch that case before a developer accidentally emails, charges, or leaks real data.
- Loosening startup checks to make local boot easier.
- Assuming a service secret can replace user identity.
- Allowing arbitrary webhook destinations during production testing.