fix(api): add tiered rate limiting to prevent polling lockouts (#460)

* fix(api): add tiered rate limiting to prevent polling lockouts

Replace the single global rate limiter (100 req/min/IP) with a tiered
system that separates high-frequency polling endpoints from standard
API traffic:

- Polling tier (300/min): /stats, /system/stats, /stacks/statuses,
  /metrics/historical, /health, /meta, /auth/status, /auth/sso/providers,
  /license. Exempt from the global limiter but governed by their own
  safety net to prevent resource exhaustion.
- Standard tier (200/min): All other endpoints, raised from 100.
- Webhook tier (500/min): POST /webhooks/:id/trigger, dedicated limiter
  for CI/CD platforms sharing datacenter IPs.
- Auth tier: Unchanged (5-10 attempts / 15 min).

Enterprise adaptations:
- Authenticated requests keyed by user session (JWT sub/username) instead
  of IP, preventing shared NAT/VPN environments from pooling budgets.
- Internal node-to-node traffic (node_proxy tokens) bypasses all rate
  limiters entirely.

Includes comprehensive stress tests (21 cases) validating tier
separation, node proxy bypass, and per-user keying.

* chore(deps): bump axios to 1.15.0 to fix SSRF vulnerability

Addresses GHSA-3p68-rc4w-qgx5 (NO_PROXY hostname normalization bypass).
This commit is contained in:
Anso
2026-04-09 17:56:58 -04:00
committed by GitHub
parent 7b6cd79e92
commit 8e1b9826cf
9 changed files with 452 additions and 21 deletions
+16 -1
View File
@@ -88,7 +88,20 @@ This applies to all stack endpoints: read, write, deploy, down, restart, stop, s
## Rate limiting
All API endpoints are rate-limited per IP address. Authentication endpoints have stricter limits to prevent brute-force attacks. When exceeded, the server responds with `429 Too Many Requests`:
Sencho uses a tiered rate limiting system that balances security with usability:
| Tier | Limit | Applies to | Keyed by |
|------|-------|------------|----------|
| **Polling** | 300/min | Dashboard status endpoints (`/stats`, `/system/stats`, `/stacks/statuses`, etc.) | User session |
| **Standard** | 200/min | All other API endpoints | User session |
| **Webhooks** | 500/min | `POST /webhooks/:id/trigger` | IP address |
| **Auth** | 5-10/15min | Login, setup, SSO endpoints | IP address |
Authenticated requests (browser sessions and API tokens) are rate-limited per user, not per IP. This prevents teams behind shared NAT or VPN from pooling rate limit budgets. Unauthenticated requests fall back to IP-based limiting.
Internal node-to-node traffic (requests authenticated with node proxy tokens) bypasses rate limiting entirely.
When exceeded, the server responds with `429 Too Many Requests`:
```json
{
@@ -98,6 +111,8 @@ All API endpoints are rate-limited per IP address. Authentication endpoints have
Standard rate limit headers (`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`) are included in all API responses, so clients can self-throttle accordingly.
The default limits can be tuned via environment variables (`API_RATE_LIMIT`, `API_POLLING_RATE_LIMIT`). See [Self-Hosting](/operations/self-hosting) for details.
## License tier requirements
Some endpoints are gated by license tier: