fix(#2164): address argus review — Option<String> secret + stale hook cleanup

Two issues flagged by argus in PR #2388 review:

1. Empty-key fallback: when load_or_generate() failed, webhook_secret was
   String::new(). An attacker knowing this could forge deliveries with a
   valid HMAC of the empty key. Fix: change to Option<String>; on None,
   skip hook registration entirely and return 503 from /webhook/* handlers
   (rather than 401 with a misleadingly-verifiable empty-key HMAC).

2. Stale hook cleanup: on upgrade from old code, old loopback hooks
   (http://127.0.0.1:.../webhook/knowledge, .../webhook/config-pr) were
   left alongside the new domain-URL hook. Fix: during ensure_webhook /
   ensure_config_pr_webhook, after listing hooks, delete any that end with
   our path suffix but point at a different base URL.

clippy + nix fmt clean.
This commit is contained in:
atlas 2026-07-12 02:21:33 +02:00
commit 7b1b1d9db8
5 changed files with 84 additions and 12 deletions

View file

@ -190,6 +190,26 @@ pub async fn ensure_webhook(
tracing::debug!(%target_url, "knowledge: push webhook already configured");
return Ok(());
}
// Delete stale hooks that point at our path but a different base
// (e.g. old loopback hooks from before the SSRF-bypass migration).
for h in &hooks {
let hook_url = h
.config
.as_ref()
.and_then(|c| c.get("url"))
.map_or("", String::as_str);
if hook_url.ends_with("/webhook/knowledge")
&& hook_url != target_url
&& let Some(id) = h.id
{
tracing::info!(hook_url, "knowledge: deleting stale webhook (wrong base)");
let _ = tokio::time::timeout(
HTTP_TIMEOUT,
client.repo_delete_hook(ORG, REPO, id).send(),
)
.await;
}
}
}
Err(e) => {
tracing::debug!(error = %e, "knowledge: listing hooks failed; attempting create");