From 9c90b3a59732a4abec952c3260ebf7ec031ff2fe Mon Sep 17 00:00:00 2001 From: memosr Date: Sun, 12 Apr 2026 13:26:33 +0300 Subject: [PATCH] fix(security): validate secret in _reload_dynamic_routes to prevent HMAC bypass --- gateway/platforms/webhook.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/gateway/platforms/webhook.py b/gateway/platforms/webhook.py index d7714ff5652..115b22d196f 100644 --- a/gateway/platforms/webhook.py +++ b/gateway/platforms/webhook.py @@ -308,11 +308,26 @@ class WebhookAdapter(BasePlatformAdapter): data = json.loads(subs_path.read_text(encoding="utf-8")) if not isinstance(data, dict): return - # Merge: static routes take precedence over dynamic ones - self._dynamic_routes = { - k: v for k, v in data.items() - if k not in self._static_routes - } + # Merge: static routes take precedence over dynamic ones. + # Reject any dynamic route whose effective secret is empty — + # an empty secret would cause _handle_webhook to skip HMAC + # validation entirely, letting unauthenticated callers in. + new_dynamic: Dict[str, dict] = {} + for k, v in data.items(): + if k in self._static_routes: + continue + effective_secret = v.get("secret", self._global_secret) + if not effective_secret: + logger.warning( + "[webhook] Dynamic route '%s' skipped: 'secret' is " + "missing or empty. Set a valid HMAC secret, or use " + "'%s' to explicitly disable auth (testing only).", + k, + _INSECURE_NO_AUTH, + ) + continue + new_dynamic[k] = v + self._dynamic_routes = new_dynamic self._routes = {**self._dynamic_routes, **self._static_routes} self._dynamic_routes_mtime = mtime logger.info(