security: v1.7.7 hardening release
- Add WEBHOOK_CLIENT_SECRET validation for Graph webhooks - Add Redis-backed rate limiting (fetch/ask/write/default tiers) - Validate LLM_BASE_URL to prevent SSRF (HTTPS only, block private IPs) - Enforce non-wildcard CORS when AUTH_ENABLED=true - Add Content-Security-Policy headers - Fix audit middleware to use verified JWT claims via contextvars - Cap bulk_tags updates to 10,000 documents - Return generic error messages to clients (no internal detail leakage) - Strict AlertCondition Pydantic model for alert rules - Security warning on MCP stdio server startup - Remove MongoDB/Redis host ports from docker-compose - Remove mongo_query from /ask API response
This commit is contained in:
@@ -158,7 +158,7 @@ def list_events(
|
||||
cursor_query = events_collection.find(query).sort([("timestamp", -1), ("_id", -1)]).limit(safe_page_size)
|
||||
events = list(cursor_query)
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to query events: {exc}") from exc
|
||||
raise HTTPException(status_code=500, detail="Failed to query events") from exc
|
||||
|
||||
next_cursor = None
|
||||
if len(events) == safe_page_size:
|
||||
@@ -241,9 +241,17 @@ def bulk_tags(
|
||||
update = {"$set": {"tags": tags}} if body.mode == "replace" else {"$addToSet": {"tags": {"$each": tags}}}
|
||||
|
||||
try:
|
||||
matched = events_collection.count_documents(query, limit=10001)
|
||||
if matched > 10000:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Bulk tag update matches too many events (>10000). Narrow your filters.",
|
||||
)
|
||||
result_obj = events_collection.update_many(query, update)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update tags: {exc}") from exc
|
||||
raise HTTPException(status_code=500, detail="Failed to update tags") from exc
|
||||
|
||||
log_action(
|
||||
"bulk_tags",
|
||||
@@ -268,7 +276,7 @@ def filter_options(
|
||||
actor_upns = sorted([a for a in events_collection.distinct("actor_upn") if a])[:safe_limit]
|
||||
devices = sorted([a for a in events_collection.distinct("target_displays") if isinstance(a, str)])[:safe_limit]
|
||||
except Exception as exc:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to load filter options: {exc}") from exc
|
||||
raise HTTPException(status_code=500, detail="Failed to load filter options") from exc
|
||||
|
||||
if not user_can_access_privacy_services(user):
|
||||
services = [s for s in services if s not in PRIVACY_SERVICES]
|
||||
|
||||
Reference in New Issue
Block a user