Oct 28, 2026·10 min read·9 visits
Electrum Bitcoin Wallet versions prior to 4.8.3 fail to verify cryptographic signatures on wallet-owned inputs for unconfirmed transactions received from Electrum servers. A malicious server can inject a spoofed, zero-fee mempool transaction spending the user's funds to an attacker address and induce the user to sign it via a Replace-By-Fee (RBF) fee bump.
A missing cryptographic signature verification vulnerability exists in Electrum Bitcoin Wallet versions prior to 4.8.3. In Electrum's Simplified Payment Verification (SPV) architecture, the client failed to validate input signatures on unconfirmed mempool transactions received from connected Electrum servers. A malicious Electrum server can exploit this flaw by intercepting a legitimate user broadcast, suppressing it, and returning a spoofed unconfirmed transaction with an invalid signature and zero fee that redirects the user's UTXOs to an attacker-controlled address. When the user initiates a Replace-By-Fee (RBF) action to accelerate the seemingly stuck transaction, Electrum signs the spoofed outputs with the user's private keys, resulting in the direct theft of funds.
Electrum is a widely deployed lightweight Bitcoin wallet that operates under the Simplified Payment Verification (SPV) client-server architecture. Rather than downloading and indexing the full Bitcoin blockchain locally, Electrum clients connect over TLS/TCP via JSON-RPC to external Electrum servers (such as ElectrumX, Fulcrum, or Electrs) to retrieve address histories, unspent transaction outputs (UTXOs), and unconfirmed mempool states.
In versions prior to 4.8.3, Electrum contains an improper verification of cryptographic signature vulnerability (CWE-347) within its transaction synchronization subsystem (AddressSynchronizer). While confirmed transactions are cryptographically verified against block header Merkle roots and proof-of-work difficulty chains, unconfirmed transactions residing in the mempool lack block inclusion proofs. Prior to the patch, Electrum accepted raw unconfirmed transactions from the connected server and inserted them directly into the local wallet database without verifying whether the transaction inputs possessed valid cryptographic signatures.
This architectural oversight exposes users connected to untrusted or compromised Electrum servers to transaction state manipulation. By returning crafted mempool data via the blockchain.scripthash.get_history and blockchain.transaction.get RPC methods, a malicious server can inject arbitrary unconfirmed transactions that purport to spend the user's own coins (is_mine inputs).
When combined with Bitcoin's Replace-By-Fee (BIP 125) mechanism, this state pollution enables an attacker to hijack legitimate outgoing payments. The attacker server suppresses a user's valid transaction broadcast, substitutes an invalid transaction paying the attacker with a zero or negligible fee, and relies on the user invoking the wallet's fee-bumping utility to generate a valid cryptographic signature over the attacker's outputs.
The root cause of CVE-2026-117601 lies in the asymmetry between how Electrum validates confirmed versus unconfirmed transactions in electrum/address_synchronizer.py. When a transaction is confirmed in a block, its validity is guaranteed by the cumulative proof-of-work of the block header and the corresponding Merkle branch proof verified by the SPV client. However, unconfirmed transactions exist only in the mempool, meaning the light client cannot rely on block headers to prove that a full node has validated the transaction's script execution and ECDSA/Schnorr signatures.
Prior to version 4.8.3, AddressSynchronizer.add_transaction() parsed raw transaction hex received from the server and immediately updated the internal UTXO and transaction history tables (self.db). The method did not inspect the scriptSig or witness fields of transaction inputs spending outputs owned by the wallet. Consequently, a transaction containing empty, malformed, or arbitrary signature bytes was treated as a legitimate pending spend in the wallet's state machine and user interface.
Once an invalid transaction (Tx2) spending a wallet-owned UTXO is recorded in self.db, it marks that specific outpoint as spent and displays the transaction in the client's History tab. Because the client trusts the server's representation of the mempool, the user sees an unconfirmed transaction with the parameters specified by the server, including its fee rate and output destinations.
The critical failure occurs when the user interacts with this polluted state through the Replace-By-Fee (RBF) workflow. When a user selects "Bump Fee" on an unconfirmed transaction, Electrum retrieves the stored transaction object from the local database, clones its inputs and outputs, adjusts the fee deduction or attaches additional inputs, and passes the resulting PartialTransaction to wallet.sign_transaction(). Because the wallet constructs the replacement transaction directly from the unverified Tx2 template, signing the replacement produces a fully valid, network-broadcastable transaction that sends the user's funds to the attacker's outputs.
The vulnerability was resolved in commit 90fe30822561e515a9d2d6e5a352cc07780999eb by introducing explicit cryptographic signature verification for all unconfirmed transaction inputs owned by the wallet, alongside a staging mechanism for transactions claimed to be mined.
First, the patch defines a dedicated exception InvalidTxSignature in electrum/util.py and integrates an input verification gate within AddressSynchronizer.add_transaction() in electrum/address_synchronizer.py. When a transaction is added, the synchronizer checks whether all inputs spending wallet-owned UTXOs contain valid signatures:
# electrum/address_synchronizer.py
# check signatures of inputs spending our coins
stored_tx = self.db.get_transaction(tx_hash)
is_new_witness = stored_tx is None or isinstance(stored_tx, PartialTransaction)
checked_tx = tx if is_new_witness else stored_tx # db keeps a complete tx
if not self._are_new_inputs_valid(checked_tx, range(len(tx.inputs())), is_new_witness=is_new_witness):
if not is_new_witness:
self.remove_transaction(tx_hash)
raise InvalidTxSignature()The helper method _are_new_inputs_valid() iterates over the transaction inputs, filters for non-coinbase inputs matching self.is_mine(addr), locates the parent transaction (prev_tx) in the local database, and invokes the self.verify_txins callback if the transaction is not already SPV-verified:
# electrum/address_synchronizer.py
def _are_new_inputs_valid(
self, tx: Transaction, txin_idxs: Iterable[int], *, prev_tx: Transaction = None, is_new_witness: bool,
) -> bool:
if self.verify_txins is None or not tx.is_complete():
return True
if isinstance(tx, PartialTransaction):
tx = Transaction(tx.serialize()) # input addresses from our db, not from the psbt
if self.db.is_in_verified_tx(tx.txid()):
return True # mined
prev_txs = {}
for txin_idx in txin_idxs:
txin = tx.inputs()[txin_idx]
if txin.is_coinbase_input():
continue
addr = self.get_txin_address(txin)
if not self.is_mine(addr):
continue
txin_prev_tx = prev_tx or self.db.get_transaction(txin.prevout.txid.hex())
if txin_prev_tx is None:
continue # checked when prev_tx is added
if not is_new_witness and txin.prevout.to_str() in dict(self.db.get_txi_addr(tx.txid(), addr)):
continue
prev_txs[txin_idx] = txin_prev_tx
return not prev_txs or self.verify_txins(tx, prev_txs)The core cryptographic check is implemented in electrum/wallet.py via _verify_txin_signatures(). This method converts the transaction into a PartialTransaction, attaches the parent output (witness_utxo) and local script descriptor to each wallet-owned input, extracts DER-encoded signatures from the witness and scriptSig, and verifies each signature against the wallet's public keys using ptx.verify_sig_for_txin():
# electrum/wallet.py
def _verify_txin_signatures(self, tx: Transaction, prev_txs: Mapping[int, Transaction]) -> bool:
if isinstance(tx, PartialTransaction):
tx = Transaction(tx.serialize())
ptx = PartialTransaction.from_tx(tx)
sighash_cache = SighashCache()
for txin_idx, prev_tx in prev_txs.items():
ptxin = ptx.inputs()[txin_idx]
prev_txout = prev_tx.outputs()[ptxin.prevout.out_idx]
ptxin.witness_utxo = prev_txout
ptxin.script_descriptor = self.get_script_descriptor_for_address(prev_txout.address)
if not ptxin.script_descriptor:
continue # cannot verify, e.g. watch-only imported address
txin = tx.inputs()[txin_idx]
try:
items = txin.witness_elements() + [x[1] for x in script_GetOp(txin.script_sig or b'')]
sigs = [x for x in items if x and x[0] == 0x30 and 9 <= len(x) <= 73]
pubkeys = ptxin.pubkeys
if len(pubkeys) > 1: # multisig: sigs are in script order
script = ptxin.witness_script or ptxin.redeem_script
pubkeys = [x[1] for x in script_GetOp(script) if x[1] in pubkeys]
except Exception:
return False
sigs_iter = iter(sigs)
sig = next(sigs_iter, None)
for pubkey in pubkeys:
if sig is None:
break
try:
is_valid = ptx.verify_sig_for_txin(
txin_index=txin_idx, pubkey_bytes=pubkey, sig=sig, sighash_cache=sighash_cache)
except Exception:
is_valid = False
if is_valid:
ptxin.sigs_ecdsa[pubkey] = sig
sig = next(sigs_iter, None)
try:
script_sig = Transaction.input_script(ptxin)
witness = Transaction.serialize_witness(ptxin)
except MissingSolutionPiece:
return False
if script_sig != (txin.script_sig or b'') or witness != (txin.witness or construct_witness([])):
return False
return TrueFinally, to prevent a malicious server from bypassing mempool signature checks by falsely claiming a spoofed transaction is already mined (tx_height > 0), the patch introduces a staging dictionary self._held_txs. If the server reports a positive block height for an unverified transaction, Electrum holds the transaction in self._held_txs[txid] and defers database insertion until its SPV Merkle proof is cryptographically verified against the block header chain.
> [!NOTE]
> Fix Completeness & Residual Surface: The patch specifically validates inputs where self.is_mine(addr) evaluates to True and where the wallet possesses a valid script_descriptor. Unconfirmed incoming transactions from external third parties (is_mine is False on inputs) and watch-only or watchtower wallets (verify_txins=None) are not cryptographically verified in the mempool prior to block confirmation.
Exploiting CVE-2026-117601 requires the attacker to operate or compromise an Electrum server to which the victim client connects. Because Electrum clients select public servers from peer-advertised lists by default when automatic server selection is enabled, an adversary can deploy rogue Electrum servers onto the network and await incoming client connections.
The attack sequence begins when the connected victim constructs, signs, and broadcasts a legitimate transaction (Tx1) intended for a merchant or external recipient. The victim client transmits the signed raw transaction hex to the malicious Electrum server via the blockchain.transaction.broadcast RPC method. Instead of relaying Tx1 to the Bitcoin peer-to-peer network, the malicious server intercepts and drops Tx1.
Immediately after suppressing Tx1, the server constructs a conflicting transaction (Tx2) that spends the exact same user UTXO(s) referenced in Tx1, but replaces the recipient output scripts with an address controlled by the attacker. The server sets the transaction fee of Tx2 to zero (or 1 sat/vB) so that it appears unable to confirm, populates the input witness or scriptSig with dummy bytes, and sends Tx2 back to the victim client via a blockchain.scripthash.subscribe notification and subsequent blockchain.transaction.get response.
Upon receiving Tx2, the vulnerable Electrum client inserts it into the local database without verifying the input signature. In the victim's GUI, the just-sent transaction appears in the History list as an unconfirmed transaction with an abnormally low fee. To resolve the stuck payment, the user right-clicks the transaction, selects "Bump Fee", adjusts the fee rate slider, and approves the signature prompt. Electrum constructs Tx3 using Tx2's attacker-controlled outputs, signs Tx3 using the user's private keys, and broadcasts Tx3 to the server, which immediately relays it to the Bitcoin network to complete the theft.
The primary impact of CVE-2026-117601 is the unauthorized, irreversible theft of cryptocurrency funds from affected wallets. Once the victim signs the Replace-By-Fee replacement transaction (Tx3), the resulting transaction is cryptographically valid according to Bitcoin consensus rules and cannot be revoked once broadcast and mined.
The vulnerability is assigned a CVSS v4.0 base score of 9.3 (Critical) by the assigning CNA (VulnCheck) with the vector CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N. Under the NVD CVSS v3.1 scoring model, it is rated 7.5 (High) with the vector CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H. The divergence in scoring reflects differing interpretations of the attack prerequisites: CVSS v3.1 accounts for the requirement of positioning a malicious Electrum server (AC:H) and relies on the user initiating the RBF workflow (UI:R).
Beyond direct fund loss via RBF poisoning, the lack of signature validation allows a malicious server to corrupt a client's local UTXO state and transaction history. By injecting fabricated unconfirmed transactions spending user coins, a server can cause denial of service to the wallet's coin selection algorithm, making valid UTXOs appear spent and preventing the user from constructing legitimate outgoing transactions until the wallet connects to an honest server and synchronizes.
The definitive remediation for CVE-2026-117601 is to upgrade the Electrum client to version 4.8.3 or later. Version 4.8.3 incorporates commit 90fe30822561e515a9d2d6e5a352cc07780999eb, which enforces cryptographic signature validation on all unconfirmed transaction inputs belonging to the wallet and stages purported mined transactions until SPV proof verification completes.
For users who cannot immediately upgrade or who wish to implement defense-in-depth against malicious Electrum server operators, the most effective operational mitigation is to disable automatic server selection and connect exclusively to a self-hosted, trusted Electrum server (such as ElectrumX, Fulcrum, or Electrs) backed by the user's own Bitcoin Core full node. This can be configured via the Electrum GUI under Tools -> Network by unchecking "Select server automatically" or via the command line using the --oneserver and --server arguments.
Additionally, users executing Replace-By-Fee (RBF) or Child-Pays-For-Parent (CPFP) operations should manually verify all destination addresses and output amounts prior to signing. When using hardware wallets (such as Coldcard, Trezor, or Ledger), users must inspect the recipient address displayed on the hardware device's physical screen during the fee-bump signing ceremony to ensure the outputs match the intended recipient rather than relying solely on the host computer's UI.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Electrum Bitcoin Wallet Electrum Technologies GmbH (spesmilo) | < 4.8.3 | 4.8.3 |
| Attribute | Detail |
|---|---|
| Vulnerability ID | CVE-2026-117601 / GHSA-6jp4-8v48-cc5v |
| CWE Classification | CWE-347: Improper Verification of Cryptographic Signature |
| CVSS v4.0 / v3.1 Score | 9.3 (Critical) / 7.5 (High) |
| EPSS Score | 0.18% (7.12th Percentile) |
| Attack Vector | Network (Malicious Electrum Server / SPV Protocol Manipulation) |
| Affected Versions | Electrum < 4.8.3 |
| Patched Version | Electrum 4.8.3 |
| Primary Impact | Theft of Bitcoin funds via Replace-By-Fee (RBF) transaction poisoning |
The product does not verify, or improperly verifies, the cryptographic signature for data.
Tornado versions prior to 6.6.0 contain an inefficient algorithmic complexity vulnerability in the HTTP header parser. When processing requests with obsolete line folding (obs-fold), the HTTPHeaders.parse_line method repeatedly concatenates immutable Python strings within a list structure. An unauthenticated remote attacker can send an HTTP request containing thousands of short header continuation lines to trigger quadratic O(N^2) CPU consumption on the main IOLoop thread, starving concurrent connections and resulting in a denial of service.
Tornado versions prior to 6.6.0 contain an open redirect vulnerability (CWE-601) in tornado.web.RedirectHandler. When route templates incorporate regular expression capture groups at the start of a path, directly after a fixed host without a trailing slash, or at the beginning of a destination template, unvalidated URL-decoded parameters allow attackers to manipulate the redirect scheme or network location. Because modern browsers normalize URLs according to the WHATWG specification—treating backslashes as forward slashes and stripping control characters—attackers can bypass naive prefix checks and redirect victims to arbitrary external domains.
Tornado versions prior to 6.6.0 contain an uncontrolled resource consumption vulnerability (CWE-400) in the HTTP/1.1 chunked transfer encoding parser. When processing incoming chunked payloads that have already buffered in the socket read buffer, Tornado parses chunks in a tight synchronous loop without yielding to the event loop. Furthermore, non-streaming request handlers and SimpleAsyncHTTPClient store each chunk as a discrete Python bytes object inside a list, inducing an approximately 20-fold memory overhead for 1-byte chunks.
CVE-2026-118254 (GHSA-r5wg-q8fc-p6w2) is a high-severity DOM-based Cross-Site Scripting (DOM XSS) vulnerability in the pagination module of Highcharts Grid (@highcharts/grid-lite and @highcharts/grid-pro). The flaw stems from the direct assignment of localization configuration strings (lang.pageInfo and lang.pageSizeLabel) and pagination UI labels to DOM elements via the innerHTML property instead of text-safe DOM APIs such as textContent or innerText. When an application passes attacker-controlled data into the Grid localization options with pagination enabled, arbitrary HTML and JavaScript execute automatically within the hosting document's origin context upon component rendering.
CVE-2026-118255 is a Cross-Site Scripting (XSS) vulnerability in Highcharts Grid and Highcharts Grid Pro. The flaw occurs because the credits component assigns the configuration-supplied credits.href property directly to an HTML anchor element without verifying the URI scheme against the internal Abstract Syntax Tree (AST) allowlist. An attacker who can influence the grid configuration can supply a javascript: URI, resulting in arbitrary JavaScript execution in the victim's browser session when the credits link is clicked.
The Model Context Protocol (MCP) TypeScript SDK contains an uncontrolled resource consumption vulnerability in its HTTP server transport implementations and middleware adapters. Affected versions fail to enforce upper bounds on incoming HTTP POST request body sizes and JSON-RPC 2.0 batch array lengths. In addition, middleware wrappers for Express and Hono parse request bodies into memory prior to validating Host and Origin headers. Unauthenticated remote attackers can exploit these flaws to trigger Out-Of-Memory (OOM) process crashes or monopolize the single-threaded Node.js event loop.