Skip to content

One-time macaroon rotation for leaked macaroons - #11

Merged
rockstardev merged 1 commit into
lnd/v0.21.1-betafrom
macaroon-root-key-rotation
Aug 7, 2026
Merged

rockstardev merged 1 commit into
lnd/v0.21.1-betafrom
macaroon-root-key-rotation

Conversation

@Kukks

@Kukks Kukks commented Aug 7, 2026 •

Copy link
Copy Markdown

Incident response for macaroons that leaked off a node.

Why the macaroon files alone are not enough

The obvious fix — delete admin.macaroon and friends and let lnd recreate them — does not revoke anything. genDefaultMacaroons (lnd.go) only recreates files that are missing, and re-bakes them from the same bakery root key in macaroons.db. The leaked macaroon keeps validating; the new file is just an equivalent token sitting next to it.

macaroons.db has to go as well. lnd then creates a new root key and regenerates its own macaroons on the next unlock, and every macaroon issued from the old key is dead.

Clearing all of them, not just lnd's three

Every macaroon on the volume dies with that root key, not only the three lnd manages. Leaving the rest on disk just leaves tokens lying around that no longer work — hand baked ones from lncli bakemacaroon --save_to, scoped macaroons handed to other services, stale copies in backup folders.

Clearing the network dir copy of admin.macaroon matters in particular: once the root key is gone it is a dead token, and genDefaultMacaroons skips regenerating it precisely because the file still exists, so it would never self-heal.

find is used rather than a fixed list so nothing on the volume is missed, and -exec rm rather than -delete because busybox find on the alpine amd64 image may be built without -delete (the arm images are debian, where it exists). Each removed path is printed, so the container log is an audit trail of what was revoked.

Running in the entrypoint before lnd starts means nothing is holding the files open, and no restart dance is needed.

How it is triggered

Set LND_MACAROON_ROTATION_ID. A marker on the data volume ($LND_DATA/.macaroon-rotated-<id>) keeps it to one rotation per id; bump the id to rotate again after a future incident. With the variable unset the entrypoint behaves exactly as before.

Every client of an affected node has to be re-paired afterwards. Anything that reads the macaroon from the shared volume path recovers on restart; anything holding a copy (a macaroon pasted as hex into a config, RTL/ThunderHub/Zeus/Alby, plugins) needs re-pairing. Channels and funds are untouched — macaroons are auth only.

Test plan

The rotation block was extracted from the committed file and exercised directly:

Case Expected Result
Disarmed (no env var) nothing touched pass
Armed, first run all macaroons + macaroons.db removed, marker written pass
Hand baked, scoped and nested stale macaroons present all removed too pass
Marker present, lnd has regenerated skipped, regenerated macaroons left alone pass
Id bumped rotates again pass
LND_CHAIN/LND_ENVIRONMENT unset, so NETWORK/ENV empty still rotates correctly pass

wallet.db, channel.db, walletunlock.json and tls.cert survive every case. bash -n passes.

Not verified: this has not been run in a real container, so lnd-side regeneration after the rotation is untested. One regtest cycle would close that.

Worth raising separately

walletunlock.json stores the wallet password and the cipher seed mnemonic in cleartext on the data volume. If the exploit read files off that volume, the seed is compromised and rotating macaroons does not help — the funds need to move to a new wallet.

@Kukks
Kukks force-pushed the macaroon-root-key-rotation branch from 56d9e7b to c10d0ad Compare August 7, 2026 18:59
@Kukks Kukks changed the title One-time macaroon root key rotation for leaked macaroons One-time macaroon rotation for leaked macaroons Aug 7, 2026
Incident response for macaroons that leaked off a node.

Deleting the macaroon files is not enough on its own. genDefaultMacaroons
only recreates files that are missing, and re-bakes them from the same bakery
root key in macaroons.db, so a leaked macaroon keeps validating and the new
file is just an equivalent token next to it. macaroons.db has to go as well;
lnd then creates a new root key and regenerates its own macaroons on the next
unlock.

Every macaroon on the volume dies with that root key, not just the three lnd
manages, so all of them are cleared instead of being left behind as tokens
that no longer work: hand baked ones from lncli bakemacaroon --save_to,
scoped macaroons handed to other services, stale copies in backup folders.
Clearing the network dir copy of admin.macaroon matters in particular,
because lnd skips regenerating a file that still exists, so a leftover there
would never self-heal.

find is used rather than a fixed list so nothing on the volume is missed, and
-exec rm rather than -delete because busybox find on the alpine image may be
built without it.

This runs in the entrypoint before lnd starts, so nothing is holding the
files open. Set LND_MACAROON_ROTATION_ID to arm it; a marker on the data
volume keeps it to one rotation per id, and bumping the id rotates again.
With the variable unset the entrypoint behaves exactly as before.

Every client of an affected node has to be re-paired afterwards.
@Kukks
Kukks force-pushed the macaroon-root-key-rotation branch from c10d0ad to 489b439 Compare August 7, 2026 19:12
@rockstardev
rockstardev self-requested a review August 7, 2026 19:21
@rockstardev
rockstardev merged commit c1d8818 into lnd/v0.21.1-beta Aug 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants