feat(api): add global rate limiter for all API endpoints (#317)

Apply a global rate limit of 100 requests/min per IP to all /api/ routes
in production, configurable via API_RATE_LIMIT env var. Auth endpoints
retain their existing stricter limits which stack independently.
Returns 429 Too Many Requests when exceeded.
This commit is contained in:
Anso
2026-04-01 20:12:30 -04:00
committed by GitHub
parent 19f9a1c804
commit b28ebfa6ff
4 changed files with 35 additions and 1 deletions
+3
View File
@@ -7,6 +7,9 @@ JWT_SECRET=your-secure-jwt-secret-here
# Directory containing docker-compose files
COMPOSE_DIR=/path/to/your/compose/files
# Global API rate limit (requests per minute per IP, production only)
API_RATE_LIMIT=100
# ─── SSO / LDAP Configuration (Admiral) ───────────────────────────
# LDAP / Active Directory
+4
View File
@@ -13,6 +13,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
* **api:** global rate limiter on all `/api/` routes — 100 requests/min per IP in production (configurable via `API_RATE_LIMIT` env var). Auth endpoints retain their existing stricter limits (5 req/15min) which stack independently. Returns `429 Too Many Requests` when exceeded.
### Fixed
* **security:** enforce `isValidStackName()` on all routes that accept a `stackName` parameter — 11 routes had no validation and 2 used a weaker manual check; all now use the canonical `^[a-zA-Z0-9_-]+$` regex guard, returning `400 Invalid stack name` on rejection
+15
View File
@@ -133,6 +133,21 @@ app.use(cors({
credentials: true,
}));
// Global API rate limiter — caps total requests per IP across all /api/ routes.
// Auth-specific limiters (authRateLimiter, ssoRateLimiter) apply additional,
// stricter limits on their respective routes and stack independently.
const globalApiLimiter = rateLimit({
windowMs: 60 * 1000,
max: process.env.NODE_ENV === 'production'
? parseInt(process.env.API_RATE_LIMIT || '100', 10)
: 1000,
standardHeaders: true,
legacyHeaders: false,
message: { error: 'Too many requests. Please try again shortly.' },
});
app.use('/api/', globalApiLimiter);
// Conditionally parse JSON bodies. Remote proxy requests must NOT have their body
// consumed here: express.json() drains the IncomingMessage stream into req.body
// and http-proxy then pipes an already-ended stream to the remote server.
+13 -1
View File
@@ -88,7 +88,19 @@ This applies to all stack endpoints: read, write, deploy, down, restart, stop, s
## Rate limiting
Authentication endpoints (`/api/auth/*`) are rate-limited to **5 requests per 15 minutes** in production. API token-authenticated requests are not rate-limited.
All `/api/` endpoints are subject to a global rate limit of **100 requests per minute** per IP address. When exceeded, the server responds with `429 Too Many Requests`:
```json
{
"error": "Too many requests. Please try again shortly."
}
```
Authentication endpoints (`/api/auth/*`) have an additional, stricter limit of **5 requests per 15 minutes** to prevent brute-force attacks. Both limits apply independently — a login request counts against both the global limit and the auth limit.
The global rate limit is configurable via the `API_RATE_LIMIT` environment variable (requests per minute, default `100`).
Standard rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are included in all API responses.
## License tier requirements