Add optional inbound webhook signature verification (closes #67) (#228)
Some checks failed
check / check (push) Superseded by a newer commit; never tested

The receiver had no inbound authentication of any kind: /webhook/{uuid}
was mounted behind a rate limiter alone, so the only thing protecting an
entrypoint was the secrecy of a v4 UUID in a URL path. Inbound headers are
forwarded almost verbatim to the target, so anyone who learned the URL
also chose the headers the downstream service received.

Adds an optional per-entrypoint secret with two schemes: github
(X-Hub-Signature-256, HMAC-SHA256 hex over the raw body) and gitlab
(X-Gitlab-Token, a plain shared token). Comparison is constant-time, the
HMAC is computed over the raw body before any parsing, and rejection
happens before persistence -- an unauthenticated request creates no event
row. An entrypoint with no secret behaves exactly as before, including
every row that predates this change.

The scheme's credential header is stripped from the header map before it
is marshalled into Event.Headers, so the GitLab token reaches neither the
event store nor any delivery target. SchemeInfo.HeaderIsDigest defaults to
false meaning strip, so a scheme added later is protected unless its
header is positively declared a digest.
This commit was merged in pull request #228.
This commit is contained in:
2026-08-20 08:01:32 +02:00
parent ac782f4c5a
commit fcead5d401
16 changed files with 2259 additions and 25 deletions

View File

@@ -213,6 +213,10 @@ func (s *Server) setupSourceRoutes() {
"/entrypoints/{entrypointID}/toggle",
s.h.HandleEntrypointToggle(),
)
r.Post(
"/entrypoints/{entrypointID}/secret",
s.h.HandleEntrypointSecret(),
)
r.Post("/targets", s.h.HandleTargetCreate())
// The edit form is the one page that renders a target's
// destination URL and header values in full; see