This report contains vulnerability disclosure information for two bugs that were fixed in the xrpld 3.4.1 release: a Batch inner transaction wrapper validation error, and a payment engine XRP overflow error.
Date Reported: September 18, 2026
Affected Version(s): xrpld 3.3.0 and xrpld 3.4.0 (BatchV1_1 amendment, not activated on Mainnet at the time of finding)
The Batch feature (XLS-56) lets one account submit up to eight transactions as a single unit, with modes such as all-or-nothing and independent execution. It is a building block for atomic multi-step operations on the XRP Ledger. During re-verification of findings from the Sherlock Attackathon, a validation gap was identified in the code for this amendment. The spec says that each inner transaction in a Batch must be wrapped in a RawTransaction field. The server did not check this. A Batch transaction that wrapped an inner transaction in a different object field could be accepted, applied, and recorded in the ledger.
Further analysis found that the same gap could cause a consensus disagreement between server versions. Because the set of accepted wrapper fields was whatever object-type fields the server version defined, rather than a rule in the Batch transactor, two versions of the software that defined different fields could disagree on whether the same Batch transaction was valid. On a mixed network, this could stop ledger validation until enough validators upgraded.
The original Batch amendment was previously removed due to a separate vulnerability and later replaced with the BatchV1_1 amendment in xrpld 3.3.0. Neither amendment had been activated on Mainnet, so no Mainnet accounts or funds were affected.
No loss of funds, private key compromise, or consensus failure occurred. The inner transaction would still have to be fully valid in every case: correctly signed, balance-checked, and with the same on-chain result regardless of the wrapper name. No Mainnet transactions were processed incorrectly.
There were two risks if BatchV1_1 had activated without the fix:
The first risk was to network liveness. The xrpld 3.4.0 release removed six unused object-type fields that existed in 3.3.0. A 3.3.0 node would accept a Batch inner transaction wrapped in one of these fields. A 3.4.0 node could not deserialize it, so it would drop the transaction and build its ledger without it. Depending on the share of different versions in the network and the UNL, it's possible this could cause a halt in which no new ledgers would validate.
The second risk was on the data processing side. Any system that parses Batch transactions from the network and assumes the wrapper is RawTransaction would fail on a different field name. This includes the XRPL SDKs (xrpl.js, xrpl-py, xrpl4j), whose code assumes Batch inner transactions use RawTransaction. Because a signed transaction cannot be edited after validation without invalidating its signature, any malformed Batch transaction that reached Mainnet would stay in ledger history permanently and would require a permanent carve-out in all SDK type definitions.
The bug could only be triggered by someone who hand-crafted raw transaction binary. No SDK, client library, or normal workflow would produce this.
The issue was first reported as finding F48 in the Sherlock Attackathon and was rated low severity. It was believed to be fixed during work on the BatchV1_1 amendment. During a re-verification pass of the Attackathon findings, Denis Angell (XRPL Foundation) confirmed that the fix was incomplete and reported it to the RippleX engineering team.
During impact assessment, Mayukha Vadari (RippleX) found that the gap also created a consensus risk between server versions, because the accepted wrapper set depended on which fields each version defined. This raised the severity from low to a pre-activation blocker.
STObject::applyTemplateFromSField is a no-op when a field has no entry in InnerObjectFormats, and nothing else checked the field name of a RawTransactions array element. As a result, any template-less object field could wrap an inner transaction in place of RawTransaction, and the Batch transaction executed normally.
Fields that already had a template (Signer, BatchSigner, SignerEntry, Book) were rejected by their templates. Fields with no template (such as Memo, CreatedNode, ModifiedNode, DeletedNode, FinalFields, NewFields, PreviousFields, and TransactionMetaData) were not.
Because the only check was "is this a known object-type field," the set of accepted wrappers changed whenever a field was added or removed between versions. The normal defense for a new or removed field is a transactor check on that field. No transactor reads the wrapper field, so that defense did not apply here.
An attacker hand-crafts a Batch transaction in binary and wraps one or more inner transactions in a field other than RawTransaction. The attacker signs and submits the transaction through the normal path. The server accepts it, applies each inner transaction in full, and records the Batch transaction in the ledger with the non-canonical wrapper. Downstream parsers that expect RawTransaction then fail or stop processing on that transaction.
The attacker does not gain anything on-chain. The payoff is a permanently malformed record and the chance to break off-chain indexers, explorers, and SDKs.
In the consensus variant, the attacker wraps the inner transaction in a field that one server version knows and another does not (for example, EmittedTxn, which 3.3.0 knows and 3.4.0 does not). Nodes on the older version accept and relay the Batch transaction. Nodes on the newer version fail to deserialize it and drop it. If the older version holds a clear UNL majority, validators disagree on the ledger contents and validation stalls. At a 50/50 split or with a newer-version majority, the dispute thresholds drop the transaction and the attack must be retried each round. The attack costs only the transaction fee.
The protocol now returns temMALFORMED when any element of RawTransactions is wrapped in a field other than RawTransaction. The check is folded into the loop that Batch::preflight already runs over the inner transactions.
The check is gated behind a new amendment, fixBatchV1_2, which votes "yes" by default and became enabled today, 2026-10-09. The amendment is necessary to maintain consistency in transaction processing. Before activation, every node accepts a malformed Batch transaction as long as it uses a non-canonical wrapper they know about; after activation every node rejects it, and no node disagrees with another in between. STTx::buildBatchTxns runs during deserialization, where no amendment rules are in scope, so the check could not be placed there.
The fix is small and narrowly scoped. Now that fixBatchV1_2 is active, every node rejects any wrapper other than RawTransaction, regardless of which fields that version defines. This closes both the read-side issue and the cross-version consensus risk.
At the time the vulnerability was reported, the BatchV1_1 amendment held majority support and was scheduled to be enabled on 2026-09-29. Ripple and other validator operators switched their votes on the BatchV1_1 amendment to "no" to reset its activation clock. After the fix amendment received majority support, voters switched back to voting yes on BatchV1_1, ensuring that Batch functionality would become available only after the fix activated. This was the preferred path over activating BatchV1_1 with a known bug and shipping the fix afterward, which would have risked a network halt on a mixed-version UNL, permanent malformed records in Mainnet history, and a permanently worse developer experience in the SDKs. The fix and voting strategy was discussed with members of the XRPL Foundation, xrpld maintainers, and the UNL before the approach was adopted.
| Key Action | Timestamp | Description |
|---|---|---|
| Initial report | 2026 (Attackathon) | Finding F48 reported in the Sherlock Attackathon and rated low severity |
| Bug confirmed | Sep 18, 2026 | Denis Angell confirms the original fix was incomplete and opens public PR rippled #8248 |
| Impact assessed | Sep 21, 2026 | RippleX Engineering, DevEx, and partner engineering assess downstream impact on SDKs and indexers |
| Consensus risk identified | Sep 22, 2026 | Mayukha Vadari identifies the mixed-version consensus halt scenario; severity raised to blocker; decision made to pull BatchV1_1 votes and ship a fix first |
| Votes pulled | Sep 23–24, 2026 | Ripple and Vet pull their votes on BatchV1_1; activation clock resets |
| Fix created and reviewed | Sep 23–24, 2026 | Fix reviewed by Mayukha Vadari, Valentin Balaschenko, and Bart Thomee; merged into 3.4.1-rc3 |
| Fix released | Sep 25, 2026 | xrpld v3.4.1 released; fixBatchV1_2 enters its two-week activation period; BatchV1_1 regains majority support and enters its two-week activation period (roughly 30 minutes behind the fix) |
| Fix activated | Oct 9, 2026 | fixBatchV1_2 and BatchV1_1 activate on Mainnet |
| Report published | Oct 9, 2026 | Public vulnerability disclosure report (this document) published |
Date Reported: September 22, 2026
Affected Version(s): xrpld 3.4.0 and earlier
A researcher reported through the XRPL Bug Bounty program that an integer overflow in the payment engine could be exploited to create XRP from nothing. Using a set of specially crafted offers and a single payment, an attacker could mint new XRP in violation of the XRP Ledger's intended rules, and spend it like any other XRP.
The overflow could occur where the payment engine sums amounts from a single payment that consumes many offers from the order book. XRP balances are stored as integers with a fixed maximum. When a sum exceeds that maximum, it did not fail with an error, but wrapped around to a small number. The engine paid each offer owner their full amount individually but charged the buyer only the wrapped-around total. The difference was new XRP that should never have existed.
The XRP Ledger has a built-in invariant (safety check) that confirms no transaction creates XRP. However, that check summed balance changes the same way, so it wrapped around identically and detected nothing.
Although investigation suggests that the bug had been present since the current payment engine was written in 2015, it only surfaced now because a researcher participating in the XRPL Bug Bounty program identified and reported the vulnerability. The defense-in-depth model we described in June layers independent audits, public attackathons, AI-assisted red teaming, fuzz testing, and formal verification on top of the bug bounty. Findings like this class of long-dormant issue are what the program exists to produce.
The fix shipped in xrpld 3.4.1. We have found no evidence that this issue was exploited on any public network.
If it had been exploited, this would have been a critical issue. An attacker could have created spendable XRP far beyond the total supply in a single validated transaction. The minted XRP would then sit in ordinary accounts and could be moved, traded, or sent to exchanges.
The attack did not need a large starting balance. The large numbers in the attack are what the offer owners receive, not what the attacker has to put up front. All of the accounts involved could belong to the attacker. The real cost was a few hundred XRP in account and offer reserves, which are returned when the objects are removed, plus ordinary transaction fees.
The attack could not be triggered by accident. It required hundreds of offers priced in a way no real trader would use, followed by a payment built specifically to consume them all at once. No normal payment or trade would reach this code path with values large enough to cause the problem, because the total supply of XRP is far below the point where the numbers wrap around.
The issue was reported through the XRPL bug bounty program. The report rated the finding as Major. The RippleX engineering team reproduced it on a local standalone server and in the unit test framework, confirmed that the minted XRP could be spent in a follow-up payment, and raised the severity to critical.
When a payment goes through an order book, the engine takes every offer at the best available price in one pass and computes what the buyer owes across those offers. That sum used plain 64-bit integer addition with no overflow check.
Each individual offer amount was valid on its own. But a few hundred offers, each asking for a very large amount of XRP, add up to more than a 64-bit integer can hold. The total wrapped around to a tiny value. The engine still credited each offer owner the full amount of their offer, while charging the buyer only the wrapped total.
Two existing safety checks should have caught this, and neither did:
- The "no XRP created" invariant summed the net change across the whole transaction using the same kind of 64-bit counter. It wrapped around in the same way, so the net change appeared to be just the transaction fee, which is what a normal transaction looks like.
- The per-account balance check only fails if a single account holds more than the total XRP supply. Spreading the minted XRP across hundreds of accounts kept every individual balance under that limit.
The overflow has been present since the current payment engine was written, and the "no XRP created" invariant check, added two years later, was built on the same unchecked arithmetic. As far as we know, the bug went unnoticed for about a decade because no normal payment comes anywhere near a 64-bit overflow. Only a deliberately constructed set of offers can get there.
The attacker creates a few hundred accounts and has each one place an offer selling a tiny amount of some token for a very large amount of XRP. Each offer is valid on its own. The attacker then sends a single payment from another account that buys through all of those offers at once.
The payment engine pays each offer owner their full XRP amount, but the buyer is charged only a few hundred drops plus the fee, because the total wrapped around. The transaction succeeds, the safety checks pass, and the offer owners now hold more XRP than the buyer account spent. The attacker can then spend that XRP from any of those accounts.
The initial report also suggested that these offers could be placed in an order book to block other people's payments. In practice this doesn't work. To trigger the overflow, the offers have to be priced so badly that they always sit at the bottom of the book, below all real liquidity. Normal payments are filled before they reach them, and testing showed that payments and path finding return the same results with or without them.
Starting in xrpld 3.4.1, the payment engine checks for overflow when it sums amounts across offers. If the total would overflow, that part of the payment fails cleanly and the transaction ends with a normal "path dry" or "partial payment" result. Nothing is minted. The same check was added to the step that combines results from multiple payment paths.
The "no XRP created" safety check now uses a wider counter that cannot wrap around, so it would catch a similar problem in the future even if it came through a different code path. A few other places that add up balances across many accounts were hardened the same way as a precaution, although none of them could be reached by a real transaction today.
Changes to how the XRP Ledger processes transactions normally go through the amendment process, where the new rule ships in the software but stays off until after enough validators vote for it. It turns on only after it has kept the support of more than 80 percent of trusted validators for two weeks. Every server then switches to the new rule at the same ledger, so the network never disagrees about the result of a transaction. This is how the XRP Ledger changes its rules in a decentralized way: no single party can change the protocol, and validators decide together when a change takes effect.
This fix did not go through that process. It changes how certain payments are processed, but it took effect on each server as soon as that server upgraded to 3.4.1. This is the first time a change to transaction processing has deliberately shipped this way since the amendment system was introduced more than ten years ago.
The deciding factor was severity. The exploit was cheap, needed no special access, and could have minted and spent new XRP, damage that would be very hard to undo. xrpld is open source, so any release containing the fix also shows where the bug is. Under the amendment process, the fix would have been public for weeks before it took effect, including time for operators to upgrade, for validators to vote, and then the two-week activation period. For all of that time, the bug would have been both visible and still exploitable on Mainnet.
Skipping the amendment process carries its own risk. While the network is upgrading, servers on different versions follow different rules for these payments. If someone had tried the exploit during that window, upgraded servers would have rejected it while older servers accepted it, and the two groups would have disagreed about the resulting ledger. Servers that had not upgraded would have fallen out of sync until they did, or in the worst case, the network could halt due to disagreement on the next ledger version (similar to the consensus risk with Batch above). Normal transactions never reach this code path, so the exploit itself was the only transaction that could cause a disagreement. The fix only rejects transactions that should never have succeeded and does not change the result of any normal transaction. In this case, a network halt would actually be preferable to processing exploit transactions and creating an incorrect ledger state that would be hard to roll back. We judged a short upgrade window with this limited risk to be far safer than weeks of a publicly known, open exploit.
This only worked because validators acted quickly. Because of how serious the issue was, operators of validators on the default UNL upgraded to 3.4.1 right away, and more than 80 percent of them were running it on the day it was released, even though the source code for the fix was not yet published. A slower upgrade would have made the mixed-version risk much greater and the case for skipping the amendment process much weaker.
This case does not change how the XRP Ledger makes protocol changes. Changes to transaction processing still go through amendments, and skipping that process only makes sense for the most severe bugs, where the exploit is even worse than a network halt. This specific bug cleared that very high bar, as judged by the members of the XRPL Foundation, RippleX, and XRPL validators who agreed that the risk justified moving quickly. This decision was made in collaboration with the community and was not made lightly. We are grateful for the trust and efforts of everyone who took rapid action to apply the fix.
| Key Action | Timestamp | Description |
|---|---|---|
| Initial report | Sep 22, 2026 | Finding reported through the XRPL bug bounty program and rated Major |
| Bug confirmed | Sep 22, 2026 | RippleX Engineering reproduces the mint on a standalone server, confirms the minted XRP is spendable, and raises the severity to critical |
| Fix created and reviewed | Sep 22–23, 2026 | Fix developed privately and reviewed over several rounds; decision made to ship it as a direct change rather than an amendment |
| Fix merged | Sep 23, 2026 | Fix merged into the 3.4.1 release branch and included in 3.4.1-rc1 |
| Secondary impact assessed | Sep 24, 2026 | Order book "griefing" scenario analyzed and tested; found not to be a practical attack |
| Fix released | Sep 25, 2026 | xrpld v3.4.1 released |
| Network protected | Sep 25, 2026 | More than 80 percent of default UNL validators running 3.4.1 or later |
| Report published | Oct 9, 2026 | Public vulnerability disclosure report (this document) published |
Fixes for both vulnerabilities shipped in xrpld 3.4.1, released on 2026-09-25. The XRP overflow fix applies immediately upon upgrade, while the Batch Inner Transaction Wrapper vulnerability was fixed in the fixBatchV1_2 amendment, which became enabled on Mainnet on 2026-10-09. All server operators must upgrade to 3.4.1 (or newer) to maintain sync with the network. With the new amendment enabled, older servers are now amendment blocked.
Additionally, the source code for xrpld 3.4.1 has now been published.
The team plans to continue testing and hardening XRP Ledger core software based on these findings. In addition to defense-in-depth protections against similar types of errors, the team is adding a re-verification step to the release process: every security finding marked as fixed, regardless of source (including audits, bug bounties, and AI red-teaming) is re-tested against the release candidate, and is only closed when the release candidate passes a test that reproduces the original reported issue.
We'd like to thank the following for identifying and helping resolve these findings:
- Cayden Liao and Veria AI, for the original XRP overflow report and proof of concept submitted via the XRPL bug bounty program
- XRPL Foundation
- RippleX Engineering and RippleX Docs teams
- The Sherlock Attackathon participants and judges
To report security issues, see the xrpld Security Policy.