pay-rails/pay - non-constant-time HMAC comparison in Paddle Billing webhook signature verifier
Published: July 01, 2026
SECURITY IDENTIFIERS
- GHSA: GHSA-mjgf-xj26-9qf9
GEM
SEVERITY
CVSS v3.x: 7.4 (High)
PATCHED VERSIONS
>= 11.6.2
DESCRIPTION
Summary
The Pay::Webhooks::PaddleBillingController#valid_signature?
(app/controllers/pay/webhooks/paddle_billing_controller.rb)
verifies the Paddle Billing webhook signature by computing
OpenSSL::HMAC.hexdigest(...) and comparing it to the attacker-supplied
header value using Ruby's String#==. Ruby's == is non-constant-time —
it returns as soon as the first byte mismatches — and exposes a per-byte
timing side channel on the webhook signature verification path. The
canonical mitigation is to use a constant-time primitive
(OpenSSL.fixed_length_secure_compare /
ActiveSupport::SecurityUtils.secure_compare).
- IMPACT - CWE-208 — Observable Timing Discrepancy on the webhook
signature verifier.
- An attacker who can deliver requests to the
/pay/webhooks/paddle_billingmount point can probe the verifier with guessedPaddle-Signatureheader values. BecauseString#==short-circuits on the first mismatching byte, the response-time distribution shifts as the prefix of the guess matches the real hex digest. - A signature recovered through the oracle lets the attacker deliver
forged Paddle Billing webhook events (e.g.
subscription.created/transaction.completed) against the host application. Pay's webhook 'processor enqueues aPay::Webhooks::ProcessJobfor any accepted' webhook, which downstream applications use to update billing state — including provisioning paid features, recording refunds, and triggering customer notifications. - The endpoint is internet-reachable by definition (Paddle must POST events to it).
- An attacker who can deliver requests to the
- Credit - Reported by tonghuaroot (https://github.com/tonghuaroot)
RELATED
- https://advisories.gitlab.com/gem/pay/GHSA-mjgf-xj26-9qf9
- https://rubygems.org/gems/pay/versions/11.6.2
- https://github.com/pay-rails/pay/releases/tag/v11.6.2
- https://github.com/pay-rails/pay/commit/ba6494109d88209fba2a4df2d9d6373fe81ed805
- https://github.com/pay-rails/pay/issues/1232
- https://github.com/rubysec/ruby-advisory-db/pull/1158
- https://github.com/pay-rails/pay/security/advisories/GHSA-mjgf-xj26-9qf9
- https://github.com/advisories/GHSA-mjgf-xj26-9qf9
