fix(webhook): rate-limit V1 deprecation warning + document V2 signature

- warn once per route instead of on every request (busy senders would
  spam the log)
- document X-Webhook-Signature-V2 / X-Webhook-Timestamp in the webhooks
  user guide

Follow-ups for salvaged #58461.
This commit is contained in:
teknium1 2026-07-04 14:17:18 -07:00 committed by Teknium
parent 70449a4939
commit 708b57e009
2 changed files with 16 additions and 9 deletions

View file

@ -121,6 +121,9 @@ class WebhookAdapter(BasePlatformAdapter):
self._dynamic_routes_mtime: float = 0.0
self._routes: Dict[str, dict] = dict(self._static_routes)
self._runner = None
# Routes already warned about legacy V1 body-only signatures
# (once-per-route so a busy sender doesn't spam the log).
self._v1_signature_warned: set[str] = set()
# Delivery info keyed by session chat_id.
#
@ -913,14 +916,17 @@ class WebhookAdapter(BasePlatformAdapter):
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
logger.warning(
"[webhook] Route '%s' uses legacy body-only HMAC (no "
"timestamp), which is vulnerable to replay attacks. Add "
"an 'X-Webhook-Timestamp' header and switch to "
"'X-Webhook-Signature-V2' (HMAC-SHA256 of "
"'<timestamp>.<body>').",
request.match_info.get("route_name", ""),
)
route_name = request.match_info.get("route_name", "")
if route_name not in self._v1_signature_warned:
self._v1_signature_warned.add(route_name)
logger.warning(
"[webhook] Route '%s' uses legacy body-only HMAC (no "
"timestamp), which is vulnerable to replay attacks. Add "
"an 'X-Webhook-Timestamp' header and switch to "
"'X-Webhook-Signature-V2' (HMAC-SHA256 of "
"'<timestamp>.<body>').",
route_name,
)
return hmac.compare_digest(generic_sig, expected)
# No recognised signature header but secret is configured → reject

View file

@ -387,7 +387,8 @@ The adapter validates incoming webhook signatures using the appropriate method f
- **GitHub**: `X-Hub-Signature-256` header — HMAC-SHA256 hex digest prefixed with `sha256=`
- **GitLab**: `X-Gitlab-Token` header — plain secret string match
- **Generic**: `X-Webhook-Signature` header — raw HMAC-SHA256 hex digest
- **Generic (V2, recommended)**: `X-Webhook-Signature-V2` + `X-Webhook-Timestamp` headers — HMAC-SHA256 hex digest of `<timestamp>.<body>`. The timestamp (Unix seconds) must be within ±300 seconds of the server clock, which prevents captured requests from being replayed later.
- **Generic (V1, legacy)**: `X-Webhook-Signature` header — raw HMAC-SHA256 hex digest of the body only. Still accepted for backward compatibility, but it has no replay protection (a captured request replays indefinitely); the gateway logs a deprecation warning once per route. Switch senders to V2.
If a secret is configured but no recognized signature header is present, the request is rejected.