- Add setup-zitadel.sh: idempotent script that creates PVM project and OIDC app via Zitadel Management API using machine user PAT - Add machine user + PAT auto-generation to docker-compose via FIRSTINSTANCE env vars with bind-mounted machinekey directory - Add SMTP configuration for email sending (verification, password reset) - Fix JWT algorithm confusion attack: restrict to RS256/384/512 only - Add docs/TODO_SECURITY.md tracking review findings - Update .env.example files with correct local dev URLs - Add docker/machinekey/ to .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
37 lines
2.1 KiB
Markdown
37 lines
2.1 KiB
Markdown
# Security & Technical Debt
|
|
|
|
Items identified during code review that should be addressed before production.
|
|
|
|
## High Priority
|
|
|
|
### Token refresh logic in SvelteKit (`apps/dashboard/src/auth.ts`)
|
|
The JWT callback stores `expiresAt` and `refreshToken` but never checks expiry or initiates a refresh. Auth.js does not auto-refresh tokens. Without this, users get silently logged out when their access token expires (typically 5-15 minutes for Zitadel).
|
|
|
|
**Fix:** Add expiry check in the `jwt` callback and use `refreshToken` to obtain a new access token when expired.
|
|
|
|
### JWKS cache thundering herd (`crates/pvm-auth/src/jwks.rs`)
|
|
When the cache expires, every concurrent request sees stale cache and calls `refresh()` simultaneously. The `RwLock` serializes writes but each request still makes an HTTP call before acquiring the lock.
|
|
|
|
**Fix:** Add a "refresh-in-progress" flag or use double-checked locking so only one request triggers the refresh while others wait.
|
|
|
|
## Medium Priority
|
|
|
|
### `trustHost: true` in auth.ts
|
|
Disables CSRF origin check in Auth.js. Required for local dev behind localhost, but must be removed or made conditional for production.
|
|
|
|
### `devMode: true` in OIDC app config (`docker/setup-zitadel.sh`)
|
|
Disables redirect URI validation in Zitadel. The setup script is dev-only, but if a similar script is used for production, this must be `false`.
|
|
|
|
### Custom login UI
|
|
Replace the default Zitadel login v1 UI with a fully custom login/signup flow built into the SvelteKit dashboard using Zitadel's Session API. Includes: login, signup, password reset, 2FA flows. Must match PVM visual design.
|
|
|
|
## Low Priority
|
|
|
|
### Shell script JSON parsing (`docker/setup-zitadel.sh`)
|
|
Uses `grep -o` and `cut` to extract JSON fields. Fragile if JSON format changes. Consider using `jq` with a fallback.
|
|
|
|
### PAT expiration
|
|
Machine user PAT expires 2030-01-01. Fine for dev, but production should use shorter-lived credentials.
|
|
|
|
### Running Zitadel as root (`docker-compose.dev.yml`)
|
|
`user: "0"` is required for `start-from-init` to write the PAT file. Dev-only concern — production deployment should use proper volume permissions.
|