loop/looprpc/perms.go
Slyghtning 508b9dfdc1
multi: require Loop Out permission for Instant Out
The InstantOut RPC accepts a caller-controlled dest_addr that becomes
the output of the cooperative sweepless sweep (and of the htlc success
sweep on the fallback path), so it is a fund-moving operation equivalent
to LoopOut. Until now it required only swap:execute, while LoopOut
requires both swap:execute and loop:out. A macaroon scoped to
swap:execute -- intended for, say, an autoloop scheduler or a quote
poller -- could therefore drain reservation balances to an attacker
address. ReservationRequest is analogous on the inbound side: it
triggers an outgoing LN prepayment, so it also belongs behind loop:out.

We also harden the address handling in instantout.Manager.NewInstantOut
to match validateLoopOutRequest:

  - sweepAddr.IsForNet(m.cfg.Network) is now enforced. btcutil
    .DecodeAddress is more permissive than IsForNet for some formats
    (notably anything that happens to share a network prefix); without
    the explicit network check cross-chain copy-paste mistakes parse
    silently and then sign over an unspendable output.
  - The address must be one of the formats Loop normally accepts: P2TR /
    P2WSH / P2WPKH / P2SH / P2PKH. Anything else (e.g. a future address
    type that the user's wallet would otherwise interpret differently)
    is rejected up front rather than failing later in the signing path.

InstantOutQuote and ReservationQuote stay on swap:read since they are
read-only.
2026-08-11 15:02:42 +02:00

220 lines
4.1 KiB
Go

package looprpc
import "gopkg.in/macaroon-bakery.v2/bakery"
// RequiredPermissions is a map of all loop RPC methods and their
// required macaroon permissions to access loopd.
var RequiredPermissions = map[string][]bakery.Op{
"/looprpc.SwapClient/LoopOut": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/LoopIn": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/Monitor": {{
Entity: "swap",
Action: "read",
}},
"/looprpc.SwapClient/ListSwaps": {{
Entity: "swap",
Action: "read",
}},
"/looprpc.SwapClient/SwapInfo": {{
Entity: "swap",
Action: "read",
}},
"/looprpc.SwapClient/AbandonSwap": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "in",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/LoopOutTerms": {{
Entity: "terms",
Action: "read",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/LoopOutQuote": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/SweepHtlc": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/GetLoopInTerms": {{
Entity: "terms",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/GetLoopInQuote": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/GetL402Tokens": {{
Entity: "auth",
Action: "read",
}},
"/looprpc.SwapClient/NewStaticAddress": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/ListUnspentDeposits": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/WithdrawDeposits": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/ListStaticAddressDeposits": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/ListStaticAddressWithdrawals": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/ListStaticAddressSwaps": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/GetStaticAddressSummary": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/StaticAddressLoopIn": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/StaticOpenChannel": {{
Entity: "deposit",
Action: "spend",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/GetLsatTokens": {{
Entity: "auth",
Action: "read",
}},
"/looprpc.SwapClient/FetchL402Token": {{
Entity: "auth",
Action: "write",
}},
"/looprpc.SwapClient/SuggestSwaps": {{
Entity: "suggestions",
Action: "read",
}},
"/looprpc.SwapClient/GetInfo": {{
Entity: "suggestions",
Action: "read",
}},
"/looprpc.SwapClient/GetLiquidityParams": {{
Entity: "suggestions",
Action: "read",
}},
"/looprpc.SwapClient/SetLiquidityParams": {{
Entity: "suggestions",
Action: "write",
}},
"/looprpc.SwapClient/Probe": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "in",
}},
"/looprpc.SwapClient/ListReservations": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/ReservationRequest": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/ReservationQuote": {{
Entity: "swap",
Action: "read",
}},
"/looprpc.SwapClient/InstantOut": {{
Entity: "swap",
Action: "execute",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/InstantOutQuote": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/ListInstantOuts": {{
Entity: "swap",
Action: "read",
}, {
Entity: "loop",
Action: "out",
}},
"/looprpc.SwapClient/StopDaemon": {{
Entity: "loop",
Action: "admin",
}},
}