Expand description
POST /webhook/git route.
Accepts a GitHub-shaped push payload, verifies the HMAC-SHA256
signature against the operator’s configured shared secret, applies
the optional branch filter, and triggers a scan via the same
ScanTrigger handle the manual /api/v1/scan endpoint uses.
Layout chosen for maximum compatibility with self-hosted git servers:
- Header:
X-Hub-Signature-256: sha256=<hex>(the GitHub / Gitea / Forgejo / Sourcehut convention). - Body: any JSON object carrying a
"ref": "refs/heads/<branch>"field. Other fields are ignored so a thin Gitea / Bitbucket payload also works. - HMAC: the signature is computed over the raw body bytes; we use
subtle::ConstantTimeEqto avoid timing leaks. - The webhook bypasses bearer auth because HMAC IS the auth.
Errors:
- Missing / invalid signature → HTTP 401.
- Missing / unset secret → HTTP 503 (operator must configure
triggers.webhook_secret_ref). - Wrong branch → HTTP 200 with
triggered=falseso the upstream git server records a successful delivery and stops retrying.
Structs§
- Bitbucket
Server Extractor - Decodes the Bitbucket Server / Data Center push shape:
{ "changes": [ { "refId": "refs/heads/<branch>", ... } ], "repository": { "slug": "..." } }. - Delivery
Dedup Cache - Bounded LRU-ish set of delivery ids we have already processed.
Insertion is O(1) amortised: a
HashSetanswers membership, aVecDequerecords arrival order so the oldest entry rolls off once the cap is reached. The cap isDELIVERY_DEDUP_CAP. - EnvSecret
Resolver - Resolves
env:<NAME>against the process environment. Any other shape is treated as the literal secret. ReturnsNonewhen the referenced environment variable is unset (so the handler returns 503 rather than accepting unauthenticated triggers). - Parsed
Push - Outcome of pulling the push fields out of an upstream-signed body.
branchcarries the bare branch name (e.g.main, not the fullrefs/heads/mainref).repo_hintis the upstream-reported repo identifier when present; reserved for the future per-repo trigger path (today the handler scope-alls and ignores the hint). - RefHeads
Extractor - Decodes the top-level
"ref": "refs/heads/<branch>"shape shipped by GitHub, Gitea, Forgejo, Gogs, and GitLab. Tolerates extra fields and readsrepository.full_namewhen present so future per-repo routing has a hint to work with. - Sourcehut
Extractor - Decodes the Sourcehut
hgmail/builds shape that nests the refs underevent.refs[0].name. Repo hint comes fromevent.repo.name. - Static
Secret Resolver - In-memory secret resolver for tests.
- Webhook
Concurrency Limit - Hand-rolled semaphore-backed concurrency cap on
webhook_git. Lives onWebhookConfigso a router rebuild keeps the live permit count intact. Wraps anArc<Semaphore>directly rather than depending ontower::limit::ConcurrencyLimitLayerso the workspace stays off thetowerbase crate. - Webhook
Config - Per-route config attached to the webhook handler.
- Webhook
Rate Limiter - Per-source-IP token bucket.
capacitytokens refill atrefill_per_sectokens/second; each admitted request consumes one. When the bucket empties the next call toWebhookRateLimiter::admitreturnsfalseso the handler can refuse with 429. - Webhook
Response
Enums§
- Event
Kind - What kind of event the upstream advertised. Read from the
provider-specific event header;
Unknownwhen no recognised header is present so the handler can fall through to the legacy best-effort path.
Constants§
- DEFAULT_
WEBHOOK_ MAX_ CONCURRENT - Default cap on simultaneous in-flight webhook handlers. Set above
the dispatcher’s scan-request queue depth so legitimate bursts are
absorbed, but bounded so a flood of valid-HMAC deliveries cannot
peg every tokio worker on HMAC verification + body read in
parallel. Operators tune via
[triggers].webhook_max_concurrent. - DEFAULT_
WEBHOOK_ RATE_ LIMIT_ BURST - Burst depth for the per-IP token bucket. Matches the default per-minute rate so a fresh sender can fire up to this many deliveries back-to-back before the bucket drains.
- DEFAULT_
WEBHOOK_ RATE_ LIMIT_ MAX_ IPS - Maximum number of IPs the per-IP rate limiter tracks before it evicts the least-recently-seen entry. Caps memory under a flood of unique source addresses.
- DEFAULT_
WEBHOOK_ RATE_ LIMIT_ PER_ MINUTE - Default per-source-IP token bucket size. One push every two seconds
sustained, with bursts up to
DEFAULT_WEBHOOK_RATE_LIMIT_BURSTallowed. Operators tune via[triggers].webhook_rate_limit_per_minute. - DELIVERY_
DEDUP_ CAP - Bounded cap on the in-memory replay-dedup cache. Each entry is the raw delivery id string from the upstream provider (typically a UUID, ~36 bytes); 1024 entries caps memory at well under 100 KiB and covers the largest plausible burst window before older deliveries naturally roll off.
- MAX_
WEBHOOK_ BODY_ BYTES - Maximum webhook body the handler will buffer before bailing out with 413. 1 MiB covers every observed git-server payload comfortably while still bounding peak memory under a malicious caller.
Traits§
- Webhook
Payload Extractor - Provider-specific decoder for the verified webhook body. Implementors
receive the raw headers (so they can pick a payload shape per
Content-Typeor X-Event-Key) and the byte slice the HMAC already covered. ReturningNonesignals “this body is not a push we can route” and the handler responds 200 +triggered=false. - Webhook
Secret Resolver - Pluggable resolver that turns the operator’s
triggers.webhook_secret_refvalue into the raw bytes used as the HMAC key. Production mapsenv:<NAME>to$NAME, but tests substitute an in-process stub so they don’t have to mutate the environment.
Functions§
- classify_
event - Read the provider-specific event header into an
EventKind. - delivery_
id - Read the provider-specific delivery id (if any). Returned trimmed so trailing whitespace from misbehaving clients does not split the dedup cache.
- extractor_
for_ provider - Parse the operator’s
[triggers].webhook_providerstring into an extractor. Unknown / empty strings fall back to the default (RefHeadsExtractor) so a typo never silently disables webhooks. - sign
- Helper used by the daemon’s wiring + the test harness to mint the
sha256=<hex>header for a given (secret, body). - verify_
signature - Constant-time HMAC-SHA256 verification.
- webhook_
git POST /webhook/githandler.