We are releasing Zebra 4.4.0 today. This release contains fixes for multiple security vulnerabilities, including several consensus-critical issues, and we strongly encourage all node operators to upgrade immediately.
Security Advisories
GHSA-28xj-328h-72vm: Permanent Block Discovery Halt via Gossip Queue Saturation + Syncer Poisoning
Zebra’s block discovery pipeline contained a composite denial-of-service vulnerability that allowed a remote attacker to permanently halt all new block discovery on a targeted node. The attack exploited three independent weaknesses in the gossip, syncer, and download subsystems — all exercisable from a single TCP connection — to create a monotonically growing block deficit that never self-heals.
The gossip path was vulnerable because there was no per-connection rate limit on inv messages. A single connection could send enough sequential inv messages with fake block hashes to fill the entire gossip download queue in under a millisecond, and the FullQueue return value was silently ignored. The syncer backup path could be degraded by responding with empty inv to FindBlocks requests (valid protocol, zero misbehavior penalty) and with NotFound to block download requests. The attack produced zero misbehavior score, zero bans, and zero disconnections.
The fix drops connections that send empty responses to FindBlocks and FindHeaders messages, preventing attackers from degrading the syncer path without consequence.
GHSA-jv4h-j224-23cc: Consensus Block Sigops Undercount
Zebra’s block validator undercounted transparent signature operations against the 20,000-sigop block limit, allowing it to accept blocks that zcashd rejects. Two distinct undercounts were identified:
- Coinbase sigops.
zcashdincludes the coinbase input’sscriptSigin its sigop count. Zebra skipped the coinbase input entirely, and up to ~98 sigops could be hidden inside the coinbasescriptSigwithout being charged against the block limit. - Aggregate P2SH sigops.
zcashdparses each P2SH input’s redeem script and sums those sigops into the block-wide total. Zebra computed P2SH sigops only in mempool transactions and never accumulated them during block validation. A block whose aggregate redeem-script sigops exceed 20,000 would be accepted by Zebra and rejected byzcashd.
The fix adds coinbase scriptSig to the legacy sigop iterator, introduces a shared p2sh_sigop_count function that mirrors zcashd‘s GetP2SHSigOpCount, and accumulates both legacy and P2SH sigops in the block-validation path.
Thanks to sangsoo-osec for finding and reporting this issue.
GHSA-gq4h-3grw-2rhv: Consensus Divergence in Transparent Sighash Hash-Type Handling due to Stale Buffer
The fix for a previous sighash advisory (GHSA-8m29-fpq5-89jj) introduced a separate issue due to insufficient error handling when the sighash hash type is invalid. Zebra’s transparent script verification calls Bitcoin Script verification code in C++ through a foreign function interface (FFI), with a Rust callback that computes the sighash. The previous fix correctly returned None for undefined hash types, but the FFI bridge only writes to the C++ sighash buffer when the callback returns Some, and the C++ checker reads that buffer unconditionally — so the failure signal was lost.
An attacker could exploit this by constructing a transparent output spent by a script that runs a valid OP_CHECKSIGVERIFY immediately before an OP_CHECKSIG with an undefined hash type. The first opcode primes the C++ sighash buffer with a valid digest; the second causes Zebra’s callback to return None while the C++ checker verifies the invalid signature against the stale digest. Zebra would accept the spend while zcashd would reject it, creating a consensus split.
The fix fills the sighash output buffer with random bytes on validation failure, which makes signature verification fail as expected with overwhelming probability. This is a workaround that avoids a breaking release of the zcash_script crate; a future release will propagate the error correctly for a direct fix.
Thanks to sangsoo-osec for finding and reporting this issue.
GHSA-cwfq-rfcr-8hmp: Transparent SIGHASH_SINGLE Corresponding-Output Handling
A divergence between Zebra and zcashd was reported in how SIGHASH_SINGLE is handled for V5 transparent transactions when an input index has no corresponding output. In zcashd, this case triggers a script failure under ZIP-244; in Zebra, the sighash engine computed a digest for the missing-output case rather than failing.
We do not consider this a practically exploitable security issue: the scenario requires an attacker to both submit a malformed transaction through Zebra’s mempool and have an external miner include it in a block — a chain of events with no economic incentive that has never occurred on mainnet. Nonetheless, we have added an explicit pre-check that rejects these transactions, matching zcashd‘s behavior and closing the divergence. (#10510)
Thanks to sangsoo-osec and defuse for finding and reporting this issue.
GHSA-438q-jx8f-cccv: Allocation Amplification in Inbound Network Deserializers
Several inbound deserialization paths in Zebra allocated buffers sized against generic transport or block-size ceilings before the tighter protocol or consensus limits were enforced. An unauthenticated or post-handshake peer could force the node to preallocate and parse orders of magnitude more data than the protocol intended, amplifying per-message memory and parse cost. Four vectors were identified:
headersmessage receive cap. TheCountedHeadervector was deserialized via the genericTrustedPreallocatepath, allowing up to ~1,409 entries per message. The protocol ceiling of 160 was only enforced on the send side, creating an ~8.8× preallocation gap on receive. This was reachable before the version handshake completed.- Equihash solution length. The equihash solution was decoded as a generic
Vecand only checked against the consensus size (1,344 bytes on mainnet) afterwards. A single fixed-size header field could be inflated to nearly the full block-size ceiling before rejection. - Sapling spend vectors in coinbase transactions. V5
spend_prefixesand V4shielded_spendswere allocated with block-size-derived ceilings (~5,681 / ~5,208 entries) before the consensus rule that coinbase transactions have zero Sapling spends was enforced in the verifier. The fix reads the Sapling spend count and rejects coinbase transactions with any spends before allocating the spend vector. - Coinbase script bytes. The coinbase script was read as a generic
Vecup to the message-size cap before enforcing the consensus rule that coinbase scripts are between 2 and 100 bytes.
Each individual case is bounded by the 2 MiB transport ceiling or the block-size cap, so no single message causes unbounded allocation, but the cumulative gap between intended and actual limits is significant and stackable across concurrent connections. All four vectors have been fixed by capping allocations to the protocol-specified limits before deserialization begins.
Thanks to Zk-nd3r for finding and reporting these issues.
Security Improvements
Indexer gRPC Server Resource Limits
The indexer gRPC server had no connection or subscription cap, and used a 4,000-message per-stream queue with full block payloads. The fix switches indexer streams to use try_send, which drops slow consumers instead of backpressuring the server, and reduces the per-stream buffer from 4,000 to 64 messages. Note: gRPC subscribers that fall behind are now dropped instead of backpressuring the server. Well-behaved clients are unaffected.
RPC Request Body Size Limit
The RPC compatibility middleware read, parsed, and re-serialized the full HTTP request body before applying any local size cap. The fix bounds the HTTP request body via http_body_util::Limited before allocation, with the limit derived from MAX_BLOCK_BYTES to accommodate submitblock.
getrawtransaction Block Hash Validation Race
A TOCTOU race condition existed in getrawtransaction between block hash validation and txid lookup during chain forks, which could return incorrect results. The fix reuses the caller-provided block hash and its best-chain flag from the initial query, avoiding a third state lookup that could race with a reorg.
RPC Auth Cookie File Permissions
The RPC authentication cookie file was written without explicit 0600 permissions, potentially allowing other local users to read it on multi-user systems. The fix sets restrictive file permissions on the cookie file at creation time on Unix, and also rejects symlinks at the cookie path.
Thanks to Zk-nd3r for finding and reporting the four issues above.
New Features
nTx Field in getblock Response
The getblock RPC response now includes the nTx field, reporting the number of transactions in a block. This improves compatibility with downstream tooling that expects this field. (#10498)
Criterion Benchmark Suite
A Criterion-based benchmark suite and CI workflow have been added, giving the team a systematic way to track and catch performance regressions across releases. (#10444)
Sentry Environment Alignment
Sentry CI metadata and environment tagging have been aligned, improving how crash and error reports are categorized across development, staging, and production deployments. (#10490)
Bug Fixes
getrawtransaction Confirmations
A bug in the getrawtransaction RPC that returned incorrect confirmation counts has been fixed. (#10507)
Dependency Updates
librustzcash
The entire librustzcash dependency stack has been migrated to the latest version. This replaces the yanked core2 crate with corez 0.1.1, clearing the RUSTSEC-2026-0105 advisory. (#10522)
Other Changes
- Remaining Groth16 proof verification abstractions have been removed from
zebra-consensus, completing the cleanup after the Sapling verifier migration. (#10436) cargo-vetconfiguration has been fixed to run correctly after releases. (#10504)
Upgrading
We strongly recommend all Zebra node operators upgrade to 4.4.0 as soon as possible, particularly due to the consensus vulnerabilities described above. There are no known workarounds — upgrading is the only way to ensure your node remains on the correct chain and is protected against the issues listed in this release. You can find the release on GitHub.
Thank You to Our Contributors
This release was made possible by the work of @alchemydc, @arya2, @conradoplg, @daira, @gustavovalverde, @mpguerra, @oxarbitrage, @schell, and @upbqdn. Thank you for your continued contributions to Zebra.
Zebra is the Zcash Foundation’s independent, Rust-based implementation of the Zcash protocol. Learn more at github.com/ZcashFoundation/zebra.
