Security
Security architecture, cryptographic choices, and hardening recommendations for Vaultaris.
Cryptography
| Purpose | Algorithm |
|---|---|
| Password hashing | Argon2id (memory-hard, OWASP-recommended parameters) |
| OAuth token signing | Rotating ed25519 / ECDSA P-256 keys (JWK set) |
| Internal token signing (MFA, setup) | HMAC-SHA256 (HS256) |
| Sensitive data at rest (TOTP secrets) | AES-256-GCM via vaultaris-crypto crate |
| Token generation | CSPRNG via OS (rand + getrandom) |
| PKCE challenge | SHA-256 + base64url |
| DPoP proof verification | ed25519 / P-256 ECDSA — RFC 9449 |
| WebAuthn credential signatures | ES256 (ECDSA P-256 + SHA-256), RS256 fallback |
| WebAuthn challenge binding | SHA-256 of clientDataJSON |
| Billing shadow service token | Constant-time comparison (subtle crate) |
DPoP — Sender-Constrained Tokens
DPoP (RFC 9449) prevents token replay attacks by cryptographically binding access and refresh tokens to the client's key pair. A stolen token cannot be used by an attacker who doesn't possess the private key.
Vaultaris verifies:
- The
DPoPheader JWT is signed by the key in thejwkclaim - The
htm/htuclaims match the actual request method and URL - The
iatclaim is within the acceptable clock skew window - The
jtiis unique (replay prevention) - The token's
cnf.jktthumbprint matches the proof's public key
WebAuthn / Passkeys
W3C WebAuthn Level 2 server-side verification (no external library). Covers: CBOR attestation object parsing, clientDataJSON verification, authenticatorData parsing, COSE public key extraction, sign counter validation (clone detection), challenge binding.
rpId and expected origin are derived from EXTERNAL_URL. Changing EXTERNAL_URL invalidates all stored passkey credentials.
Security properties:
- Challenges expire after 5 minutes, single-use
- Sign counter regression →
400response (indicates potentially cloned authenticator) - Unique
(tenant_id, credential_id_base64)constraint per tenant
Passwords
Argon2id via the argon2 crate. Constant-time comparison. Password reset tokens: 32 bytes → 64 hex chars, 24-hour TTL, single-use. Password policy enforcement: minimum length, complexity, history, max age, and lockout threshold are configurable per tenant.
Sessions
Sessions stored in PostgreSQL — not stateless JWTs. Instantly revocable server-side. Tied to IP and user-agent. Global sessions validate the requesting domain against a per-session allowlist with wildcard matching.
Device Registry
Every login registers or updates a device record derived from the User-Agent. New devices trigger an email alert to the user and fire the NEW_DEVICE_REGISTERED plugin hook, enabling security notifications and anomaly detection.
Admins and users can trust or revoke individual devices from the dashboard.
Rate Limiting
IP-level, evaluated before business logic. Default: 120 requests / 60-second window. Distributed via Redis sliding-window algorithm (Lua script). Falls back to in-memory fixed-window when Redis is unavailable. Returns 429 Too Many Requests with Retry-After.
Tenant Isolation
All queries are scoped by tenant_id at the middleware layer. AuthenticatedUser extractor enforces the tenant_id claim in the Bearer token matches the route's {tenant_id} path parameter. Cross-tenant access is impossible except from the master tenant.
Email Enumeration Prevention
Password reset always returns HTTP 200 regardless of whether the email exists in the system. The response body is identical in both cases.
Audit Trail
Every auth event, token issuance, role change, freeze/unfreeze action, and admin operation produces an append-only audit record with actor, action, target resource, timestamp, IP, and user-agent.
OWASP Top 10
| Risk | Mitigation |
|---|---|
| A01 Broken Access Control | Tenant-scoped DB queries + AuthenticatedUser middleware |
| A02 Cryptographic Failures | Argon2id, AES-GCM, CSPRNG, DPoP sender-binding |
| A03 Injection | SQLx parameterized queries only — no string interpolation |
| A04 Insecure Design | OAuth 2.0 + PKCE; email enumeration prevention; DPoP |
| A05 Security Misconfiguration | Sane defaults; production checklist in docs |
| A06 Vulnerable Components | Pure Rust audited crates; cargo audit in CI |
| A07 Auth Failures | Argon2 + token expiry + session revocation + lockout |
| A08 Software Integrity | Deterministic builds; release binary SHA-256 checksums |
| A09 Logging & Monitoring | Structured tracing + append-only audit log |
| A10 SSRF | No user-controlled URL fetching in core paths |
Secrets Management
Never commit secrets to git or embed them in Docker images or Kubernetes ConfigMaps. Use a dedicated secrets manager:
- AWS Secrets Manager / Parameter Store
- HashiCorp Vault
- 1Password Secrets Automation
- Doppler
Kubernetes: inject secrets as environment variables from kind: Secret objects, not ConfigMaps.
TLS
Terminate TLS at the edge (nginx, Caddy, cloud load balancer). Vaultaris listens on plain HTTP. Never expose port 8080 to the internet directly. Minimum TLS 1.2; prefer TLS 1.3.
Caddy example (auto Let's Encrypt):
auth.example.com {
reverse_proxy localhost:8080
}
License Enforcement & Freeze Security
The freeze system ensures that Vaultaris cannot be used to exceed purchased resource limits. Freeze operations are audited, and the FreezeReason enum distinguishes automatic (license-driven) freezes from manual admin freezes. Manual freezes are never automatically reversed on upgrade — they require explicit admin action.
Responsible Disclosure
GitHub Security Advisories or security@vaultaris.net. 90-day coordinated disclosure policy.