[ANCHOR-1268]: SEP-10 authentication bypass and owner lockout via sign-extended threshold/weight parsing on the RPC ledger backend - #1987
Open
amandagonsalves wants to merge 2 commits into
Open
Conversation
* fix sign-extension issue when parsing on-chain uint8 threshold and master-weight bytes * update `stellarrpc.java` to properly interpret unsigned byte values as positive integers * add new `sep10rpcthresholdsignextensiontest.kt` to validate the fix
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes RPC ledger parsing of unsigned threshold and master-weight bytes, preventing SEP-10 authentication bypasses and owner lockouts.
Changes:
- Converts XDR threshold bytes to unsigned integers before use.
- Adds regression coverage for bypass, lockout, boundary, and control cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
core/src/main/java/org/stellar/anchor/ledger/StellarRpc.java |
Correctly parses unsigned XDR threshold bytes. |
core/src/test/kotlin/org/stellar/anchor/ledger/Sep10RpcThresholdSignExtensionTest.kt |
Tests parsing and SEP-10 authentication behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
JiahuiWho
approved these changes
Aug 3, 2026
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.
Description
StellarRpc.getAccount()reads an account'smed_thresholdand master-key weight from the on-chain XDRThresholdsfield, which the Java SDK models as a signedbyte[]even though the wire type isuint8. The code widened each byte with a plain(int)/(long)cast, which in Java sign-extends: any byte in[128,255]becomes negative (200→-56,255→-1).Sep10Servicethen compares that value against the summed SEP-10 challenge signer weights via the SDK'sSep10Challenge.verifyChallengeTransactionThreshold, which does a signedif_icmpge. A negative threshold makes that comparison unconditionally true, so on accounts withmed_threshold >= 128(institutional custody, issuers, or any account using per-signer weights like50/100/255instead of1/2/3), a single listed signer of any weight — including a master key the owner deliberately demoted to weight 0 after a compromise — is enough to obtain a valid SEP-10 JWT for that account. The same cast applied to the master weight produces the mirror-image bug: any single-signer account whose owner raised their master weight to>= 128is permanently locked out, with the anchor returning a negative weight in the rejection message.This only affects the RPC ledger backend (
stellar_network.type: rpc); Horizon is unaffected becauseAccountResponse.Thresholdsis already deserialized from JSON into correctly-rangedints. RPC is not the schema default, but it is what SDF's own shipped reference profile sets and is required for Soroban/SEP-45 support, so this isn't an edge-case configuration.The fix is a single masking helper (
b & 0xFF), applied at all four points the raw bytes are widened. No other change is needed: once the parsed value is guaranteed non-negative by construction, both the bypass and the lockout resolve as a direct consequence, and no defense-in-depth range check or signer-list change is required.Changes
StellarRpc.getAccount: addedunsignedByte(byte)(b & 0xFF) and used it for the three threshold bytes and the master-key signer weight, replacing the sign-extending(int)/(long)casts.Sep10RpcThresholdSignExtensionTest.kt(new): wires the realStellarRpc+Sep10Service+ pinned Stellar SDK together (only the Soroban-RPC network call itself is stubbed, same boundaryStellarRpcTestalready stubs) and runs real, freshly-signed SEP-10 challenges through them, so the SDK's actual threshold-comparison bytecode decides pass/fail. Covers: the parsing defect in isolation, a below-threshold co-signer bypass, a revoked weight-0 master-key bypass, the owner-lockout case, a control case proving the check itself works, and a 7-value parameterized sweep (0, 1, 127, 128, 200, 254, 255) asserting nouint8byte value ever parses as negative.Acceptance Criteria
med_threshold = 200rejects a SEP-10 challenge signed only by a single weight-100 co-signer.med_threshold = 100authenticates successfully with its own master key (no lockout).StellarRpc.getAccount()returns non-negativelow/medium/highthresholds and a non-negative master-signer weight for every possible on-chain byte value (0–255).<128) are unaffected.Context
HackerOne #3893785
Testing
./gradlew :core:test --tests "org.stellar.anchor.ledger.Sep10RpcThresholdSignExtensionTest"./gradlew :core:test --tests "org.stellar.anchor.ledger.StellarRpcTest"(no regressions in existing coverage)./gradlew :core:test :platform:test :core:spotlessCheck(full suite, confirms no regressions elsewhere)Documentation
N/A
Known limitations
N/A