Files
OwnCord/Server/api/constants.go
T
J3vbandClaude Opus 4.8 7b178ff30b fix(security): harden server against verified code-review findings
Applies fixes for 20 adversarially-verified findings from a whole-codebase
security review (server side). All Go build-tag variants build, `go vet` is
clean, and the suite passes (the sole failing test, ws TestEmitEvents, is a
pre-existing nil-harness failure unrelated to these changes).

High severity:
- auth: close TOCTOU in TOTP verify rate-limit by recording each attempt
  atomically up-front (was Check-then-Allow), restoring the per-user
  brute-force cap.
- plugin: enforce the CPU/time budget on every WASM guest call via a
  WithTimeout context (WithCloseOnContextDone interrupts runaways); the
  configured budget was previously parsed but never applied.
- api/waf: inspect request bodies for chunked (ContentLength==-1) requests
  so the SQLi/XSS/RCE body rules can no longer be bypassed.
- ws: rate-limit voice_join/voice_leave and voice_e2ee announce/offer, which
  fan out to every participant and could force mass disconnects.

Medium severity:
- api: run bcrypt on the unknown-user login path (no || short-circuit) to
  remove the timing-based username-enumeration oracle.
- ws: verify LiveKit webhooks via the SDK receiver so the signature is bound
  to the body hash (kills forgery/replay).
- authz: require READ_MESSAGES for reactions and for plugin-command
  broadcasts; route the latter through RequireChannelAccess.
- api: cache the client-update signature fetch and rate-limit the endpoint.
- service: propagate DeleteOtherSessions failure from ChangePassword instead
  of silently reporting success.
- api: trust the rightmost non-proxy X-Forwarded-For entry, not the
  client-controllable leftmost one.
- plugin: route auto-registered commands through the conflict-checked
  RegisterCommand; pin the DNS-validated IP for host_http dials
  (DNS-rebinding TOCTOU).
- api: mark access-controlled downloads private/no-cache + Vary: Origin.

Low severity:
- auth: fail closed when a fully-shaped TOTP ciphertext fails GCM auth
  (was returning the ciphertext as plaintext).
- api: apply the livekit-proxy path allowlist to WebSocket upgrades too.
- service: verify attachment ownership before linking (IDOR).
- admin: bound the bootstrap setup invite (5 uses / 24h); re-verify the
  update binary hash immediately before rename+spawn (TOCTOU).
- service: require BanMembers + role hierarchy for moderation ban/unban.

chore: stop tracking the stray Server/owncord-server.exe build artifact.

Test infra: add uploader_id to the hand-rolled ws test attachment schemas and
make MemStore.GetAttachmentByID a no-op lookup, matching production/DB behavior.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 21:08:54 +02:00

149 lines
5.7 KiB
Go

package api
import (
"time"
"github.com/owncord/server/config"
)
// ─── Rate limits ────────────────────────────────────────────────────────────
//
// Each constant defines either a request cap or a sliding-window duration used
// by the per-endpoint rate limiters.
const (
// registerRateLimitPerMinute is the maximum registration attempts per IP per minute.
registerRateLimitPerMinute = 3
// loginRateLimitPerMinute is the maximum login attempts per IP per minute.
loginRateLimitPerMinute = 5
// verifyTOTPRateLimitPerMinute is the maximum TOTP verification attempts per IP per minute.
verifyTOTPRateLimitPerMinute = 10
// sensitiveEndpointRateLimitPerMinute is the rate limit applied to destructive
// or sensitive endpoints (account deletion, TOTP enable/confirm/disable).
sensitiveEndpointRateLimitPerMinute = 5
// searchRateLimitPerMinute is the maximum full-text search requests per IP per minute.
searchRateLimitPerMinute = 30
// livekitProxyRateLimitPerMinute is the maximum LiveKit proxy requests per IP per minute.
livekitProxyRateLimitPerMinute = 30
// clientUpdateRateLimitPerMinute is the maximum client-update checks per IP per minute.
clientUpdateRateLimitPerMinute = 30
// loginFailureThreshold is the number of failed login attempts (within
// loginFailureWindow) before the IP is locked out.
loginFailureThreshold = 9
// loginFailureWindow is the sliding window for counting login failures.
loginFailureWindow = 15 * time.Minute
// loginLockoutDuration is how long an IP is locked out after exceeding
// loginFailureThreshold.
loginLockoutDuration = 15 * time.Minute
// deleteAccountFailureThreshold is the number of wrong-password attempts
// before the per-user lockout kicks in.
deleteAccountFailureThreshold = 3
// deleteAccountFailureWindow is the sliding window for counting
// delete-account password failures.
deleteAccountFailureWindow = 15 * time.Minute
// deleteAccountLockoutDuration is how long the account-deletion endpoint
// is locked after exceeding deleteAccountFailureThreshold.
deleteAccountLockoutDuration = 15 * time.Minute
// totpFailureRateLimit is the maximum TOTP verification failures per user
// within totpFailureWindow before the user is rate-limited.
totpFailureRateLimit = 10
// totpFailureWindow is the sliding window for counting per-user TOTP failures.
totpFailureWindow = 15 * time.Minute
// partialAuthMaxFailures is the number of failed TOTP attempts on a single
// partial-auth challenge before it is revoked.
partialAuthMaxFailures = 5
// profilePasswordRateLimitPerMinute is the maximum password change attempts
// per IP per minute.
profilePasswordRateLimitPerMinute = 5
// profileUpdateRateLimitPerMinute is the maximum profile update attempts
// per user per minute.
profileUpdateRateLimitPerMinute = 10
// loginUserFailureThreshold is the number of failed login attempts for a
// specific username (regardless of source IP) before the account is locked.
loginUserFailureThreshold = 9
// loginUserFailureWindow is the sliding window for per-username login failures.
loginUserFailureWindow = 15 * time.Minute
// loginUserLockoutDuration is how long a username is locked after exceeding
// loginUserFailureThreshold.
loginUserLockoutDuration = 15 * time.Minute
// pwConfirmFailureThreshold is the number of wrong-password attempts on
// password-confirmation endpoints before per-user lockout kicks in.
pwConfirmFailureThreshold = 3
// pwConfirmFailureWindow is the sliding window for per-user password
// confirmation failures.
pwConfirmFailureWindow = 15 * time.Minute
// pwConfirmLockoutDuration is how long password-confirmation endpoints are
// locked after exceeding pwConfirmFailureThreshold.
pwConfirmLockoutDuration = 15 * time.Minute
// uploadRateLimitPerMinute is the maximum file uploads per user per minute.
uploadRateLimitPerMinute = 10
)
// ─── Timeouts & TTLs ────────────────────────────────────────────────────────
const (
// partialAuthStoreTTL is the lifetime of a partial-auth (2FA) challenge token.
partialAuthStoreTTL = 10 * time.Minute
// pendingTOTPStoreTTL is the lifetime of a pending TOTP enrollment secret.
pendingTOTPStoreTTL = 10 * time.Minute
// rateLimiterCleanupInterval is how often stale rate-limiter entries are reaped.
rateLimiterCleanupInterval = 5 * time.Minute
// rateLimiterCleanupMaxWindow is the maximum window considered when pruning
// stale rate-limiter entries.
rateLimiterCleanupMaxWindow = 15 * time.Minute
// hstsMaxAgeSeconds is the max-age value for the Strict-Transport-Security header.
hstsMaxAgeSeconds = 31536000
// fileCacheMaxAgeSeconds is the max-age value for the Cache-Control header on served files.
fileCacheMaxAgeSeconds = 31536000
)
// ─── Size limits ────────────────────────────────────────────────────────────
const (
// defaultMaxBodySize is the default request body size limit (1 MiB).
defaultMaxBodySize = config.MaxMessageBytes
// uploadMaxBodySize is the request body size limit for file uploads (100 MiB).
uploadMaxBodySize = 100 << 20
// multipartMemoryLimit is the in-memory limit for multipart form parsing;
// data beyond this is spilled to disk.
multipartMemoryLimit = 10 << 20
// maxUploadFilenameLength is the maximum length of an upload filename
// (filesystem-safe limit).
maxUploadFilenameLength = 255
// maxAvatarURLLen is the maximum length of a user avatar URL.
maxAvatarURLLen = 512
)