paqad-ai
Version:
Spec-driven development framework — AI agents that think before they type
166 lines (123 loc) • 4 kB
Markdown
# Rate Limit Signals
verified: 2026-11-02
Per-framework patterns that indicate rate limiting is present or absent.
## Contents
- [Laravel](#laravel)
- [Express.js](#expressjs)
- [Django / Django REST Framework](#django--django-rest-framework)
- [FastAPI](#fastapi)
- [Rails](#rails)
- [Spring Boot](#spring-boot)
- [General Red Flags (All Frameworks)](#general-red-flags-all-frameworks)
## Laravel
**Present (safe):**
```php
Route::post('/login', [AuthController::class, 'login'])
->middleware('throttle:10,1'); // 10 requests per minute
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
Route::post('/api/export', [ExportController::class, 'export']);
});
RateLimiter::for('login', function (Request $request) {
return Limit::perMinute(5)->by($request->ip());
});
```
**Absent (flag):**
```php
Route::post('/login', [AuthController::class, 'login']); // no throttle middleware
Route::post('/reset-password', [PasswordController::class, 'reset']); // no throttle
Route::post('/api/export', [ExportController::class, 'export']); // no throttle on bulk export
```
## Express.js
**Present (safe):**
```javascript
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({ windowMs: 60000, max: 10 });
app.post('/api/login', loginLimiter, authController.login);
```
**Absent (flag):**
```javascript
app.post('/api/login', authController.login); // no rate limit
app.post('/api/reset-password', resetController.reset); // no rate limit
app.post('/api/export', exportController.export); // no rate limit on bulk
```
## Django / Django REST Framework
**Present (safe):**
```python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'anon': '10/minute',
'user': '100/minute',
}
}
# view
class LoginView(APIView):
throttle_classes = [AnonRateThrottle]
```
**Absent (flag):**
```python
REST_FRAMEWORK = {} # no DEFAULT_THROTTLE_CLASSES
class LoginView(APIView):
pass # no throttle_classes
```
## FastAPI
**Present (safe):**
```python
from slowapi import Limiter
limiter = Limiter(key_func=get_remote_address)
.post("/login")
.limit("10/minute")
async def login(request: Request, ...):
...
```
**Absent (flag):**
```python
.post("/login")
async def login(credentials: LoginSchema):
... # no .limit decorator, no SlowAPI middleware
.post("/export")
async def export(request: Request):
... # no rate limit on bulk export
```
## Rails
**Present (safe):**
```ruby
# config/initializers/rack_attack.rb
Rack::Attack.throttle('login/ip', limit: 5, period: 60) do |request|
request.ip if request.path == '/users/sign_in' && request.post?
end
```
**Absent (flag):**
```ruby
# No Rack::Attack configuration for auth routes
# No throttle blocks in config/initializers/rack_attack.rb
```
## Spring Boot
**Present (safe):**
```java
public class AuthController {
public ResponseEntity<?> login(...) { ... }
}
```
**Absent (flag):**
```java
public ResponseEntity<?> login( LoginRequest request) {
// no @RateLimiter annotation, no custom rate limit filter
}
```
## General Red Flags (All Frameworks)
- Authentication endpoint has no per-IP or per-account request limit
- OTP/MFA verification endpoint has no attempt limit (6-digit OTP = 1,000,000 guesses)
- Password reset endpoint has no rate limit (enables reset token enumeration)
- List endpoint accepts unlimited `per_page` / `limit` values
- File upload endpoint has no per-user daily quota
- Unauthenticated endpoint triggers expensive DB query or external API call without throttling
- WebSocket `message` handler has no per-connection message rate counter