Files
BetterDesk/web-nodejs/middleware/rateLimiter.js
T
Knienartowicz f8fbb88e47 security: apply 12 audit fixes (H-03/H-04/M-03/M-04/M-05/M-06/M-07/L-01/L-02/L-04/I-02/I-04)
Node.js (no recompile required):

- H-04: RUSTDESK_API_DISABLE_TOTP now requires explicit _ACKNOWLEDGED flag

- M-03: drop Referer-based skip from apiLimiter; add dedicated widgetLimiter

- M-06: gate /api/system/info, /logs/recent, /database/stats, /docker/containers, /speed-test behind requirePermission('metrics.view')

- L-01: startup banner now warns when TRUST_PROXY is off in production / errors when TOTP bypass is set without acknowledgement

Scripts / Docker:

- M-04: betterdesk.sh migration tool invocation switched from eval(cmd-string) to bash array exec

- M-05: all admin / PostgreSQL password generators switched from openssl rand -base64+tr+head to openssl rand -hex 16 (full entropy)

- L-02: docker-compose.yml / single.yml / quick.yml services gain security_opt: no-new-privileges and cap_drop: ALL

Documentation:

- I-04: add SECURITY.md (supported versions, reporting channels, SLA, scope, hardening defaults)

Go server (requires rebuild on host: cd betterdesk-server && go build ./...):

- H-03: /metrics now gated by METRICS_IP_ALLOWLIST / METRICS_PUBLIC; per-username login + 2FA rate-limit added on top of per-IP

- M-07: enrollment (/api/devices/register*) and branding (GET /api/branding) endpoints rate-limited per IP

- I-02: bd-mgmt WebSocket gets SetReadLimit(16 MiB) to bound memory

- L-04: auth middleware skips noisy public probes and redacts /peers/{id} segments

This commit was made possible thanks to Insolve.
2026-05-26 13:58:16 +02:00

84 lines
2.2 KiB
JavaScript

/**
* BetterDesk Console - Rate Limiter Middleware
*/
const rateLimit = require('express-rate-limit');
const config = require('../config/config');
const defaultKeyGenerator = (req) => req.ip || req.headers['x-forwarded-for'] || 'unknown';
/**
* General API rate limiter.
*
* SECURITY (audit fix M-03, 2026-04-10): the previous Referer-based skip was
* removed because Referer is fully client-controlled. High-frequency widget /
* dashboard refresh endpoints now have their own higher-quota limiter
* (`widgetLimiter`) that the panel routes opt into explicitly.
*/
const apiLimiter = rateLimit({
windowMs: config.rateLimitWindowMs,
max: config.rateLimitMax,
standardHeaders: true,
legacyHeaders: false,
message: {
success: false,
error: 'Too many requests. Please try again later.'
},
keyGenerator: defaultKeyGenerator
});
/**
* Widget / dashboard refresh limiter. Higher quota (600 req/min by default)
* because the panel polls many widgets in parallel. Still authenticated —
* mount only on routes that require an active session.
*/
const widgetLimiter = rateLimit({
windowMs: 60 * 1000,
max: parseInt(process.env.WIDGET_RATE_LIMIT_MAX, 10) || 600,
standardHeaders: true,
legacyHeaders: false,
message: {
success: false,
error: 'Too many widget requests. Please slow down.'
},
keyGenerator: defaultKeyGenerator
});
/**
* Strict rate limiter for login attempts
*/
const loginLimiter = rateLimit({
windowMs: config.rateLimitWindowMs,
max: config.loginRateLimitMax,
standardHeaders: true,
legacyHeaders: false,
message: {
success: false,
error: 'Too many login attempts. Please try again in a minute.'
},
keyGenerator: (req) => {
return req.ip || req.headers['x-forwarded-for'] || 'unknown';
}
});
/**
* Very strict limiter for password changes
*/
const passwordChangeLimiter = rateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes
max: 3,
standardHeaders: true,
legacyHeaders: false,
message: {
success: false,
error: 'Too many password change attempts. Please try again later.'
}
});
module.exports = {
apiLimiter,
widgetLimiter,
loginLimiter,
passwordChangeLimiter
};