46 lines
3.4 KiB
Markdown
46 lines
3.4 KiB
Markdown
# Security review
|
|
|
|
Review date: 2026-08-02
|
|
|
|
## Scope
|
|
|
|
Next.js portal and APIs, Discord bot, PostgreSQL persistence, Keycloak admin authentication, and the Velocity admission plugin.
|
|
|
|
## Automated checks
|
|
|
|
- Semgrep `auto`: 0 findings
|
|
- `npm audit`: 0 known vulnerabilities after dependency overrides
|
|
- TypeScript, ESLint, unit tests, Next.js production build: passing
|
|
- Velocity Java tests and shaded plugin build: passing
|
|
|
|
## Implemented controls
|
|
|
|
- Discord login and session tokens use cryptographically secure randomness and are stored only as SHA-256 hashes.
|
|
- Login links expire after ten minutes, are single use, and are rate limited per Discord user with a PostgreSQL advisory lock.
|
|
- Session cookies are `httpOnly`, `sameSite=lax`, path-scoped, and secure in production.
|
|
- Admin access uses Keycloak OIDC and a required role.
|
|
- User mutations verify ownership server-side.
|
|
- Mojang lookup is server-side and targets a fixed host, avoiding client-forged validation and SSRF.
|
|
- Velocity credentials are high-entropy bearer tokens stored only as hashes.
|
|
- Velocity admission and confirmed-connection requests have a 45-second clock window and database-unique request IDs for cross-instance replay prevention.
|
|
- Velocity and its API fail closed.
|
|
- Registered players require an enabled effective group; explicit assignments replace rather than combine with the protected, disabled-by-default `everyone` fallback.
|
|
- Group and membership mutations re-check the Keycloak administrator role server-side; destructive group deletion and its audit event commit atomically.
|
|
- Event filters accept only event types already present in the ledger, and event detail routes remain role-protected.
|
|
- The administrator-only map defaults to bundled Natural Earth boundaries. OpenStreetMap tile requests begin only after an explicit operator opt-in; marker coordinates are not transmitted as data, but the requested tiles disclose the viewed geographic extent along with the administrator's IP and portal origin.
|
|
- ORM-parameterized queries are used throughout.
|
|
- CSP, clickjacking, MIME-sniffing, referrer, and browser-permission headers are configured.
|
|
- Forwarded IP headers are ignored unless `TRUST_PROXY=true` is explicitly configured.
|
|
- Private and reserved addresses are not sent to ProxyCheck.io; lookup results are cached to reduce disclosure and API usage.
|
|
- Portal and game login events include approximate network location and VPN/proxy classification when available.
|
|
- Structured Pino logging redacts credential fields, and secrets are excluded from logs and repository configuration.
|
|
|
|
## Outstanding production requirements
|
|
|
|
- Monitor ProxyCheck.io usage, detection quality, and false positives. User account additions fail closed when classification is unavailable; hosting-provider blocking remains optional.
|
|
- Define and automate retention for exact IP addresses, cached provider responses, approximate location, and audit events.
|
|
- Add monitoring and alerts for repeated login denials, plugin authentication failures, and Discord API failures.
|
|
- Use HTTPS for the public application and Velocity API URL. Protect the Velocity configuration file because it contains the one-time-displayed API token.
|
|
- Restrict database credentials so normal application roles cannot update or delete historical event rows outside approved application paths.
|
|
- Validate migrations in staging before production. Local migration application was unavailable during development because the Docker daemon was not running.
|