Repository navigation
fix: stale issuer cache, XDR size limits, and missing Soroban param validation - #882
Merged
Just-Bamford merged 3 commits intoOct 21, 2026
Merged
Conversation
…cts (Sorokit#837) fetchStellarToml and getAnchorInfo both trusted a domain-keyed cache hit indefinitely within its TTL, with no check that the cached document still agrees with what the domain currently serves. A compromised or stale RPC response for a domain would be returned forever within the TTL window even if the real document had since changed its SIGNING_KEY or CURRENCIES issuer addresses, both of which control where payments get routed. Add detectStellarTomlConflict() to compare two stellar.toml documents on these security-critical fields. Both cache readers now re-verify a cache hit against a fresh fetch/resolve by default (verifyOnCacheHit, opt-out): on a mismatch, the cache is invalidated, a warning is logged with the conflict detail, and the fresh (not the stale) document is returned. If the re-verification fetch itself fails, the unverified cached value is served rather than failing the call outright.
…actionXdr (Sorokit#838) validateTransactionXdr parsed any input string with TransactionBuilder.fromXDR with no upper bound on length and no cap on the resulting operation count, making XDR decoding a CPU/memory DOS vector for an oversized or deeply structured payload. Reject XDR strings longer than maxXdrLength (default: 100_000 chars) before attempting to parse them at all, and reject a successfully-parsed transaction whose operation count exceeds maxOperationCount (default: 100, the protocol's own cap) before the per-operation validation loop runs. Both limits are configurable via ValidationRules for callers with different needs.
…invocation (Sorokit#843) prepareContractCall only validated argument count against cachedMetadata or contractAbi when one of them was explicitly supplied by the caller. Without either, a missing required parameter was silently passed through to simulation and submission, failing on-chain with an unhelpful error after fees were already paid. When neither is supplied, auto-discover the contract's methods from its deployed Wasm via the existing getContractMethods() (already cached/deduplicated), and validate against that instead. Also improve the mismatch error message to name the specific missing parameter(s) by declared name, not just report a count mismatch.
|
@davedumto Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
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.
Summary
This batch fixes three distinct validation/security gaps: a stellar.toml cache that trusted stale issuer data indefinitely, an XDR validator with no size or complexity bound, and a Soroban invocation builder that skipped required-parameter validation when no metadata was explicitly supplied.
closes #837
closes #838
closes #843
Changes
IssuerDiscoverycaches incorrect asset issuers from stale or compromised RPC responses — no cache invalidation on error #837 - IssuerDiscovery caches incorrect asset issuers from stale or compromised RPC responses:fetchStellarToml(src/integration/sep1Toml.ts) andgetAnchorInfo(src/integration/anchors.ts) both returned a domain-keyed cache hit immediately within its TTL, with no check that the cached document still agreed with what the domain currently serves. AddeddetectStellarTomlConflict(), which compares two stellar.toml documents on the two fields that actually control payment/auth routing: SIGNING_KEY (used for SEP-10 trust verification) and CURRENCIES issuer addresses. Both cache readers now re-verify a cache hit against a fresh fetch/resolve by default (newverifyOnCacheHitoption, defaults to true, opt-out available for latency-sensitive callers). On a detected mismatch, the stale cache entry is invalidated, a warning is logged with the conflict detail via an optionallogger, and the fresh (not the stale) document is returned. If the re-verification fetch itself fails (e.g. a transient network blip), the unverified cached value is served rather than failing the whole call. Found and fixed the same vulnerability in two places sincegetAnchorInforeads the same cache through a separate code path that bypassedfetchStellarTomlentirely.validateXDRdoes not check for malicious or oversized XDR envelopes — accepts invalid XDR silently #838 - validateXDR does not check for malicious or oversized XDR envelopes:validateTransactionXdr(src/transaction/validateTransactionXdr.ts) calledTransactionBuilder.fromXDRon an unbounded input string with no size or structural limit, making XDR decoding a CPU/memory DOS vector. Added a size check (maxXdrLength, default 100,000 base64 chars) that rejects oversized input before attempting to parse it at all, and an operation-count check (maxOperationCount, default 100, matching the protocol's own per-transaction cap) that rejects a successfully-parsed transaction with too many operations before the per-operation validation loop runs. Both limits are configurable viaValidationRules.buildSorobanInvocationdoes not validate that all required contract parameters are provided — missing params are silently ignored #843 - buildSorobanInvocation does not validate that all required contract parameters are provided:prepareContractCall(src/soroban/prepareCall.ts) only validated argument count when the caller explicitly suppliedcachedMetadataorcontractAbi- both are optional fields, and when neither was passed, zero parameter validation occurred, letting missing required params through to simulation and on-chain submission. When neither is supplied, the function now auto-discovers the contract's methods from its deployed Wasm via the existinggetContractMethods()(already cached and deduplicated against concurrent in-flight requests), and validates against that instead. Also improved the mismatch error message incontractMetadata.ts'svalidateCachedMetadatato name the specific missing parameter(s) by their declared name (e.g. "missing required parameter(s): to, amount") rather than only reporting a count mismatch, satisfying the issue's "clear error messages indicate which parameters are missing" criterion.#840 (ContractInvocationCache does not handle Soroban contract upgrades) was found already fully implemented on
mainatsrc/soroban/upgradeDetection.ts'sdetectContractUpgrade(): it hashes the deployed Wasm, compares against a cached hash, invalidates every related cache (schema/read/state/metadata) on a detected upgrade, and is already wired intocreateSorokitClient.tswith its own passing test suite. No change needed; left out of this batch.Test plan
src/integration/sep1Toml.test.tsandsrc/integration/anchors.test.ts: updated the pre-existing cache-hit tests to opt intoverifyOnCacheHit: falsewhere they're specifically testing basic TTL caching (that behavior is unchanged when opted out), added new tests for the new default (re-verifies on every hit), a conflict scenario (changed SIGNING_KEY triggers invalidation, warning, and returns the fresh data), and a re-verification-fetch-failure fallback.src/tests/validate-transaction-xdr-limits.test.ts(new): covers acceptance/rejection at and around the size boundary, the operation-count boundary, and confirms the size check runs before parsing (an oversized garbage string is reported as too-large, not as a parse error).src/tests/soroban.test.ts: added a new describe block covering the zero-cachedMetadata/zero-contractAbi auto-discovery path (single and multiple missing parameters, naming each one), the success path once all required params are supplied, and confirmedcachedMetadatastill short-circuits auto-discovery when explicitly provided. Updated two pre-existing tests whose assertions on the exact error message text needed to change to match the new, more specific wording (the underlying count-mismatch behavior is unchanged, only the message is more informative).npm run typecheck,npm run lint, andnpm run testall pass (191 test files, 3849 tests passed, 0 failed, 80 pre-existing skips).The SEP-1/anchor cache-verification fix changes a previously-undocumented performance characteristic (a cache hit no longer skips the network call by default) in exchange for closing the actual security gap the issue describes;
verifyOnCacheHit: falseis available for callers who need the old latency profile and already verify issuer identity another way.