security: v1.7.7 hardening release
All checks were successful
CI / lint-and-test (push) Successful in 51s
Release / build-and-push (push) Successful in 1m57s

- 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:
2026-04-26 15:49:02 +02:00
parent 7cd7709b4a
commit d01e7801ed
14 changed files with 341 additions and 30 deletions

View File

@@ -1,4 +1,5 @@
import structlog
from config import WEBHOOK_CLIENT_SECRET
from fastapi import APIRouter, Request, Response
router = APIRouter()
@@ -10,9 +11,12 @@ async def graph_webhook(request: Request):
"""
Receive Microsoft Graph change notifications.
Handles the validation handshake by echoing validationToken.
Validates clientState on notifications to prevent spoofing.
"""
validation_token = request.query_params.get("validationToken")
if validation_token:
# Microsoft sends validationToken as a query param during subscription creation.
# Echo it back as plain text to prove endpoint ownership.
return Response(content=validation_token, media_type="text/plain")
try:
@@ -21,12 +25,26 @@ async def graph_webhook(request: Request):
logger.warning("Invalid webhook payload", error=str(exc))
return Response(status_code=400)
for notification in body.get("value", []):
notifications = body.get("value", [])
if not isinstance(notifications, list):
logger.warning("Invalid webhook payload structure")
return Response(status_code=400)
for notification in notifications:
client_state = notification.get("clientState")
if WEBHOOK_CLIENT_SECRET and client_state != WEBHOOK_CLIENT_SECRET:
logger.warning(
"Graph webhook rejected: invalid clientState",
change_type=notification.get("changeType"),
resource=notification.get("resource"),
)
return Response(status_code=401)
logger.info(
"Received Graph notification",
change_type=notification.get("changeType"),
resource=notification.get("resource"),
client_state=notification.get("clientState"),
client_state=client_state,
)
return {"status": "accepted"}