Skip to content

feat: prompt Lightning credential rotation after the 2.4.2 security fix; 2.4.2:0 → 2.4.2:1 - #105

Merged
MattDHill merged 4 commits into
masterfrom
feat/lnd-macaroon-rotation-task
Aug 7, 2026
Merged

feat: prompt Lightning credential rotation after the 2.4.2 security fix; 2.4.2:0 → 2.4.2:1#105
MattDHill merged 4 commits into
masterfrom
feat/lnd-macaroon-rotation-task

Conversation

@MattDHill

@MattDHill MattDHill commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

2.4.2 patches an actively exploited vulnerability, but patching does not undo access an attacker already took. Everything BTCPay could reach on a pre-2.4.2 build should be treated as exposed, and for the Lightning backends that is actionable — so the up migration raises a critical task against whichever backend btclightning names:

btclightning Task target Rotates
LND lndrevoke-macaroons The admin macaroon BTCPay reads off the mount
CLN c-lightningrevoke-runes Any rune mintable through the admin RPC socket

Both tasks are gated on the backend being the configured one and its package being installed. The second guard is load-bearing: a critical task stops the source service and is cleared only when the target service runs the action, so raising one for an absent package would leave BTCPay unstartable short of a force-start.

Two exposures the migration deliberately does not try to cover, documented in the release notes and instructions.md instead:

  • An install that pointed at a node and later switched away is indistinguishable from one that never did, so those users are told to rotate anyway.
  • A hot on-chain wallet generated inside BTCPay has keys that cannot be rotated at all — those funds have to move.

Version bumped rather than folded into 2.4.2:0 so installs that already took :0 from the alpha deploy still receive the prompt.

Dependency floors

dependencies.ts floors lnd at >=0.21.1-beta:11 and c-lightning at >=26.6.6:9, up from :4 and :1. Neither action existed in usable form before those releases — LND's was named recreate-macaroons and only deleted the macaroon files, leaving the root key that signs them in place, so it revoked nothing (lnd-startos#177); revoke-runes did not exist (cln-startos#185). Without the floors a user could be stopped by a critical task pointing at an action that does nothing.

Merge order: both sibling PRs must land, and reach the branches package.json pins, before this one builds.

Test plan

  • With LND selected, update from 2.4.2:0; confirm BTCPay stops and shows a critical task pointing at LND's Revoke Macaroons
  • Run that action; confirm the task clears, BTCPay starts, and Lightning invoicing still works with the new macaroon
  • Repeat with CLN selected, against Revoke All Runes
  • With None/External selected, confirm the update raises no task and BTCPay starts normally
  • With LND selected but LND uninstalled, confirm no task is raised and BTCPay is not stranded
  • With an LND older than 0.21.1-beta:11 installed, confirm the dependency floor surfaces rather than the task pointing at an ineffective action

🤖 Generated with Claude Code

MattDHill and others added 2 commits August 7, 2026 11:57
…ix; 2.4.2:0 → 2.4.2:1

2.4.2 patches an actively exploited vulnerability, but patching does not undo
access an attacker already took. Everything BTCPay could reach on a pre-2.4.2
build should be treated as exposed, and for the Lightning backends that is
actionable, so the up migration raises a critical task against whichever one
btclightning names:

- LND — lnd:recreate-macaroons. BTCPay reads LND's admin macaroon off the mount,
  which is full control of the node.
- CLN — c-lightning:revoke-runes. BTCPay reaches CLN over the unrestricted
  lightning-rpc admin socket, so there is no credential of ours to rotate, but a
  compromised BTCPay could have minted itself a rune that survives the patch.

Both tasks are gated on the backend being the configured one *and* its package
being installed. The second guard is load-bearing: a critical task stops the
source service and is cleared only when the *target* service runs the action, so
raising one for an absent package would leave BTCPay unstartable short of a
force-start.

Two exposures the migration deliberately does not try to cover, both documented
in the release notes and instructions instead: an install that pointed at a node
and later switched away is indistinguishable from one that never did, and a hot
on-chain wallet generated inside BTCPay has keys that cannot be rotated at all —
those funds have to move.

Version bumped rather than folded into 2.4.2:0 so installs that already took :0
from the alpha deploy still receive the prompt.

Depends on lnd-startos 0.21.1-beta:11 and cln-startos 26.6.6:9 reaching the
branches package.json pins: recreate-macaroons did not delete the macaroon root
key before that release, so it did not actually revoke anything, and revoke-runes
did not exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… work

The 2.4.2:1 rotation task points at lnd's revoke-macaroons and
c-lightning's revoke-runes. Neither existed in usable form before
0.21.1-beta:11 and 26.6.6:9 respectively — LND's was named
recreate-macaroons and only deleted macaroon files, leaving the root key
that signs them in place, so it revoked nothing; CLN's did not exist.
Flooring the dependencies there means a user cannot end up stopped by a
critical task pointing at an action that does nothing.

Also repoints the import at the renamed lnd action.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MattDHill and others added 2 commits August 7, 2026 12:36
Both siblings' next branches were fast-forwarded to master after #177 and
#185 merged, so the pins now carry revoke-macaroons and revoke-runes — the
actions 2.4.2:1's rotation task targets. Only the lockfile moves;
package.json still tracks #next.

cln-startos brings its start-sdk pin up to 2.0.9 with it, which the
overrides entry keeps hoisted to a single copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…anifests

effects.getInstalledPackages was the only raw-effect call left in the
package; sdk.getInstalledPackages is the same call through the facade
everything else already uses.

The package ids came from string literals, which the repo takes from the
dependency's own manifest elsewhere for exactly this reason — c-lightning's
id is 'c-lightning', not the 'cln' the repo and directory are named after,
and includes() on a wrong literal fails silently rather than at compile
time. main.ts already imports both manifests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MattDHill
MattDHill merged commit 9d8b215 into master Aug 7, 2026
3 checks passed
@MattDHill
MattDHill deleted the feat/lnd-macaroon-rotation-task branch August 7, 2026 18:41
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