TLDR
ldk-node treats any ChannelManager KV-store read error as a missing manager and creates a fresh, empty node. A timeout, connection failure, authentication error, or exhausted VSS 5xx can therefore return a successful node instead of BuildError::ReadFailed.
Vulnerable code
src/builder.rs:1935:
if let Ok(reader) = channel_manager_bytes_res {
// Restore ChannelManager.
} else {
// Create a fresh ChannelManager.
}
The else branch does not distinguish NotFound from other storage failures.
Impact
An established node can start with zero channels after a manager read failure. If the fresh manager is later persisted, it can overwrite the valid manager and permanently destroy the live off-chain channel state.
Loaded ChannelMonitors remain active, so the expected manager-only outcome is unilateral channel closure, fees, and delayed access to funds—not loss of the full channel balance. Principal loss requires the monitors to also be missing or unsafe.
Verification
The vulnerable behavior is confirmed by source inspection of upstream main.
The attached test poc.txt is an end-to-end regtest PoC using two real ldk-node instances and a real channel. It injects a manager-only ErrorKind::Other at the KVStore boundary and verifies that:
- a total store outage fails closed;
- a manager-only failure builds a node with zero channels;
- the channel is unilaterally closed on-chain; and
- graceful shutdown overwrites the valid manager with the smaller empty manager.
ErrorKind::Other is the error class produced by VSS for a 5xx after retry exhaustion. The PoC tests the resulting KV-store failure directly; it does not emulate the HTTP request or VSS retry layer itself.
Proposed fix
match channel_manager_bytes_res {
Ok(reader) => restore_manager(reader)?,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => create_fresh_manager(),
Err(error) => {
log_error!(logger, "Failed to read ChannelManager: {}", error);
return Err(BuildError::ReadFailed);
}
}
Additionally, a missing manager with existing monitors should require an explicit recovery path rather than appearing as a normal empty node.
TLDR
ldk-node treats any
ChannelManagerKV-store read error as a missing manager and creates a fresh, empty node. A timeout, connection failure, authentication error, or exhausted VSS 5xx can therefore return a successful node instead ofBuildError::ReadFailed.Vulnerable code
src/builder.rs:1935:The
elsebranch does not distinguishNotFoundfrom other storage failures.Impact
An established node can start with zero channels after a manager read failure. If the fresh manager is later persisted, it can overwrite the valid manager and permanently destroy the live off-chain channel state.
Loaded
ChannelMonitors remain active, so the expected manager-only outcome is unilateral channel closure, fees, and delayed access to funds—not loss of the full channel balance. Principal loss requires the monitors to also be missing or unsafe.Verification
The vulnerable behavior is confirmed by source inspection of upstream
main.The attached test poc.txt is an end-to-end regtest PoC using two real ldk-node instances and a real channel. It injects a manager-only
ErrorKind::Otherat theKVStoreboundary and verifies that:ErrorKind::Otheris the error class produced by VSS for a 5xx after retry exhaustion. The PoC tests the resulting KV-store failure directly; it does not emulate the HTTP request or VSS retry layer itself.Proposed fix
Additionally, a missing manager with existing monitors should require an explicit recovery path rather than appearing as a normal empty node.