SECURITY-10974: raise AuthError on a missing HMAC header, and stop shipping auth tokens in samples - #219
Draft
tomas-amaro wants to merge 2 commits into
Draft
Conversation
…is absent SECURITY-10974 / HackerOne 3688453 reported an HMAC disclosure in the PHP SDK. This SDK does not have that defect - MessageDigest.isEqual is constant-time and AuthError carries a fixed message - but verifying it surfaced a related one. NotificationHandler.toObject() called hash.getBytes() before checking hash for null. A request carrying no X-Riskified-Hmac-Sha256 header therefore produced a NullPointerException rather than AuthError, so a caller catching the documented exception mishandled precisely the unauthenticated case. It fails closed either way; the exception type is what was wrong. The .NET reference guards this explicitly, and now so does this one. Both operands of the comparison also now pin US_ASCII rather than inheriting the platform default charset. Both values are hex, so behaviour is unchanged today - it removes a latent dependency on deployment-host configuration. Adds NotificationHandlerTest, which did not exist. It covers the valid path, wrong and foreign signatures, the missing-header case that regressed here, and asserts AuthError discloses neither HMAC nor the request body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both notification server samples supplied the Riskified auth token as a source literal. These samples ship in a public repository, so the literals were disclosed credentials - and the auth token is exactly what an attacker needs to forge a notification that passes HMAC verification, which is the impact SECURITY-10974 describes. Both now resolve the token via SampleAuthToken.fromEnvironment(), which reads RISKIFIED_AUTH_TOKEN and fails with an actionable message when it is unset rather than starting a server that would reject every notification. The socket sample reads it once at startup and injects it into HTTPPOSTServer, mirroring how NotificationServlet already received its key, rather than re-reading the environment per connection. Note this does not remove the literals from history. If either was ever a live token it needs rotating; that is tracked separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Verification follow-up to SECURITY-10974 / HackerOne report 3688453, which reported an HMAC disclosure and webhook authentication bypass in the PHP SDK.
This SDK does not have the reported defect
Checked directly, and worth stating plainly so the negative result is on record:
MessageDigest.isEqualis constant-time — no timing side channelAuthErrorcarries a fixed message and discloses neither HMACNo equivalent of the reported bypass exists here. Verifying that surfaced two adjacent problems.
1. A missing HMAC header raised NullPointerException
NotificationHandler.toObject()calledhash.getBytes()before null-checkinghash. A request carrying noX-Riskified-Hmac-Sha256header therefore produced an NPE rather thanAuthError— so a caller catching the documented exception mishandled precisely the unauthenticated case.It failed closed either way; the exception type was the bug. Now rejected as
AuthError, matching the .NET reference which guards the missing header explicitly.Both operands also now pin
US_ASCIIrather than inheriting the platform default charset. Both values are hex, so behaviour is unchanged today — it removes a latent dependency on deployment-host configuration.Adds
NotificationHandlerTest, which did not exist. It covers the valid path, wrong and foreign signatures, the missing-header case, and assertsAuthErrordiscloses neither HMAC nor the request body.2. The notification samples carried auth tokens as source literals
SampleServer.javaandHTTPPOSTServer.javaeach supplied the Riskified auth token inline. These samples ship in a public repository, so those were disclosed credentials — and the auth token is exactly what an attacker needs to forge a notification that passes HMAC verification, which is the impact the report describes.Both now resolve it through
SampleAuthToken.fromEnvironment()(RISKIFIED_AUTH_TOKEN), failing with an actionable message when unset rather than starting a server that would reject every notification. The socket sample reads it once at startup and injects it, mirroring howNotificationServletalready received its key.Important
This does not remove the literals from git history. If either value was ever live it needs rotating, which is a separate action for whoever owns those credentials and is being tracked as its own ticket. Reviewers: please confirm their status rather than assuming they were dummies.
For the record, two similar-looking literals in
riskified-samplewere checked and deliberately not touched —orderClient/Client.java:218is a cart token andAccountActionClient.javauses aSessionDetailstoken, both synthetic fixture data alongsidebob.norman@hostmail.com, not credentials.Verification
Demonstrated RED → GREEN on the NPE:
missingHmacThrowsAuthErrorNotNullPointerExceptionfails against unpatchedmasterwith "Missing HMAC header must raise AuthError, not NullPointerException", and passes with the fix.mvn test— 63 pass (was 56; 7 new), 0 failures. Two errors inRiskifiedClientTestare pre-existing and environmental: it makes live API calls and needs a real sandbox token. Identical with and without this branch.mvn -DskipTests package— both modules build, including the sample whose constructor signature changed.🤖 Generated with Claude Code