Skip to main content

Module webhook

Module webhook 

Source
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::ConstantTimeEq to 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=false so the upstream git server records a successful delivery and stops retrying.

Structs§

BitbucketServerExtractor
Decodes the Bitbucket Server / Data Center push shape: { "changes": [ { "refId": "refs/heads/<branch>", ... } ], "repository": { "slug": "..." } }.
DeliveryDedupCache
Bounded LRU-ish set of delivery ids we have already processed. Insertion is O(1) amortised: a HashSet answers membership, a VecDeque records arrival order so the oldest entry rolls off once the cap is reached. The cap is DELIVERY_DEDUP_CAP.
EnvSecretResolver
Resolves env:<NAME> against the process environment. Any other shape is treated as the literal secret. Returns None when the referenced environment variable is unset (so the handler returns 503 rather than accepting unauthenticated triggers).
ParsedPush
Outcome of pulling the push fields out of an upstream-signed body. branch carries the bare branch name (e.g. main, not the full refs/heads/main ref). repo_hint is the upstream-reported repo identifier when present; reserved for the future per-repo trigger path (today the handler scope-alls and ignores the hint).
RefHeadsExtractor
Decodes the top-level "ref": "refs/heads/<branch>" shape shipped by GitHub, Gitea, Forgejo, Gogs, and GitLab. Tolerates extra fields and reads repository.full_name when present so future per-repo routing has a hint to work with.
SourcehutExtractor
Decodes the Sourcehut hgmail/builds shape that nests the refs under event.refs[0].name. Repo hint comes from event.repo.name.
StaticSecretResolver
In-memory secret resolver for tests.
WebhookConcurrencyLimit
Hand-rolled semaphore-backed concurrency cap on webhook_git. Lives on WebhookConfig so a router rebuild keeps the live permit count intact. Wraps an Arc<Semaphore> directly rather than depending on tower::limit::ConcurrencyLimitLayer so the workspace stays off the tower base crate.
WebhookConfig
Per-route config attached to the webhook handler.
WebhookRateLimiter
Per-source-IP token bucket. capacity tokens refill at refill_per_sec tokens/second; each admitted request consumes one. When the bucket empties the next call to WebhookRateLimiter::admit returns false so the handler can refuse with 429.
WebhookResponse

Enums§

EventKind
What kind of event the upstream advertised. Read from the provider-specific event header; Unknown when 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_BURST allowed. 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§

WebhookPayloadExtractor
Provider-specific decoder for the verified webhook body. Implementors receive the raw headers (so they can pick a payload shape per Content-Type or X-Event-Key) and the byte slice the HMAC already covered. Returning None signals “this body is not a push we can route” and the handler responds 200 + triggered=false.
WebhookSecretResolver
Pluggable resolver that turns the operator’s triggers.webhook_secret_ref value into the raw bytes used as the HMAC key. Production maps env:<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_provider string 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/git handler.