Summary
The webhook dispatcher has resource, retry-policy, and signature-security weaknesses, and the whole feature is undocumented.
Findings
Unbounded thread-per-event (medium)
hexus/webhook/dispatcher.py:108-114 + mcp_server/tools.py:190-204, 340-353 — dispatch_webhook starts a new daemon thread per event, and memory_retain calls it inside the per-content loop.
- Scenario: a 5,000-item
memory_retain spawns up to 5,000 concurrent webhook threads, each retrying with backoff — thread exhaustion. No queue/worker pool.
Retries permanent 4xx (medium)
dispatcher.py:59-70 — all non-2xx responses are retried, including 401/404/410. A dead endpoint gets 4 attempts × (5s timeout + 1+2+4s backoff) per event; combined with thread-per-event, a write burst piles up dozens of ~27s threads.
Unsigned when secret unset — silent (medium)
tools.py:196, 346 — secret=os.environ.get("HEXUS_WEBHOOK_SECRET"); if unset, sign_payload is skipped and the payload (including full memory content) is POSTed unsigned with no warning. No TLS/scheme enforcement on HEXUS_WEBHOOK_URL.
Weak signature format (low)
dispatcher.py:46 — the signature header has no scheme prefix (e.g. sha256=) and signs no timestamp, so receivers can't do GitHub-style verification and payloads are replayable.
Undocumented feature (low → see docs issue)
Webhooks (HEXUS_WEBHOOK_URL/HEXUS_WEBHOOK_SECRET, payload schema, signature-verification recipe) are documented nowhere except a one-line ROADMAP mention.
Suggested direction
Use a bounded worker pool / reuse the async writer for dispatch; retry only 5xx/timeouts (and 429 with Retry-After); warn loudly (or refuse) when a URL is set without a secret; add a sha256= prefix + signed timestamp; document the feature and a verification snippet.
Filed from the 2026-07 codebase review.
Summary
The webhook dispatcher has resource, retry-policy, and signature-security weaknesses, and the whole feature is undocumented.
Findings
Unbounded thread-per-event (medium)
hexus/webhook/dispatcher.py:108-114+mcp_server/tools.py:190-204, 340-353—dispatch_webhookstarts a new daemon thread per event, andmemory_retaincalls it inside the per-content loop.memory_retainspawns up to 5,000 concurrent webhook threads, each retrying with backoff — thread exhaustion. No queue/worker pool.Retries permanent 4xx (medium)
dispatcher.py:59-70— all non-2xx responses are retried, including 401/404/410. A dead endpoint gets 4 attempts × (5s timeout + 1+2+4s backoff) per event; combined with thread-per-event, a write burst piles up dozens of ~27s threads.Unsigned when secret unset — silent (medium)
tools.py:196, 346—secret=os.environ.get("HEXUS_WEBHOOK_SECRET"); if unset,sign_payloadis skipped and the payload (including full memorycontent) is POSTed unsigned with no warning. No TLS/scheme enforcement onHEXUS_WEBHOOK_URL.Weak signature format (low)
dispatcher.py:46— the signature header has no scheme prefix (e.g.sha256=) and signs no timestamp, so receivers can't do GitHub-style verification and payloads are replayable.Undocumented feature (low → see docs issue)
Webhooks (
HEXUS_WEBHOOK_URL/HEXUS_WEBHOOK_SECRET, payload schema, signature-verification recipe) are documented nowhere except a one-line ROADMAP mention.Suggested direction
Use a bounded worker pool / reuse the async writer for dispatch; retry only 5xx/timeouts (and 429 with Retry-After); warn loudly (or refuse) when a URL is set without a secret; add a
sha256=prefix + signed timestamp; document the feature and a verification snippet.Filed from the 2026-07 codebase review.