security

package
v0.2.93 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 27, 2026 License: Apache-2.0 Imports: 19 Imported by: 1

Documentation

Overview

Package security provides security features for OAuth including encryption, rate limiting, audit logging, and secure header management.

Package security provides security-related functionality for the OAuth server, including rate limiting, encryption, IP validation, and audit logging.

Rate Limiting

The RateLimiter provides per-identifier rate limiting using a token bucket algorithm with automatic memory management through LRU (Least Recently Used) eviction.

## Memory Management

To prevent unbounded memory growth under distributed attacks, the rate limiter implements a configurable maximum entries limit. When this limit is reached, the least recently used entries are automatically evicted.

Default configuration:

  • MaxEntries: 10,000 unique identifiers
  • CleanupInterval: 5 minutes
  • IdleTimeout: 30 minutes

## Example Usage

// Create rate limiter with default settings (10,000 max entries)
limiter := security.NewRateLimiter(10, 20, logger)
defer limiter.Stop()

// Create rate limiter with custom max entries
limiter := security.NewRateLimiterWithConfig(10, 20, 5000, logger)
defer limiter.Stop()

// Check if request is allowed
if !limiter.Allow(clientIP) {
    // Rate limit exceeded
    return http.StatusTooManyRequests
}

// Monitor memory usage
stats := limiter.GetStats()
if stats.MemoryPressure > 80.0 {
    logger.Warn("Rate limiter memory pressure high",
        "pressure", stats.MemoryPressure,
        "current_entries", stats.CurrentEntries,
        "max_entries", stats.MaxEntries)
}

## Monitoring and Alerting

The GetStats() method provides metrics for monitoring:

  • CurrentEntries: Number of tracked identifiers
  • MaxEntries: Configured limit (0 = unlimited)
  • TotalEvictions: Number of LRU evictions performed
  • TotalCleanups: Number of cleanup operations completed
  • MemoryPressure: Percentage of max capacity used (0-100)

Set up alerts when:

  • MemoryPressure consistently > 80%: Consider increasing MaxEntries
  • TotalEvictions increasing rapidly: Possible distributed attack
  • CurrentEntries near MaxEntries: May need capacity adjustment

## Security Considerations

The rate limiter is designed to prevent:

  • Memory exhaustion from distributed attacks
  • Resource exhaustion through controlled limits
  • Timing attacks (constant-time operations where possible)

The LRU eviction strategy ensures that legitimate users (who make repeated requests) are less likely to be evicted, while one-time attack IPs are evicted first.

Index

Constants

View Source
const (
	// DefaultMaxRegistrationsPerHour is the default limit for client registrations per IP per hour
	DefaultMaxRegistrationsPerHour = 10

	// DefaultRegistrationWindow is the default time window for rate limiting (1 hour)
	DefaultRegistrationWindow = time.Hour

	// DefaultRegistrationCleanupInterval is how often the cleanup goroutine runs
	DefaultRegistrationCleanupInterval = 15 * time.Minute

	// DefaultMaxRegistrationEntries is the maximum number of IPs to track
	DefaultMaxRegistrationEntries = 10000
)
View Source
const (

	// EventTokenIssued is logged when a new access token is issued to a client
	EventTokenIssued = "token_issued"

	// EventTokenRefreshed is logged when an access token is refreshed using a refresh token
	EventTokenRefreshed = "token_refreshed"

	// EventTokenProactivelyRefreshed is logged when a token is proactively refreshed before expiry
	EventTokenProactivelyRefreshed = "token_proactively_refreshed"

	// EventTokenRevoked is logged when a token is revoked by the user or client
	EventTokenRevoked = "token_revoked"

	// EventAllTokensRevoked is logged when all tokens for a user are revoked
	EventAllTokensRevoked = "all_tokens_revoked" //nolint:gosec // G101: False positive - this is an event type name, not a credential

	// EventAuthorizationFlowStarted is logged when an authorization flow is initiated
	EventAuthorizationFlowStarted = "authorization_flow_started"

	// EventAuthorizationCodeIssued is logged when an authorization code is issued
	EventAuthorizationCodeIssued = "authorization_code_issued"

	// EventAuthorizationCodeReuseDetected is logged when an authorization code is reused (attack)
	EventAuthorizationCodeReuseDetected = "authorization_code_reuse_detected"

	// EventClientRegistered is logged when a new OAuth client is registered
	EventClientRegistered = "client_registered"

	// EventClientRegisteredViaTrustedScheme is logged when a client is registered without a token
	// because it uses only trusted custom URI schemes (e.g., cursor://, vscode://).
	// This enables compatibility with MCP clients that don't support registration tokens.
	EventClientRegisteredViaTrustedScheme = "client_registered_via_trusted_scheme"

	// EventClientRegistrationRejected is logged when client registration is rejected for security reasons
	EventClientRegistrationRejected = "client_registration_rejected"

	// EventClientRegistrationRateLimitExceeded is logged when client registration rate limit is exceeded
	EventClientRegistrationRateLimitExceeded = "client_registration_rate_limit_exceeded"

	// EventAuthFailure is logged when authentication fails (wrong credentials, etc.)
	EventAuthFailure = "auth_failure"

	// EventRateLimitExceeded is logged when a rate limit is exceeded
	EventRateLimitExceeded = "rate_limit_exceeded"

	// EventInvalidPKCE is logged when PKCE validation fails
	EventInvalidPKCE = "invalid_pkce"

	// EventPKCEValidationFailed is logged when PKCE code_verifier validation fails
	EventPKCEValidationFailed = "pkce_validation_failed"

	// EventPKCERequiredForPublicClient is logged when a public client attempts flow without PKCE
	EventPKCERequiredForPublicClient = "pkce_required_for_public_client"

	// EventInsecurePublicClientWithoutPKCE is logged when insecure flow is attempted
	EventInsecurePublicClientWithoutPKCE = "insecure_public_client_without_pkce"

	// EventTokenReuseDetected is logged when refresh token reuse is detected (theft)
	EventTokenReuseDetected = "token_reuse_detected" //nolint:gosec // G101: False positive - this is an event type name, not a credential

	// EventRefreshTokenReuseDetected is logged when a refresh token is reused in the same family
	EventRefreshTokenReuseDetected = "refresh_token_reuse_detected"

	// EventRefreshTokenMissingClientBinding is logged when a refresh token lacks client binding
	// This may occur for legacy tokens issued before OAuth 2.1 client binding was implemented
	EventRefreshTokenMissingClientBinding = "refresh_token_missing_client_binding"

	// EventRefreshTokenClientBindingMismatch is logged when the requesting client doesn't match
	// the client that was originally issued the refresh token (OAuth 2.1 Section 6 violation)
	// This is a critical security event indicating possible cross-client token theft
	EventRefreshTokenClientBindingMismatch = "refresh_token_client_binding_mismatch"

	// EventRefreshTokenFamilyRevoked is logged when an entire refresh token family is revoked
	// during explicit token revocation (all tokens sharing the same family ID become invalid)
	EventRefreshTokenFamilyRevoked = "refresh_token_family_revoked"

	// EventRevokedTokenFamilyReuseAttempt is logged when a revoked token family is accessed
	EventRevokedTokenFamilyReuseAttempt = "revoked_token_family_reuse_attempt"

	// EventSuspiciousActivity is logged for general suspicious behavior
	EventSuspiciousActivity = "suspicious_activity"

	// EventInvalidRedirect is logged when an invalid redirect URI is used
	EventInvalidRedirect = "invalid_redirect"

	// EventScopeEscalationAttempt is logged when a client tries to escalate scopes
	EventScopeEscalationAttempt = "scope_escalation_attempt"

	// EventScopeDefaultsApplied is logged when provider default scopes are used (forensics/compliance)
	EventScopeDefaultsApplied = "scope_defaults_applied"

	// EventResourceMismatch is logged when resource parameter doesn't match (RFC 8707)
	EventResourceMismatch = "resource_mismatch"

	// EventCrossClientTokenAccepted is logged when a token is accepted via TrustedAudiences.
	// This occurs in SSO scenarios where tokens issued to a trusted upstream (e.g., muster)
	// are accepted by a downstream MCP server. This event is logged for security monitoring
	// and forensics to track cross-client token usage patterns.
	EventCrossClientTokenAccepted = "cross_client_token_accepted"

	// EventForwardedIDTokenAccepted is logged when a forwarded ID token (JWT) is validated
	// and accepted via JWKS signature verification. This is part of SSO token forwarding
	// where an upstream MCP server's ID token is passed as a Bearer token to downstream services.
	EventForwardedIDTokenAccepted = "forwarded_id_token_accepted"

	// EventInvalidProviderCallback is logged when provider callback validation fails
	EventInvalidProviderCallback = "invalid_provider_callback"

	// EventProviderStateMismatch is logged when provider state parameter doesn't match
	EventProviderStateMismatch = "provider_state_mismatch"

	// EventProviderCodeExchangeFailed is logged when code exchange with provider fails (PKCE, etc.)
	EventProviderCodeExchangeFailed = "provider_code_exchange_failed"

	// EventProviderRevocationThresholdExceeded is logged when provider revocation partial failure occurs
	EventProviderRevocationThresholdExceeded = "provider_revocation_threshold_exceeded"

	// EventProviderRevocationCompleteFailure is logged when all provider revocation attempts fail
	EventProviderRevocationCompleteFailure = "provider_revocation_complete_failure"

	// EventTokenRevocationNotSupported is logged when provider doesn't support token revocation
	EventTokenRevocationNotSupported = "token_revocation_not_supported"

	// EventProactiveRefreshFailed is logged when proactive token refresh fails
	EventProactiveRefreshFailed = "proactive_refresh_failed"
)

Event type constants for security audit logging. These constants ensure consistency across the codebase and prevent typos when logging security-relevant events.

View Source
const (
	// DefaultMaxEntries is the default maximum number of tracked identifiers
	DefaultMaxEntries = 10000

	// DefaultCleanupInterval is how often the cleanup goroutine runs
	DefaultCleanupInterval = 5 * time.Minute

	// DefaultIdleTimeout is how long an entry can be idle before cleanup
	DefaultIdleTimeout = 30 * time.Minute
)
View Source
const (
	// DefaultClockSkewGracePeriod is the default grace period for token expiration checks
	// This prevents false expiration errors due to time synchronization issues
	// between different systems (client, server, provider).
	//
	// Security Rationale:
	//   - Prevents false expiration errors due to minor time differences
	//   - Balances security (minimize token lifetime extension) with usability
	//   - 5 seconds is a conservative value that handles typical NTP drift
	//
	// Trade-offs:
	//   - Allows tokens to be used up to 5 seconds beyond their true expiration
	//   - This is acceptable for most use cases and improves reliability
	//   - For high-security scenarios, this can be reduced or disabled
	DefaultClockSkewGracePeriod = 5 * time.Second
)
View Source
const InterstitialScriptHash = "sha256-BSPDdcxaKPs2IRkTMWvH7KxMRr/MuFv1HaDJlxd1UTI="

InterstitialScriptHash is the SHA-256 hash of the static inline script used in the success interstitial page. This hash is computed from the minified script content and allows the script to execute under a strict Content-Security-Policy.

The script reads the redirect URL from the button's href attribute (which is set by the template), so the script content is static and the hash is stable.

To regenerate this hash if the script changes:

echo -n '<script content>' | openssl dgst -sha256 -binary | base64
View Source
const RequestIDHeader = "X-Request-ID"

RequestIDHeader is the HTTP header for request IDs

Variables

This section is empty.

Functions

func GenerateKey

func GenerateKey() ([]byte, error)

GenerateKey generates a new 32-byte encryption key for AES-256

func GenerateRequestID added in v0.1.24

func GenerateRequestID() string

GenerateRequestID generates a cryptographically secure random request ID. It uses crypto/rand to generate 16 bytes (128 bits) of entropy and encodes them as a 22-character base64url string without padding.

Request IDs are used for audit trails, security correlation, and debugging. The function panics if the system's random number generator fails, which indicates a critical system-level security failure.

func GetClientIP

func GetClientIP(r *http.Request, trustProxy bool, trustedProxyCount int) string

GetClientIP extracts the real client IP address from the request Supports X-Forwarded-For and X-Real-IP headers when behind a proxy

SECURITY CONSIDERATIONS: - Only enable trustProxy when behind a trusted reverse proxy (nginx, haproxy, etc.) - X-Forwarded-For format: "client, proxy1, proxy2, ..." - trustedProxyCount specifies how many proxies to trust from the right - This prevents X-Forwarded-For spoofing in multi-proxy setups

func GetRequestID added in v0.1.24

func GetRequestID(ctx context.Context) string

GetRequestID retrieves the request ID from the context

func IsTokenExpired

func IsTokenExpired(expiresAt time.Time) bool

IsTokenExpired checks if a token is expired with default clock skew grace period

func IsTokenExpiredWithGracePeriod added in v0.1.1

func IsTokenExpiredWithGracePeriod(expiresAt time.Time, gracePeriod time.Duration) bool

IsTokenExpiredWithGracePeriod checks if a token is expired with custom clock skew grace period

func IsTokenExpiringSoon

func IsTokenExpiringSoon(expiresAt time.Time, threshold time.Duration) bool

IsTokenExpiringSoon checks if a token will expire within the given threshold

func KeyFromBase64

func KeyFromBase64(s string) ([]byte, error)

KeyFromBase64 decodes a base64-encoded encryption key

func KeyToBase64

func KeyToBase64(key []byte) string

KeyToBase64 encodes an encryption key to base64

func RequestIDMiddleware added in v0.1.24

func RequestIDMiddleware(next http.Handler) http.Handler

RequestIDMiddleware is HTTP middleware that generates and propagates request IDs.

Security behavior:

  • Preserves valid request IDs from upstream proxies for audit trail continuity
  • Validates upstream IDs to prevent header injection attacks (CRLF, DoS)
  • Generates new cryptographically secure ID if upstream ID is missing or invalid
  • Adds request ID to response headers for end-to-end correlation

func SetInterstitialSecurityHeaders added in v0.1.48

func SetInterstitialSecurityHeaders(w http.ResponseWriter, serverURL string)

SetInterstitialSecurityHeaders sets security headers for the OAuth success interstitial page. This is similar to SetSecurityHeaders but includes a hash-based CSP exception for the inline redirect script.

Security considerations:

  • Uses hash-based script allowlisting (CSP Level 2) instead of 'unsafe-inline'
  • The script hash is computed from a static script that reads the redirect URL from the DOM, ensuring the hash remains stable across different redirect URLs
  • style-src 'unsafe-inline' is required because the CSS contains dynamic template variables (colors, gradients, custom CSS) that change per-request, making hash-based CSP impossible for styles. This is acceptable because CSS cannot execute arbitrary code - the risk is significantly lower than for scripts.
  • img-src restricts images to HTTPS sources only (plus data: for inline SVG icons)

func SetSecurityHeaders

func SetSecurityHeaders(w http.ResponseWriter, serverURL string)

SetSecurityHeaders sets comprehensive security headers on HTTP responses These headers protect against various web vulnerabilities

func WithRequestID added in v0.1.24

func WithRequestID(ctx context.Context, requestID string) context.Context

WithRequestID adds a request ID to the context

Types

type Auditor

type Auditor struct {
	// contains filtered or unexported fields
}

Auditor handles security event logging with PII protection.

func NewAuditor

func NewAuditor(logger *slog.Logger, enabled bool) *Auditor

NewAuditor creates a new security auditor

func (*Auditor) LogAuthFailure

func (a *Auditor) LogAuthFailure(userID, clientID, ipAddress, reason string)

LogAuthFailure logs an authentication failure

func (*Auditor) LogClientRegistered

func (a *Auditor) LogClientRegistered(clientID, clientType, ipAddress string)

LogClientRegistered logs when a new client is registered

func (*Auditor) LogClientRegistrationRateLimitExceeded added in v0.1.20

func (a *Auditor) LogClientRegistrationRateLimitExceeded(ipAddress string)

LogClientRegistrationRateLimitExceeded logs when client registration rate limit is exceeded

func (*Auditor) LogEvent

func (a *Auditor) LogEvent(event Event)

LogEvent logs a security event with hashed PII

func (*Auditor) LogInvalidPKCE added in v0.1.3

func (a *Auditor) LogInvalidPKCE(clientID, ipAddress, reason string)

LogInvalidPKCE logs when PKCE validation fails

func (*Auditor) LogInvalidRedirect added in v0.1.3

func (a *Auditor) LogInvalidRedirect(clientID, ipAddress, uri, reason string)

LogInvalidRedirect logs invalid redirect URI attempts

func (*Auditor) LogRateLimitExceeded

func (a *Auditor) LogRateLimitExceeded(ipAddress, userID string)

LogRateLimitExceeded logs a rate limit violation

func (*Auditor) LogSuspiciousActivity added in v0.1.3

func (a *Auditor) LogSuspiciousActivity(userID, clientID, ipAddress, description string)

LogSuspiciousActivity logs suspicious activity

func (*Auditor) LogTokenIssued

func (a *Auditor) LogTokenIssued(userID, clientID, ipAddress, scope string)

LogTokenIssued logs when a token is issued

func (*Auditor) LogTokenRefreshed

func (a *Auditor) LogTokenRefreshed(userID, clientID, ipAddress string, rotated bool)

LogTokenRefreshed logs when a token is refreshed

func (*Auditor) LogTokenReuse added in v0.1.3

func (a *Auditor) LogTokenReuse(userID, ipAddress string)

LogTokenReuse logs when refresh token reuse is detected (security event)

func (*Auditor) LogTokenRevoked

func (a *Auditor) LogTokenRevoked(userID, clientID, ipAddress, tokenType string)

LogTokenRevoked logs when a token is revoked

type ClientRegistrationRateLimiter added in v0.1.20

type ClientRegistrationRateLimiter struct {
	// contains filtered or unexported fields
}

ClientRegistrationRateLimiter provides time-windowed rate limiting for client registrations to prevent resource exhaustion through repeated registration/deletion cycles.

Lifecycle Management:

The rate limiter starts a background cleanup goroutine when created. Always call Stop() when shutting down to prevent goroutine leaks:

rl := security.NewClientRegistrationRateLimiter(logger)
defer rl.Stop() // Critical: prevents goroutine leak

The Stop() method is safe to call multiple times and can be called concurrently.

func NewClientRegistrationRateLimiter added in v0.1.20

func NewClientRegistrationRateLimiter(logger *slog.Logger) *ClientRegistrationRateLimiter

NewClientRegistrationRateLimiter creates a new client registration rate limiter with default settings.

The rate limiter starts a background cleanup goroutine. Always call Stop() when done:

rl := security.NewClientRegistrationRateLimiter(logger)
defer rl.Stop() // Important: cleanup background goroutine

func NewClientRegistrationRateLimiterWithConfig added in v0.1.20

func NewClientRegistrationRateLimiterWithConfig(maxPerWindow int, window time.Duration, maxEntries int, logger *slog.Logger) *ClientRegistrationRateLimiter

NewClientRegistrationRateLimiterWithConfig creates a new client registration rate limiter with custom configuration.

The rate limiter starts a background cleanup goroutine. Always call Stop() when done:

rl := security.NewClientRegistrationRateLimiterWithConfig(10, time.Hour, 10000, logger)
defer rl.Stop() // Important: cleanup background goroutine

func (*ClientRegistrationRateLimiter) Allow added in v0.1.20

Allow checks if a client registration from the given IP is allowed Returns true if allowed, false if rate limit exceeded

func (*ClientRegistrationRateLimiter) Cleanup added in v0.1.20

func (rl *ClientRegistrationRateLimiter) Cleanup()

Cleanup removes entries that haven't been accessed recently Entries are considered inactive if their last access is older than 2x the window

func (*ClientRegistrationRateLimiter) GetStats added in v0.1.20

GetStats returns current rate limiter statistics for monitoring and alerting

func (*ClientRegistrationRateLimiter) Stop added in v0.1.20

func (rl *ClientRegistrationRateLimiter) Stop()

Stop gracefully stops the cleanup goroutine and releases resources.

This method MUST be called when shutting down to prevent goroutine leaks. It is safe to call multiple times and can be called concurrently from multiple goroutines (uses sync.Once internally).

Best practice: Use defer immediately after creating the rate limiter:

rl := security.NewClientRegistrationRateLimiter(logger)
defer rl.Stop()

type Encryptor

type Encryptor struct {
	// contains filtered or unexported fields
}

Encryptor handles token encryption at rest using AES-256-GCM.

func NewEncryptor

func NewEncryptor(key []byte) (*Encryptor, error)

NewEncryptor creates a new encryptor. If key is nil or empty, encryption is disabled. The key must be exactly 32 bytes for AES-256.

func (*Encryptor) Decrypt

func (e *Encryptor) Decrypt(encoded string) (string, error)

Decrypt decrypts base64-encoded ciphertext using AES-256-GCM.

func (*Encryptor) Encrypt

func (e *Encryptor) Encrypt(plaintext string) (string, error)

Encrypt encrypts plaintext using AES-256-GCM. Returns base64-encoded ciphertext.

func (*Encryptor) IsEnabled

func (e *Encryptor) IsEnabled() bool

IsEnabled returns true if encryption is enabled

type Event

type Event struct {
	Type      string
	UserID    string
	ClientID  string
	IPAddress string
	UserAgent string // User-Agent header for detecting automated attacks
	RequestID string // Unique request ID for correlating log entries
	Details   map[string]any
	Timestamp time.Time
}

Event represents a security audit event

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter provides per-identifier rate limiting using token bucket algorithm with LRU eviction to prevent unbounded memory growth.

func NewRateLimiter

func NewRateLimiter(requestsPerSecond, burst int, logger *slog.Logger) *RateLimiter

NewRateLimiter creates a new rate limiter with automatic cleanup and LRU eviction. Default max entries is 10,000. Use NewRateLimiterWithConfig for custom max entries.

func NewRateLimiterWithConfig added in v0.1.19

func NewRateLimiterWithConfig(requestsPerSecond, burst, maxEntries int, logger *slog.Logger) *RateLimiter

NewRateLimiterWithConfig creates a new rate limiter with custom max entries configuration. maxEntries controls the maximum number of unique identifiers tracked simultaneously. When limit is reached, least recently used entries are evicted. Set maxEntries to 0 for unlimited (not recommended for production).

func NewRateLimiterWithFullConfig added in v0.1.24

func NewRateLimiterWithFullConfig(requestsPerSecond, burst, maxEntries int, cleanupInterval time.Duration, logger *slog.Logger) *RateLimiter

NewRateLimiterWithFullConfig creates a new rate limiter with custom configuration including cleanup interval. maxEntries controls the maximum number of unique identifiers tracked simultaneously. cleanupInterval controls how often idle entries are cleaned up. When maxEntries limit is reached, least recently used entries are evicted. Set maxEntries to 0 for unlimited (not recommended for production). Set cleanupInterval to 0 to use default (5 minutes).

func (*RateLimiter) Allow

func (rl *RateLimiter) Allow(identifier string) bool

Allow checks if a request from the given identifier is allowed. Implements LRU eviction when max entries limit is reached.

func (*RateLimiter) Cleanup

func (rl *RateLimiter) Cleanup(maxIdleTime time.Duration)

Cleanup removes inactive limiters that haven't been accessed for the given duration. Also removes corresponding entries from the LRU list.

func (*RateLimiter) GetStats added in v0.1.19

func (rl *RateLimiter) GetStats() Stats

GetStats returns current rate limiter statistics for monitoring and alerting. This is useful for detecting memory pressure and tuning maxEntries configuration.

func (*RateLimiter) Stop added in v0.1.1

func (rl *RateLimiter) Stop()

Stop gracefully stops the cleanup goroutine. Safe to call multiple times concurrently. Uses sync.Once to guarantee exactly-once execution and prevent race conditions.

type RegistrationStats added in v0.1.20

type RegistrationStats struct {
	CurrentEntries int     // Current number of tracked IPs
	MaxEntries     int     // Maximum allowed entries (0 = unlimited)
	TotalBlocked   int64   // Total registrations blocked
	TotalAllowed   int64   // Total registrations allowed
	TotalEvictions int64   // Total number of LRU evictions
	TotalCleanups  int64   // Total number of cleanup operations
	MaxPerWindow   int     // Maximum registrations per window
	Window         string  // Time window duration
	MemoryPressure float64 // Percentage of max capacity used (0-100)
}

RegistrationStats holds client registration rate limiter statistics for monitoring

type Stats added in v0.1.19

type Stats struct {
	CurrentEntries int     // Current number of tracked identifiers
	MaxEntries     int     // Maximum allowed entries (0 = unlimited)
	TotalEvictions int64   // Total number of LRU evictions
	TotalCleanups  int64   // Total number of cleanup operations
	MemoryPressure float64 // Percentage of max capacity used (0-100)
}

Stats holds rate limiter statistics for monitoring

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL