Anatomy of a Lightning Theft and How Tollbooth DPYC Operators lost no Balances
Share
On 7 August 2026, the Lightning node behind our payment processor was emptied. The theft took twelve minutes, moved 576,777 satoshis, and was one of roughly seventy carried out by the same automated campaign within the same hour.
This is the forensic account. What the vulnerability was, what the crime scene looked like, how we went onto the server, which logs we pulled and what they contained, how we reconstructed the channel configuration that no longer exists, and what we are rotating as a result. It closes with the part that matters most to anyone who depends on us: the network's Operators and Patron balances were never at risk, and never stopped working.
The vulnerability, and the timeline
BTCPay Server published version 2.4.2 on 7 August 2026 at 15:31 UTC, opening the release notes with a warning that the release fixes a critical vulnerability under active exploitation and that operators need to update as fast as they can. The named fix is a TOTP two-factor authentication bypass via Greenfield Basic authentication (PR #7491). The same release disables Greenfield Basic authentication by default five minutes after account creation, rate limits public invoice creation on payment requests, and recommends that integrators also move NBXplorer to 2.6.10. The bug was reported by brunoerg and benthecarman of the Bitcoin Red Team effort.
The project's public advisory followed on its X account about twenty minutes later. It told operators to update through Admin Dashboard → Server → Maintenance → Update and confirm the 2.4.2 string in the footer, to take the server offline if they could not update immediately, and — the part that matters most — to refresh every macaroon file and macaroons.db afterward. BTCPay confirmed that attackers had exploited the flaw and that funds were stolen, while withholding full technical detail until operators had time to patch. The Defiant's account of the scramble to patch records the same instruction to update or shut down. A complete postmortem is still pending.
In practical terms the flaw allowed an unauthenticated remote attacker to retrieve LND .macaroon credential files. A macaroon is a bearer token. Once it has been copied, it grants whatever authority it encodes for as long as its root key survives, which is why the second half of BTCPay's guidance turned out to matter more than the first.
15:31 UTC BTCPay Server 2.4.2 published 15:51 UTC Project posts its public advisory 16:41 UTC Two CloseChannel calls on our node 16:53 UTC sendcoins --sweepall issued 17:20 UTC Sweep confirmed in block 961466 ~20:00 UTC We install 2.4.2
Our node was drained seventy minutes after the fix existed and fifty minutes after the warning went out. We were inside the window between disclosure and remediation, along with everyone else the campaign reached that hour. Hardware wallet maker Foundation and the Bitcoin publication Citadel21 both reported swept nodes in the same window, as CoinDesk documented. Stating the order plainly matters, because it is the difference between a patch that failed and a patch that arrived after the credential had already been taken.
TL;DR advice
If PR #7491 is the defect that was actually exploited — which BTCPay has not confirmed, and which the sidebar further down flags as an open question — then the outcome for any given operator turned on which second factor was enrolled on the administration account.
- TOTP enrolled. Bypassed. Time-based One-Time Password, specified in RFC 6238, which extends the HMAC-based one-time password algorithm of RFC 4226. This is the ordinary authenticator app: scan a QR code, read six digits. The Greenfield API asked whether a hardware credential was registered rather than whether two-factor was enabled, so a TOTP-only account returned false and the second factor was skipped.
- FIDO2 enrolled. Blocked. Fast IDentity Online 2 is not an IETF standard but a pair of specifications: W3C Web Authentication (WebAuthn) for the browser side and the FIDO Alliance's Client to Authenticator Protocol 2 (CTAP2) for the device side. The IETF's contribution is RFC 8809, which registers the WebAuthn identifiers. A registered hardware key is precisely what the flawed check looked for. It would have found one, and enforced the second factor.
- Neither enrolled. A stolen, reused or sprayed password was sufficient on its own, because there was no second factor to bypass. That is the software working as documented rather than failing.
The practical advice follows from that. After completing the recommended recovery, a BTCPay Server instance has the patch plus an enrolled authenticator such as Duo. The patch is what makes the authenticator trustworthy again, since the corrected logic asks whether two-factor is enabled rather than which credential type happens to be registered. That combination is adequate protection. Adding a hardware key such as a YubiKey moves the administration login from adequately protected to essentially unattackable by this vector, because possession of the physical device becomes a precondition no remote attacker can satisfy.
This is an argument for hardware keys in general. It is not an argument that operators were at fault. A defect that silently voids a second factor an administrator deliberately enrolled, while the browser login page continues to enforce it correctly and show nothing wrong, is a software failure. The maintainers treated it as one: the remedy shipped in 2.4.2 was to fix the check and disable Basic authentication by default, not to advise anyone to choose better passwords.
The crime scene
The node presented as empty. Inbound capacity zero, outbound capacity zero, no active channels, no pending channels. On-chain wallet balance zero across every account. Three peers were still connected, which is what you would expect from a node whose channels have been closed rather than one that has lost its network identity.
The node's identity was intact. Same public key, full operational history, three closed channels recorded with their capacities, counterparties and settled balances. Nothing had been reset or reinitialised. The channels had not been lost. They had been closed, deliberately, and the settled proceeds had then been moved off the wallet.
Every close was a COOPERATIVE_CLOSE carrying close_initiator: INITIATOR_LOCAL. That combination is worth understanding, because it is the signature of this attack rather than an absence of one. An intruder holding a stolen admin macaroon has full RPC control of the node. Force-closing would be slower, more expensive, and would place a timelock between the intruder and the funds. Calling CloseChannel the way the operator would is simply more efficient. INITIATOR_LOCAL records that the instruction originated at our node — which it did, because by that point our node was accepting instructions from someone else.
Going on-server
We began at the web tier, from outside. The host answered on HTTPS in under 200ms and redirected correctly to the BTCPay login page behind nginx. The application itself was healthy and serving. Whatever had happened was below the web tier, at the node level.
From there we went in over SSH to the EC2 instance, as the ubuntu user with the instance's registered key pair, verified first against the fingerprint AWS holds for it. Everything that followed was read-only. We changed nothing on the machine during the investigation, on the principle that a compromised host is evidence before it is infrastructure.
On the box we enumerated the running stack — thirteen containers, all restarted roughly fourteen hours before this investigation, when we installed the 2.4.2 patch on the evening of the 7th. That restart came several hours after the sweep. The machine was running a pre-2.4.2 build at the moment of the theft, and the host itself had been up for 194 days. We then queried the Lightning node directly for its identity, its closed channels and its wallet balance, and inspected every container's published port bindings.
The perimeter was configured correctly
Worth stating plainly, because it is the first thing most operators check and in this case it was already right.
The EC2 security group permitted inbound traffic on exactly four ports. Ports 80 and 443 were open to the internet, as a public payment endpoint requires. Port 9735 was open to the internet, which Lightning peer-to-peer connectivity requires in order to maintain channels at all. Port 22 was restricted to a single /32 — one address, not a range.
The Lightning node's own control interfaces — the gRPC port 10009 and the REST ports 8080 and 8081 — were not published to the host at all. They existed only inside the Docker network, unreachable from outside the machine. Container inspection confirmed that only nginx (80, 443) and LND's peer port (9735) had host bindings.
That is a correct configuration, and it made no difference. The route in was port 443, through the web application, to the macaroon that BTCPay legitimately holds in order to operate its own internal node. No firewall rule can close that path, because the path is the product working as designed.
That reading matches the project's own account of the flaw: unauthenticated remote retrieval of .macaroon files through the application layer, not a network-level intrusion. We are not claiming to have independently reproduced the exploit, since the technical detail remains unpublished. We are saying that every artifact we hold is consistent with it, and that nothing in our logs points anywhere else.
The logs we reviewed
We pulled and preserved roughly 19 MB of evidence off the host before touching anything else:
-
/var/log/auth.logplus four rotations (auth.log.1,.2.gz,.3.gz,.4.gz) — about five weeks of SSH and sudo history -
docker logsoutput for all thirteen running containers - LND's own
lnd.log, plus ten rotated archives,lnd.log.85.gzthroughlnd.log.94.gz - BTCPay's daily application log from its data directory
- The Ride The Lightning node log
- Container port bindings and network addresses via
docker inspect - The EC2 security group rules, read from the AWS API rather than from memory
The SSH history was clean. No unfamiliar authorized keys, no user accounts beyond the expected one, no cron entries anywhere. The only root logins on the machine originated from a Docker-internal address belonging to the BTCPay container itself — that is BTCPay's own self-update channel, which legitimately holds a root key so it can restart the stack, and the timestamps line up exactly with our 2.4.2 installation that evening. There was no host-level compromise.
The live lnd.log had already rotated past the relevant window. The archive covering it had not. In lnd.log.93.gz, which opens at 16:30 UTC, the entire theft occupies four lines:
2026-08-07 16:41:04.497 [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: rpc error: code = Canceled desc = context canceled 2026-08-07 16:41:04.498 [ERR] RPCS: [/lnrpc.Lightning/CloseChannel]: rpc error: code = Canceled desc = context canceled 2026-08-07 16:53:53.058 [INF] RPCS: [sendcoins] addr=19jSfadPiKa4aCefj4F7uEYbhMEt2L8SCH, amt=0 BTC, sat/kw=1750, min_confs=1, send_all=true, select_outpoints=0 2026-08-07 16:53:53.123 [INF] RPCS: [sendcoins] spend generated txid: 10f219c427635e50dc374af6aa601e6f412ac0dccc7c545932afba5f818808f0
Two CloseChannel calls, then twelve minutes later a single sendcoins carrying send_all=true. Those are the only RPC entries in the node's logs for the entire day, apart from one unauthenticated GetInfo that evening traced to a companion container restarting without credentials.
send_all=true at sat/kw=1750 is a plain sendcoins --sweepall. Not a bespoke tool, not an exotic payload — the ordinary command, driving stolen credentials.
Following the money
The on-chain half is public and reproducible by anyone with a block explorer.
Both channels closed in block 961462 at 16:40 UTC, each paying our side out to a taproot address belonging to the node's own wallet. Four blocks later, in block 961466 at 17:20 UTC, one transaction consumed four inputs and produced exactly one output, with no change:
tx 10f219c427635e50dc374af6aa601e6f412ac0dccc7c545932afba5f818808f0 in 99,679 + 80,041 + 299,670 + 99,315 = 578,705 sats (all P2TR, our wallet) out 576,777 sats -> 19jSfadPiKa4aCefj4F7uEYbhMEt2L8SCH (P2PKH) fee 1,928 sats
Every input is a modern taproot address. The single output is a legacy address, and LND does not generate legacy addresses for itself. A wallet paying someone leaves change behind; a wallet being emptied does not.
The destination is a shared collection point
The receiving address is not specific to us. It holds 174,760,889 satoshis — roughly 1.7476 BTC — received across 71 inbound transactions, with nothing spent back out. Approximately twenty other victims' sweeps confirmed in the same block as ours, at the same minute.
Whatever ran against our node ran against many nodes simultaneously and pooled everything in one place, where it remains. We reported the address to the BTCPay Server security team and to Chainabuse while the balance was still unmoved.
Reconstructing the configuration that no longer exists
Because the node retained its identity, its closed-channel records survived the theft intact. That record is the only remaining description of a configuration that no longer exists anywhere else, and it is what we will rebuild from:
scid peer capacity settled closed at status
935418x1029x1 ACINQ 400,000 399,825 block 935663 cooperative, ~180d before the attack
935663x1415x1 Blocktank 100,000 99,679 block 961462 cooperative, taken 2026-08-07
935665x732x0 gameb_1 100,000 80,041 block 961462 cooperative, taken 2026-08-07
peers: ACINQ 03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f
Blocktank 0296b2db342fcf87ea94d981757fdf4d3e545bd5cef4919f58b5d38dfdd73bf5c9
gameb_1 023d70f2f76d283c6c4e58109ee3a2816eb9d8feb40b23d62469060a2b2867b77f
All three were opened locally. The ACINQ channel closed normally around six months before the incident and is unrelated to it; its settled proceeds were the two older wallet UTXOs that the sweep also consumed. The Blocktank and gameb_1 channels were the two live channels taken on the day.
Deleting macaroon files does not revoke them
This is the single most important operational fact of the incident, and it is the one most easily missed.
LND does not store macaroons as independent secrets. It derives them from a root key held in macaroons.db. Delete the .macaroon files and restart, and LND re-derives them from that same root — which means a macaroon copied by an attacker before the deletion still authenticates afterward. The root key itself must be replaced. Regeneration is not revocation.
Replacing the root key does work, and it works completely. Every macaroon ever derived from the old root dies at once, because the first HMAC in every chain no longer reproduces. There is no partial cleanup and no list to maintain.
Some coverage of this incident has described the update as regenerating macaroons on its own, which invites operators to treat patching as the end of the job. It is not. TechTimes puts the distinction in its headline: the patch closes the flaw, and the stolen credentials survive it. BTCPay's own guidance is stricter and correct: delete the macaroon files and macaroons.db, then restart the node. If you run BTCPay with LND and you have patched but not rotated, you are still exposed. With our wallet at zero there was nothing to migrate, so we took the cleanest path available and replaced the wallet outright rather than performing surgery on the old one.
Credential rotation
Patching to 2.4.2 closes the vulnerability. It does not invalidate a credential that has already been copied, so the node stays unfunded until rotation is complete. Refunding a node whose macaroon is in someone else's hands simply repeats the donation.
The full sequence:
- A new LND wallet with a new seed, replacing the macaroon root key and therefore every macaroon derived from it
- Regenerated TLS certificate and key
- A rotated BTCPay administrator password, and rotated store API keys
- Re-delivery of the rotated store credentials to every Operator over Secure Courier, encrypted to each Operator's Nostr identity
- A repeat review of users, API keys and webhooks for anything created during the exposure window
- Funding only after all of the above
What never stopped running, and why
The payment processor went out of service and is being rebuilt from scratch. No Operator in the Tollbooth DPYC network went down with it, and no Patron balance was ever exposed. That is not luck. It is the direct consequence of how the network separates payment from service.
A Patron's satoshi balance does not live on the payment processor. It lives in the Operator's own Neon Postgres database, in encrypted columns, entirely separate from the Lightning node and the machine hosting it. Bitcoin and Lightning are the on-ramp, not the meter. The payment processor is touched at exactly two moments — when a Patron buys credits and an invoice is created, and when that invoice is polled for settlement. Every paid tool call after that debits a balance that is already funded and already sitting in the Operator's database. There is no Lightning round-trip per request, and therefore no dependency on the payment processor being reachable.
Operator secrets are separated the same way. Each Operator's credentials are delivered over Secure Courier, encrypted to that Operator's Nostr identity, and stored AES-256-GCM encrypted in that Operator's own vault. The compromised host held BTCPay's store credentials — which is why they are being rotated — but it never held the Operators' vaults, their databases, or their Patrons' balances. The Operators themselves run on separate infrastructure entirely.
We verified this during the incident rather than assuming it. With the payment processor drained and offline, the Operators reported healthy, vaults configured, credentials intact, and continued serving paid calls against pre-funded balances throughout.
The practical effect for anyone using the network: if you hold a satoshi balance with an Operator, nothing about this incident touched it, and nothing about it interrupted your calls. The single degraded capability is topping up — new Lightning purchases wait on the rebuilt processor. Existing balances spend exactly as they did before.
Pre-funded balances are usually described as a convenience, a way to avoid interrupting people with a payment prompt in the middle of their work. This incident is the other half of that argument. A payment processor that sits outside the request path can be destroyed on a Thursday afternoon without a single Patron losing access to what they already paid for.
Sources
- BTCPay Server — v2.4.2 release notes
- TechTimes — Patch Closes Flaw but Stolen Credentials Survive
- The Defiant — Lightning Nodes Drained As Users Race To Patch
- The Cryptonomist — BTCPay Server Vulnerability
- CoinDesk — Another Bitcoin Infrastructure Exploit Hits, This Time Draining Merchant Lightning Nodes
- BTCPay Server — the public advisory post
Updated 8 August 2026 to add the disclosure timeline, the named vulnerability and its sources, TL;DR advice on second factors, sidebars on the request path and on how macaroons work, and the distinction between macaroon regeneration and revocation.








