The Bound Was in the Specification
The Bound Was in the Specification
In 72 hours: an unbounded buffer in Eclair, a global shutdown of LNbits, and a proposal to delete the Solana feature that cost $270 million. Four failures, one missing line.
On Sunday at 12:33 UTC the LNbits project told its operators to disable the Paywall extension. At 13:56 UTC it told them to shut everything down. By then an operator of a Telegram tipping bot had already watched 2,285,545 satoshis leave an instance he runs, taken between 09:22 and 14:00 UTC by accounts that had been created minutes earlier. The project’s founder expected patches within 24 to 48 hours. There is still no tagged release: the newest is v1.6.2 from 18 September. But the fix is already visible on a public branch, and its commit messages name a mechanism the project has not yet described anywhere else.
That was the loud one. The quiet one is more instructive.
Also on Sunday, on the Bitcoin developer forum Delving Bitcoin, ACINQ published a disclosure with the flat tone of a mechanic’s invoice: any peer that could open a TCP connection to an Eclair 0.14.0 node could make it buffer commitment signatures until the process ran out of memory. No channel required. No funds taken directly. Fixed in 0.14.1. Read the write-up and you find that the specification had already forbidden exactly this: BOLT 2 says a receiving node MUST ignore a start_batch whose batch_size is not strictly greater than 1, and caps the batch at 20. Eclair’s code checked the ceiling and forgot the floor.
And on Thursday, 5 November, an engineer at Anza filed SIMD-0723, a proposal to delete Solana’s durable nonces outright: the feature that let an attacker pre-sign transactions, let them sit for more than a week, and then empty Drift of at least $270 million in April. The proposal does not patch a check. It removes the capability. The earliest it can take effect is 5 May 2027.

Three incidents, three nouns, one shape. In each case the invariant existed, was written down, and was not enforced where the state accumulated. The scarce good in this industry is no longer code. It is enforcement.
A zero where a one belongs
Start with the Eclair bug, because it is the cleanest specimen.
A Lightning channel normally has one funding transaction, so one channel update needs one commitment_signed message. A splice complicates that: while splice transactions are unconfirmed, neither side knows which candidate will confirm, so an update must be signed for each of them. With N pending splices the sender emits N + 1 commitment signatures, and the receiver has to treat that pile as a single logical update. start_batch announces the pile: it carries the channel id, a 16-bit batch_size, and the message type that will follow: 132, commitment_signed. Eclair holds those messages in PeerConnection until the announced count arrives, then hands them to the channel as one CommitSigBatch.
Because the receiver queues them, BOLT 2 puts a hard ceiling of 20 on the count, “to protect against excessive queuing that could be abused to DoS receiving nodes.” It also sets a floor, and that floor is the bug. Eclair’s PeerConnection read, in condensed form from the disclosure:
} else if (msg.batchSize > 20) { ... } else { ... Some(PendingCommitSigBatch(msg.channelId, msg.batchSize, Nil)) }
A batch_size of 0 passed. Then each arriving commitment_signed for that channel id was appended to the pending list, and the completion test compared the received count against 0. Since the count starts at 1 after the first message, it never equals 0, the batch never completes, and the list grows as fast as the attacker can feed it. Only 0 triggers this; a batch of 1 completes immediately, which is presumably why nobody noticed.
Four details turn a logic slip into a remote kill switch. PeerConnection never checks that the channel id belongs to a real channel, so the attacker needs no channel and no deposit. Nothing at that layer validates signatures, so each message can approach the 65,535-byte frame limit. Each message is acknowledged to the transport before it is buffered, so there is no backpressure. Memory grows at line rate. And the buffer lives as long as the connection: the attacker only has to answer pings and avoid sending anything that flushes the batch.
The node’s channel-count limits do not help. max-no-channels caps channel-less inbound connections at 64, which is a limit on connections, not on the memory each one may consume. One connection is enough.
The disclosure is careful about impact, and so should we be: the bug gives no direct access to funds. The risk is indirect and worse in kind. A Lightning node has to be online to react to a revoked commitment and to resolve HTLCs before they expire. A node that can be crashed at will, repeatedly, by anyone who can reach its port, is a node that can be kept offline during exactly the window in which being offline costs money. That is the difference between a denial of service and a denial of custody.
The fix is one condition, rejecting batch_size <= 1 as the spec requires, and it shipped in 0.14.1. Reading the release history since then is its own argument: 0.14.3 on 14 September, 0.14.4 on 15 October, 0.14.5 on 20 October, 0.14.6 on 27 October. Four releases in six weeks, every one described as security hardening, several crediting named external researchers and one crediting “project Loupe.” Maintenance of a Lightning node in 2026 is not an annual chore. It is a subscription.
The three-link custodial chain
LNbits is the opposite case: not a protocol edge case but an application that hands out accounts.
What is confirmed, as of Monday morning: the project ordered every operator offline at about 13:56 UTC on 8 November, after a wave of attacks. The hosted service at my.lnbits.com was suspended with the rest. The only loss documented so far is 2,285,545 satoshis from the instance behind a Telegram tipping bot, whose operator pledged to reimburse users personally and confirmed that funds were frozen pending a patch. The exploit mechanism has not been published. Unauthorized account creation followed by unauthorized withdrawals is all that is established, and whether the flaw sits in the account layer or in an extension is not yet public.
Since the shutdown, three commits authored as “Arc”, the project’s founder, have appeared on a branch called fix_ssrf, the last of them at 00:07 UTC on 9 November. Read together they describe the shape of the repair rather than the exploit: a new module whose docstring says “Reject user-controlled requests back to the deployment itself”; a second module that resolves a hostname and inspects the resulting addresses before a request is allowed out; ninety lines changed in the LNURL service; and, in a separate commit, “default new account creation false + push users toward invitations.” Five hundred and sixty-four lines of new tests come with them. The project has published no post-mortem, so the link between those commits and the theft is my inference from timing and content, not its statement. The direction, though, is not ambiguous: user-supplied URLs that make the server call itself, and account creation that was open by default.
The class of bug is not new to the project. Its public advisory list carries an entry from April 2025 titled “Server-Side Request Forgery via LNURL Authentication Callback in LNbits Lightning Network Payment System.” Nineteen months later the same category forced every instance offline. That is the maintenance argument in one line: a fix for a class of bug is not a fix for the class.
At 09:31 CET this morning bitcoin traded at $74,125 by my own reading of market data, which puts those satoshis at roughly $1,694. That figure is my multiplication, not the operator’s. Set that against the $270 million in the Solana case and the proportion is almost comic. Then notice who paid: not a foundation, not a venture-funded protocol, but one person running a tipping bot, reimbursing strangers out of his own pocket. The losses in this class of failure land on whoever is closest to the users and furthest from the auditors.
The structural point that TFTC’s write-up makes well: a user of a Telegram tipping bot believes they are trusting a bot. They are in fact trusting the bot operator, who trusts an LNbits instance, which trusts the LNbits software stack, which trusts a node backend. Four links, one of them a hobby deployment with account creation open to the internet.
There is an irony in the project’s own release notes. LNbits v1.6.0, published on 2 September, introduced WASM extensions and explained why: “With more people building software with LLMs, LNbits v1.6.0 proudly introduces WASM extensions! WASM extensions are tightly sandboxed, meaning they can be vibe coded and shared without the security risks associated with traditional extensions.” The project saw the coming flood of machine-written extension code and built the sandbox for it. Ten weeks later an attack ran through account creation and outbound requests, which is the core, not an extension. Sandboxing the edges does not help if the core will fetch any URL it is handed and open an account for anyone who asks.
I am not claiming the two are connected; the post-mortem will say. I am claiming that the layer people worried about was not the layer that failed, which is the normal condition of security work and the reason “we hardened X” is never an answer to “is this safe.”
Deleting the feature is also a fix
Solana’s durable nonces are the third specimen, and the most expensive.
Every ordinary Solana transaction carries a recent blockhash. With the network running 200-millisecond slots, that stamp expires in about thirty seconds. That is a feature: a signature is bound to a moment, so a signed transaction cannot be held and replayed at the signer’s expense. Custodians and exchanges, though, sign on air-gapped machines, where moving a signature to the network takes longer than thirty seconds. Durable nonces replace the blockhash with a stamp that never goes stale. The binding to time is deliberately removed so that the operation is possible at all.
In April, attackers got approvals signed in advance, let the transactions sit for more than a week, long enough that nobody was watching the specific approval any more, and used them to drain Drift of at least $270 million on 1 April. Drift later attributed the operation to a North Korean state-affiliated group.
SIMD-0723, filed on 5 November by Gabe Rodriguez of Anza, chooses removal over repair: “After activation, every transaction needs a recent blockhash.” It does not rest its case on Drift. It rests it on years of bugs, including a 2022 incident in which a failed transaction was processed through both the blockhash and the nonce paths and halted mainnet for about 4.5 hours. The proposal sets no activation before 5 May 2027 and gives at least six months to migrate, counted from publication; the foundation’s own deprecation page still says “No date set,” which could push the cutoff later.
The usage numbers explain why this was never going to be fixed by education. Durable nonce transactions were about 3% of Solana’s traffic in one June measuring period but paid 30% of all priority fees: a small, fee-insensitive, latency-sensitive clientele. Just 555 accounts sent 97.5% of them, according to data from researcher Max Resnick cited in the proposal discussion. That is the shape of a feature that exists for a handful of institutions, gets maintained because they pay for it, and carries systemic risk for everyone else.

The mechanical lesson transfers to any system, including Bitcoin’s: a capability that unbinds a signature from time is not a convenience feature. It is a standing option for whoever holds the signature. And the correct response, once that is understood, is not a warning in the documentation but the deletion of the option, with a migration window measured in months rather than days.
The quiet fourth case
The odd one out is not a security incident at all. On 8 November a support person at Gem Wallet posted to Delving Bitcoin with a mundane complaint: users copy a Taproot receive address, try to withdraw from an exchange, and get “invalid address,” because the sending service will not accept bc1p. From the user’s side, the wallet produced a broken address. The thread asks wallet teams whether they default to Taproot or SegWit for receiving, and how they signal the difference.
It is one post and one participant. It is not a survey, and I will not dress it up as one. But it names the same failure in a different register: a default that is technically correct and operationally wrong. The invariant here is not a queue bound. It is the installed base: what the counterparty will accept. A wallet that ships the newest address format as its default has optimized for the protocol and ignored the network’s actual state, and the person who discovers the mismatch is a user who cannot move their money.
Three of this week’s four stories are about a bound that was missing where state grew. The fourth is about a default chosen without measuring the other side. Both are failures of the same discipline: knowing what the system around you will actually do, as opposed to what it is specified to do.

Who checks the check
Which raises the uncomfortable question of 2026: if the specification already contained the answer, why did it take a disclosure to apply it?
The answer is partly arithmetic. A study by Faros, cited in Latent Space’s interview with Stanford lecturer and Monaco AI lead Mihail Eric, found that teams with high AI adoption merged 98% more pull requests while their pull request review time was 91% longer. Code generation got cheap; the queue in front of the only people who can approve it got longer. Eric’s own evidence is a curriculum that had to be rewritten mid-course: the first run of Stanford’s CS146S had nine assignments and a capstone, and by the next run students could one-shot much of it. When he asked a room of Stanford sophomores and juniors how they coded, every one he surveyed was already using an agent.
So the assignment changed. Students now contribute to an open-source project, and the maintainer reviews their patch. “You can ask an agent to implement a feature in an afternoon,” Eric says in the interview. “You still have to find out whether the maintainer wants it, why the code behaves the way it does, and what else your patch might break.”
That is the same skill the Eclair bug demanded, in miniature: not writing the condition, but knowing that the condition belonged there and that its absence had a consequence. Simon Willison’s weekend experiment shows how fast the generation side has become and how little it settles. He asked a model to build a SQLite extension in C and in Rust: the Rust builds came out at 321 KB and 298 KB, the C build at 14 KB, and the Rust version was faster. The explanation (Rust’s float parsing is twice as fast as C’s strtod) led to a second prompt, and the model wrote a C parser using Clinger’s fast path that beat both. Measured over 2 million rows, cost per row on mixed data fell from 67 nanoseconds in the first C version to 19 in the second, against 46 and 41 for the two Rust variants. Hours of expert work compressed into two prompts, and the only step a human still had to own was deciding which number to believe.
The margin that pays for maintenance
Now the part that connects this to money, because maintenance is a line item and line items get cut.
The September inflation data, which I pulled from the Bureau of Labor Statistics this morning rather than taking anyone’s summary: the producer price index for final demand stood at 159.613 in September 2026 against 150.100 a year earlier, up 6.34%; the consumer price index for all urban consumers stood at 336.940 against 324.800, up 3.74%. The gap is 2.60 percentage points, and producer prices are rising about 1.7 times as fast as consumer prices. Both figures and the gap are my own division of the published index levels; the index values are the BLS’s.
The caveat matters: PPI and CPI are different baskets with different weights and different treatments of housing and services, so the gap is a proxy for margin pressure rather than a measurement of it. But the direction is not subtle. When the prices businesses receive rise 6.3% and the prices households pay rise 3.7%, something in between is absorbing 2.6 points, and the thing in between is a corporate income statement. The first budgets to be trimmed in that situation are the ones with no revenue attached: audits, dependency upgrades, the engineer who reads release notes.
Against that, the capital being committed elsewhere. SpaceX is asking a Texas city for a property tax abatement on a $24 billion project at Starbase: a super-compute facility with its own gas-fired plant, 35 turbines, 748 megawatts. The same company holds 18,712 bitcoin, worth about $1.39 billion at the time of the report — roughly one seventeenth of the project’s cost, by CoinDesk’s framing. One company can raise $24 billion for compute and 8% of that is more than its entire bitcoin treasury.
I am not making the lazy point that the money is in the wrong place. Compute at that scale is a bet on a real productivity shift, and the people placing it are not fools. The point is narrower and harder to argue with: the capital available for building new capability is enormous, and the capital available for keeping existing systems correct is not, because correctness has no revenue line. The gap between those two funding environments is where these incidents live.
The strongest counter-argument
The honest version of the sceptical case: this is four unrelated news items in one news cycle, selected because they are dramatic, and the systems corrected themselves within days. Eclair shipped a one-condition fix. Solana is deleting the feature. LNbits patches were expected inside two days. The $270 million was a single protocol’s loss in April, not a systemic event, and the sats taken from one tipping bot are a rounding error against daily Lightning volume. On this reading, my “one missing line” is pattern-matching on coincidence, and the industry’s error rate is falling, not rising.
I take that seriously, because the base rate of “everything is broken” essays is high and their predictive value is low. Three things push me the other way. First, the Eclair case shows the failure is not in the hard part: nobody got the batch protocol design wrong; someone wrote one of two required conditions. That is the failure mode that scales with code volume, and code volume is what agents multiply. Second, the fix cadence, four security releases in six weeks for one implementation, tells you the disclosure stream is now continuous, which is what you would expect if more people are looking and more people are shipping. Third, the cost of these failures is not distributed evenly: LNbits’ loss went to a volunteer operator, and Drift’s went to a protocol’s users, while the entity that could have prevented it in each case is the one that writes the next release.
Where the sceptic is right: none of this argues that the systems are unsound in aggregate. Bitcoin’s base layer has had no comparable incident, and the Lightning bugs described here are implementation failures with fast fixes, not design failures in the protocol. The record so far supports “this is manageable,” not “this is fatal.”
The honest part
Things I did not do and will not pretend to. I did not audit any of this code. The LNbits exploit mechanism has not been published by the project. What I have is a fix branch: commit messages and a diff, which show where the patch went, not what an attacker did. And no tagged release contains it yet. The Faros numbers reach me through a podcast’s show notes, not the study itself, and the durable-nonce usage figures come from one researcher’s June measuring period, which is a snapshot rather than a census. The bc1p thread is one person’s report. And my inflation arithmetic is exactly that: division of four published index levels, not a model.
What follows
If enforcement is the scarce good, three practices follow, and none of them require heroics.
Check the bound where the queue is, not where the spec says. Eclair’s authors had the right constant in front of them and compared it to the wrong side of a range. The place to look is wherever a message causes an allocation that persists after the message is acknowledged — if there is no backpressure between “received” and “buffered,” the input rate is the attacker’s choice, not yours.
Treat “no channel required” as its own severity class. Bugs that demand a funded channel cost an attacker capital and leave a trail in the chain. Bugs reachable by any TCP connection cost nothing and leave nothing. The Eclair disclosure ranks this correctly: it does not need a channel, and it says so in the first paragraph.
Measure the installed base before changing a default. The newest address format, the newest API version, the newest signing scheme: all correct in the specification, all capable of breaking a user against a counterparty that has not moved. The Taproot question will not be settled by a protocol argument. It will be settled by which exchanges accept bc1p this quarter.
And one habit for the people now reviewing machine-written patches: the question to ask is not whether the code does what the description says. It is which invariant the description never mentions, because that is where the missing condition lives, and the description will be excellent.
The zero that Eclair accepted was in a field the specification said could not be zero. In a system where thousands of commits arrive per week from authors who never saw the specification, the only defence is a reviewer who knows the sentence. That reviewer is now the bottleneck. Paying for that person is cheaper than paying for the patch, and vastly cheaper than paying for the outage.
Sources
- Delving Bitcoin — Disclosure: Memory-exhaustion DoS in Eclair 0.14.0
- Delving Bitcoin — Should wallets default to Taproot or SegWit receive addresses while some services still reject bc1p?
- TFTC (Marty Bent) — LNbits Orders Emergency Shutdown After Active Attacks Drain Sats
- GitHub — lnbits/lnbits releases (v1.6.0 to v1.6.2)
- GitHub — lnbits/lnbits commit history, dev branch
- GitHub — ACINQ/eclair releases (v0.14.3 to v0.14.6)
- GitHub — lnbits/lnbits: branch fix_ssrf compared with dev
- GitHub Security Advisories — GHSA-qp8j-p87f-c8cc, SSRF via LNURL authentication callback (April 2025)
- CoinDesk — Solana moves to scrap the transaction feature used in the $270 million Drift hack
- Solana Improvement Documents — SIMD-0723 pull request
- Latent Space — How Stanford Teaches AI-Native Engineering (Mihail Eric, Monaco)
- Simon Willison — parse_number() as a SQLite extension: C vs Rust
- Simon Willison — The Web Is More Than Enough, by Paul Ford
- U.S. Bureau of Labor Statistics — Producer Price Index, final demand (WPUFD4) and CPI-U (CUUR0000SA0), September 2026
- CoinDesk — SpaceX proposes a $24 billion Texas supercomputer worth 17 times its bitcoin pile
Write a comment