Release v1.7.15: security hardening, async auth, CSP tightening, model validation, SSRF guard, rate limiting improvements, frontend extraction, Docker compose security
Release / build-and-push (push) Successful in 3m12s

This commit is contained in:
2026-05-28 14:57:09 +02:00
parent fe95dfcfce
commit f7fca05210
18 changed files with 943 additions and 873 deletions
+28
View File
@@ -4,7 +4,9 @@ Supported channels:
- webhook: POST JSON to any URL (Slack, Teams, generic)
"""
import ipaddress
from datetime import UTC, datetime
from urllib.parse import urlparse
import requests
import structlog
@@ -15,6 +17,26 @@ logger = structlog.get_logger("aoc.notifications")
WEBHOOK_TIMEOUT = 15
def _validate_webhook_url(url: str):
"""Prevent SSRF by rejecting internal/reserved addresses."""
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
raise ValueError(f"Webhook URL scheme '{parsed.scheme}' is not allowed")
hostname = (parsed.hostname or "").lower()
if not hostname:
raise ValueError("Webhook URL must have a valid hostname")
blocked = {"localhost", "127.0.0.1", "0.0.0.0", "::1", "169.254.169.254"}
if hostname in blocked:
raise ValueError(f"Webhook URL hostname '{hostname}' is not allowed")
try:
ip = ipaddress.ip_address(hostname)
if ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved:
raise ValueError(f"Webhook URL IP '{hostname}' is not allowed")
except ValueError as exc:
if "not allowed" in str(exc):
raise
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
@@ -142,6 +164,12 @@ def send_notification(
if not webhook_url:
return False
try:
_validate_webhook_url(webhook_url)
except ValueError as exc:
logger.warning("Notification blocked: invalid webhook URL", error=str(exc))
return False
builders = {
"slack": _build_slack_payload,
"teams": _build_teams_payload,