GHSA-MJGF-XJ26-9QF9 is a high-severity security vulnerability in pay (rubygems), affecting versions <= 11.6.1. No fixed version is listed yet.
Summary Pay::Webhooks::PaddleBillingController#validsignature? (app/controllers/pay/webhooks/paddlebillingcontroller.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.fixedlengthsecurecompare / ActiveSupport::SecurityUtils.securecompare). Impact CWE-208, Observable Timing Discrepancy on the webhook signature verifier. An attacker who can deliver requests to the /pay/webhooks/paddlebilling mount point can probe the verifier with guessed Paddle-Signature header values. Because String#== 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 a Pay::Webhooks::ProcessJob for 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). Affected versions pay (rubygem) ≤ v11.6.1 (latest release as of 2026-05-27). Vulnerable code (file:line) app/controllers/pay/webhooks/paddlebillingcontroller.rb: hmac is the 64-character hex-encoded SHA-256 HMAC of "<ts>:<rawpost>" under the application's configured Paddle Billing signing secret. The comparison with h1 (the attacker-supplied h1= token from the Paddle-Signature header) uses Ruby's native String#==, which is implemented in MRI as rbstrequal and returns immediately on the first byte mismatch. How an attacker reaches this code Any Pay-using Rails application mounting Pay::Engine exposes POST /pay/webhooks/paddlebilling to the public internet (Paddle requires the endpoint to be reachable). The controller is configured by default in config/routes.rb when paddlebilling is enabled. The controller's beforeaction :verifysignature invokes validsignature? on every inbound request. An attacker repeatedly POSTs forged webhook payloads with Paddle-Signature: ts=<now>;h1=<guess> headers and measures the response time. The verifier returns early on the first mismatching byte of the hex digest; with a sufficient probe count per byte position, response-time distribution reveals when the prefix of <guess> matches the real hmac. A signature recovered through the oracle lets the attacker forge arbitrary Paddle Billing webhook deliveries. Proof of concept (microbenchmark) Local Ruby microbenchmark isolating the verifier comparison path: This isolates the same String#== path used by validsignature?. The static defect is verifiable by bundle show pay and reading line 38 of the controller. End-to-end reproduction against gem install pay --version 11.6.1 Minimal Rails 8 app mounting Pay::Engine with paddlebilling enabled: The static defect is verifiable by: After the fix is applied, the verifier uses ActiveSupport::SecurityUtils.securecompare, which compares all bytes regardless of mismatch position, and the timing oracle closes. Suggested fix Replace == with ActiveSupport::SecurityUtils.securecompare (Pay is a Rails engine, so ActiveSupport is always available). The bytesize-equality guard ensures secure_compare does not return early on a length mismatch (it falls back to == if lengths differ on older Rails versions). For the Paddle Billing signing format the hex tag is a fixed 64 chars. Credit Reported by tonghuaroot (https://github.com/tonghuaroot).
GHSA-MJGF-XJ26-9QF9 has a CVSS score of 7.4 (High). The vector is network-reachable, no privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment.
No fixed version is listed yet, so configuration controls and monitoring matter more in the interim.
rubygems
pay (<= 11.6.1)Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter instead of chasing every advisory.
Kodem's runtime-powered SCA identifies whether GHSA-MJGF-XJ26-9QF9 is reachable in your applications. Explore open-source security for your team.
See if GHSA-MJGF-XJ26-9QF9 is reachable in your applications. Get a demo
Already deployed Kodem? See GHSA-MJGF-XJ26-9QF9 in your environment →No fixed version is listed for GHSA-MJGF-XJ26-9QF9 yet.
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
GHSA-MJGF-XJ26-9QF9 is a high-severity security vulnerability in pay (rubygems), affecting versions <= 11.6.1. No fixed version is listed yet.
GHSA-MJGF-XJ26-9QF9 has a CVSS score of 7.4 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
pay (rubygems) versions <= 11.6.1 is affected.
No fixed version is listed for GHSA-MJGF-XJ26-9QF9 yet. Monitor the advisory for updates and apply mitigations in the interim.
Whether GHSA-MJGF-XJ26-9QF9 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.