Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions backend/secuscan/rate_limiter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from datetime import datetime, timedelta
from fastapi import HTTPException, Request, status
from collections import defaultdict
import asyncio

class RateLimiter:
def __init__(self, requests_per_minute=10):
self.requests_per_minute = requests_per_minute
self.requests = defaultdict(list)
self.cleanup_task = None

async def check_rate_limit(self, key: str) -> bool:
"""Check if request exceeds rate limit"""
now = datetime.now()
minute_ago = now - timedelta(minutes=1)

# Clean old requests
self.requests[key] = [
req_time for req_time in self.requests[key]
if req_time > minute_ago
]

if len(self.requests[key]) >= self.requests_per_minute:
return False

self.requests[key].append(now)
return True

async def enforce_limit(self, key: str, limit: int = None):
"""Raise HTTPException if rate limit exceeded"""
limit = limit or self.requests_per_minute
if not await self.check_rate_limit(key):
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail=f"Rate limit exceeded: max {limit} requests per minute"
)

scheduler_limiter = RateLimiter(requests_per_minute=5)

async def rate_limit_scheduler(request: Request):
"""Dependency to rate limit scheduler endpoints"""
user_id = request.user.id if hasattr(request, 'user') else 'anonymous'
await scheduler_limiter.enforce_limit(f"scheduler:{user_id}", limit=5)
7 changes: 6 additions & 1 deletion backend/secuscan/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import asyncio
from pathlib import Path
from urllib.parse import urlencode, urlparse
from secuscan.rate_limiter import rate_limit_scheduler

def parse_json_fields(rows: List[Dict], fields: List[str]) -> List[Dict]:
"""Helper to parse stringified JSON fields from SQLite."""
Expand Down Expand Up @@ -1326,7 +1327,11 @@ async def delete_workflow(workflow_id: str):


@router.post("/workflows/scheduler/tick")
async def trigger_workflow_tick():
async def trigger_workflow_tick(request: Request = Depends(rate_limit_scheduler)):
"""
Trigger scheduler tick (rate limited)
CRITICAL: Limited to 5 requests per minute per user to prevent abuse
"""
await scheduler.tick()
return {"tick": "ok"}

Expand Down
Loading