feat(webhook): accept standard webhook signatures

This commit is contained in:
HwangJohn
2026-06-17 17:31:01 +09:00
committed by Teknium
parent 62f353eed5
commit cb3b5bb64c
2 changed files with 10 additions and 6 deletions
+7 -4
View File
@@ -548,8 +548,8 @@ class WebhookAdapter(BasePlatformAdapter):
prompt = self._render_prompt(route_config.get("prompt", ""), payload, event_type, route_name)
if skills := route_config.get("skills", []):
prompt = self._apply_skills(prompt, skills)
delivery_id = headers.get("X-GitHub-Delivery", headers.get(
"svix-id", headers.get("X-Request-ID", str(int(time.time() * 1000)))))
delivery_id = headers.get("X-GitHub-Delivery", headers.get("svix-id", headers.get(
"webhook-id", headers.get("X-Request-ID", str(int(time.time() * 1000))))))
now = time.time() # idempotency: skip duplicate deliveries (webhook retries)
if not self._record_delivery_id(delivery_id, now):
logger.info("[webhook] Skipping duplicate delivery %s", delivery_id)
@@ -617,14 +617,17 @@ class WebhookAdapter(BasePlatformAdapter):
# --- Signature validation ---
def _validate_signature(self, request: "web.Request", body: bytes, secret: str) -> bool:
"""Validate webhook signature (GitHub, GitLab, Svix, Linear, generic HMAC-SHA256)."""
"""Validate webhook signature (GitHub, GitLab, Svix, Standard Webhooks, Linear, generic HMAC-SHA256)."""
headers = request.headers
def _header(name: str) -> str:
return headers.get(name, "") or headers.get(name.lower(), "") or headers.get(name.upper(), "")
# Svix / AgentMail: signed content is "{id}.{timestamp}.{raw_body}".
# Svix / AgentMail: signed content is "{id}.{timestamp}.{raw_body}". Standard Webhooks
# (webhook-*; GitLab signing tokens) is the same scheme under other header names.
svix = [_header(name) for name in ("svix-id", "svix-timestamp", "svix-signature")]
if not any(svix):
svix = [_header(name) for name in ("webhook-id", "webhook-timestamp", "webhook-signature")]
if any(svix):
return _validate_svix_signature(body, secret, *svix)
# Linear (any header case): hex HMAC of the body. GitHub: sha256=<hex>. GitLab: plain token.
@@ -498,6 +498,7 @@ 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
- **Standard Webhooks**: `webhook-id`, `webhook-timestamp`, and `webhook-signature` headers — signed content is `{id}.{timestamp}.{raw_body}` with a `v1,<base64-hmac-sha256>` signature
- **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.
@@ -529,7 +530,7 @@ Requests exceeding the limit receive a `429 Too Many Requests` response.
### Idempotency
Delivery IDs (from `X-GitHub-Delivery`, `X-Request-ID`, or a timestamp fallback) are cached for **1 hour**. Duplicate deliveries (e.g. webhook retries) are silently skipped with a `200` response, preventing duplicate agent runs.
Delivery IDs (from `X-GitHub-Delivery`, `svix-id`, `webhook-id`, `X-Request-ID`, or a timestamp fallback) are cached for **1 hour**. Duplicate deliveries (e.g. webhook retries) are silently skipped with a `200` response, preventing duplicate agent runs.
### Body size limits
@@ -588,7 +589,7 @@ This is the same trust model that applies to everything the agent reads: web pag
### Duplicate responses
- The idempotency cache should prevent this — check that the webhook source is sending a delivery ID header (`X-GitHub-Delivery` or `X-Request-ID`)
- The idempotency cache should prevent this — check that the webhook source is sending a delivery ID header (`X-GitHub-Delivery`, `svix-id`, `webhook-id`, or `X-Request-ID`)
- Delivery IDs are cached for 1 hour
### `gh` CLI errors (GitHub comment delivery)