Webhook Secret Token with HMAC Digest

Description

Currently, when creating a webhook you have the option to set a value that will be sent in the X-Gitlab-Token HTTP Header.

This is meant for validation that the request did actually come from GitLab.

But, this security feature is very poorly implemented for a few reasons:

  1. This token is sent in plain text
  2. No way to verify that the payload is valid and came from GitLab
  3. Static tokens are vulnerable to replay attacks — an attacker who intercepts a legitimate request can resend it indefinitely, since the token doesn't change per request (see also #587536 (closed))

This should function more like GitHub's version, where that token (the hook secret) is used as the key for an HMAC SHA256 hex digest, which is then sent as an HTTP Header.

You can then compute your own hash of the payload using the secret that it should be, and compare it to the one sent in the header.

However, with GitLab (as said before), you can not do this, the best that you can do is check the plain text header... Not great.

Some examples of other services that sign their requests:

Proposal

This proposal also addresses the replay attack protection described in #587536 (closed). The HMAC signing token approach solves both payload integrity verification and replay resistance in a single feature; no separate JWT or nonce mechanism is needed.

  • Add a signing token field when creating a webhook, explicitly labeled as the key for the HMAC hash of the payload. A new field is needed to ensure backwards compatibility with existing webhooks. This field should be absolutely secret, like a password (even when editing an endpoint).
  • When sending a payload to an endpoint, add two additional HTTP headers:
    • X-Gitlab-Signature — an HMAC-SHA256 hex digest of {timestamp}.{webhook-uuid}.{payload}, prefixed with sha256=
    • X-Gitlab-Timestamp — the Unix timestamp used in the signature, so receivers can verify the request is recent and reject stale/replayed requests (e.g. outside a 5-minute window)
  • The UUID and timestamp included in the HMAC message make replay attacks impractical: a replayed request carries a stale timestamp that receivers can detect and reject.

If no signing token is configured, the X-Gitlab-Signature and X-Gitlab-Timestamp headers are not sent.

Out of scope

System Hooks are not covered by this issue. Signing token support for System Hooks will be addressed separately in #503457 (closed).

Technical Proposal

@van.m.anderson worked on a merge request that was very close to implementing the feature Draft: POC for HMAC webhook signing (!163102 - closed). Thank you, Van ❤️. We can pick up the work from where they left off.

The remaining work in that MR is outlined here !163102 (comment 2085734123), which is to split the backend and frontend implementations and then add test coverage.

GitLab's Documentation on Webhooks

GitLab's "Secret Token" Documentation

GitHub's Documentation on Webhooks

Replay Attack Protection proposal (#587536)

Designs

Outdated designs
State Design
New webhook image
Edit legacy webhook (created before signing tokens were introduced) image
Edit webhook (with signing token) image
Edit webhook > Regenerated signing token image

Open Questions: Secret Token Deprecation

  1. Should the legacy secret token (X-Gitlab-Token) be deprecated once the signing token is released?

Note: The deprecation timeline will be difficult to determine from instrumentation alone. While we can measure how many webhooks have a signing token configured on the GitLab side, we have no visibility into how many receivers (external services) are actively validating it, rather than still relying on X-Gitlab-Token. This means adoption data will likely undercount real usage, and any removal decision will require tolerance for some uncertainty.

Documentation blurb

Overview

This feature proposal would allow for more secure endpoints, since users could verify that the payload is authentic and originated from a GitLab server.

It also protects against replay attacks by incorporating a timestamp and unique request UUID into the HMAC signature. Receivers can validate the timestamp is recent (e.g. within 5 minutes) to reject replayed requests, even if an attacker captured a legitimate one.

This would solve multiple security flaws in GitLab's current webhook implementation that allow for easy spoofing and replay of requests that could potentially be damaging.

Use cases

  1. Any webhook consumer that needs to verify requests genuinely originated from GitLab.
  2. Security-conscious teams in regulated industries (finance, healthcare) with compliance requirements around request integrity and audit trails.

Feature checklist

Make sure these are completed before closing the issue, with a link to the relevant commit.

Edited by Rodrigo Tomonari