mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Unbounded client-controlled values reached the 2000-entry admin log ring buffer and its SSE fan-out, letting an unauthenticated burst pin large amounts of heap. A boundRequestID middleware now drops an inbound X-Request-Id over 128 bytes or outside printable ASCII, so chi generates its own, and the logged request path is capped at 256 bytes. Both hunks are needed: a raw-socket probe showed a 1MB r.URL.Path reaches the same sink independently of the header. Verified by a panel of agents; an unpatched-tree reproduction fails 3 of the 4 added tests with the attacker bytes visible in the log record. UUID, 32-hex, W3C traceparent and chi's own generated id format all still pass unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
166 lines
6.6 KiB
Go
166 lines
6.6 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
|
|
|
|
// gifRateLimitPerMinute is the maximum GIF proxy requests per IP per minute.
|
|
// The picker debounces at 300ms, so a user typing continuously for a minute
|
|
// stays under this; it exists to bound abuse of the operator's Klipy quota.
|
|
gifRateLimitPerMinute = 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
|
|
)
|
|
|
|
// ─── 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
|
|
|
|
// maxRequestIDLen bounds a client-supplied X-Request-Id. chi's
|
|
// middleware.RequestID adopts that header verbatim, and the value then
|
|
// reaches every log record for the request (logctx, requestLogger,
|
|
// recoverer) and the echoed response header — while the admin ring buffer
|
|
// retains 2000 records, so an unbounded id becomes long-lived heap.
|
|
// 128 bytes fits every common correlation-id format (UUID, 32-hex,
|
|
// W3C traceparent, chi's own "host/prefix-000001").
|
|
maxRequestIDLen = 128
|
|
|
|
// maxLoggedPathLen bounds the request path attached to a log record. The
|
|
// URL is client-controlled and net/http accepts one up to MaxHeaderBytes
|
|
// (~1 MiB), so an unbounded path fills the ring buffer the same way an
|
|
// unbounded request id does. Well past the longest real route.
|
|
maxLoggedPathLen = 256
|
|
)
|