Skip to content

firewall: add interceptor for non-LND calls - #1271

Merged
bitromortac merged 6 commits into
lightninglabs:faraday-forwarding-abilityfrom
bitromortac:2603-faraday-prep-4
May 6, 2026
Merged

firewall: add interceptor for non-LND calls#1271
bitromortac merged 6 commits into
lightninglabs:faraday-forwarding-abilityfrom
bitromortac:2603-faraday-prep-4

Conversation

@bitromortac

Copy link
Copy Markdown
Contributor

Adds a gRPC unary interceptor to LNC sessions that applies privacy mapping to non-LND sub-daemon calls (e.g. Faraday). LND URIs are skipped since they're already handled by LND's middleware chain.

I added a diagram to see how the two flows work for lnd/subdaemon calls:
https://github.com/bitromortac/lightning-terminal/blob/eee3d033fff0f808ec5aa3119721fb5d3fd95c9c/docs/privacy-mapping-flows.md

If the design is ok, I can also use a streaming interceptor.

@bitromortac
bitromortac requested a review from ViktorT-11 March 31, 2026 10:20
@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the privacy architecture of Lightning Terminal (LiT) by extending privacy mapping capabilities to non-LND sub-daemon calls. By introducing a dedicated gRPC unary interceptor, the system now ensures that sensitive data in requests and responses for services like Faraday is correctly obfuscated before reaching the sub-daemon. The implementation follows a fail-close design pattern to maintain security integrity and includes comprehensive updates to the initialization flow and test suite.

Highlights

  • gRPC Unary Interceptor: Introduced a new gRPC unary interceptor to LNC sessions that handles privacy mapping for non-LND sub-daemon calls, ensuring consistent privacy enforcement across all services.
  • Fail-Close Security: Implemented a fail-close mechanism in the interceptor that blocks requests if dependencies are not initialized or if privacy mapping fails, preventing potential data leaks.
  • LND URI Exemption: Added logic to explicitly skip LND URIs in the new interceptor, as these are already managed by LND's existing middleware chain.
  • Documentation: Added a new documentation file with Mermaid diagrams illustrating the distinct privacy mapping flows for LND versus sub-daemon requests.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@bitromortac
bitromortac requested a review from ellemouton March 31, 2026 10:20

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a gRPC unary interceptor to apply privacy mapping for non-LND sub-daemon requests within LNC sessions, including documentation of the call flows and integration into the terminal startup logic. Feedback identifies a critical startup race condition that could cause a panic, performance inefficiencies due to redundant session database lookups, and a potential security risk where requests missing session metadata are allowed to pass through without privacy mapping.

Comment thread firewall/privacy_mapper.go
Comment thread terminal.go Outdated
Comment thread firewall/privacy_mapper.go Outdated
Comment on lines +252 to +257
sessionID, err := extractSessionFromContext(ctx)
if err != nil {
log.Tracef("LNC privacy interceptor: no session for "+
"%s, passing through", info.FullMethod)
return handler(ctx, req)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the session ID cannot be extracted from the context, the interceptor currently logs a trace and passes the request through without any privacy mapping. If this interceptor is intended for a dedicated LNC listener, any request missing a session ID should likely be rejected (fail-close) to prevent potential privacy leaks or unauthorized access to sub-daemons with real identifiers. Allowing a pass-through here might bypass the privacy mapping layer if a client manages to send a request without the expected metadata.

@ViktorT-11 ViktorT-11 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally this looks good, thanks 🔥! Worth looking into some of the review-bot comments though :).

Also: In the future, we'll likely want to make the accounts system be able to act on requests to other sub-systems than just lnd (specifically taproot-assets seems likely). I think a potential solution of how we would achieve that would be to create some a generic account interceptor layer that works on normal gRPC requests/responses, and not just lnrpc.RPCMiddlewareRequests. I think it's likely out of scope for this PR, but when we do so, it impact what's being added by this PR.
Just thought I'd add a comment regarding that, in case that in some way changes how you'd like to design this PR :).

Additionally, this PR should add a release note IMO.

Comment thread firewall/privacy_mapper.go Outdated
@bitromortac
bitromortac force-pushed the 2603-faraday-prep-4 branch from eee3d03 to 6c8ca38 Compare April 9, 2026 07:58
@bitromortac

Copy link
Copy Markdown
Contributor Author

The unit-race test failure is independent of this PR, fixed in #1281.

@ellemouton ellemouton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concept ACK

Comment thread firewall/privacy_mapper.go
Comment thread firewall/privacy_mapper.go
Comment thread terminal.go Outdated
Comment thread terminal.go Outdated
Comment thread firewall/privacy_mapper.go Outdated
@litbot-9000

Copy link
Copy Markdown

@bitromortac, remember to re-request review from reviewers when ready

The checkAndReplaceIncomingRequest and replaceOutgoingResponse helpers
each looked up the session from sessionDB by ID, resulting in two
redundant GetSession round-trips per intercepted request. Move the
lookup to the top of Intercept and pass the resolved *session.Session
into both helpers so the session is fetched exactly once.

This also makes the helpers reusable from callers that already have the
session in hand, such as the unary interceptor introduced in the next
commit.
@bitromortac
bitromortac force-pushed the 2603-faraday-prep-4 branch from 6c8ca38 to 6f74420 Compare April 28, 2026 13:50
The privacy mapper currently runs only inside LND's middleware chain
via PrivacyMapper.Intercept, so requests that LNC routes directly to
non-LND sub-daemons (e.g. Faraday) reach the sub-daemon with real
channel IDs, peer pubkeys and amounts. Add a gRPC unary server
interceptor on the LNC session server that applies the same
pseudo-to-real request mapping and real-to-pseudo response mapping for
these calls. LND URIs are skipped because LND's middleware already
covers them.

The interceptor is fail-close: it blocks the request if any dependency
(permissions manager, session DB, privacy DB) is not yet initialized,
if the call does not carry a session ID, or if no privacy checker is
registered for the URI. A small PermissionsManager interface is
introduced so the mapper can identify LND URIs without a hard
dependency on the perms package.
The unary interceptor added in the previous commit leaves streaming RPCs
unmapped, so a privacy-enabled LNC session can still open a stream
against a non-LND sub-daemon (e.g. Faraday) and receive real channel
IDs, peer pubkeys and amounts.

Add the streaming counterpart with the same fail-close logic: LND URIs
and sessions without privacy pass through; non-LND streams from
privacy-enabled sessions are rejected with PermissionDenied until
per-message stream rewriting is implemented.
Documents the dual-path privacy mapping architecture: LND requests are
mapped via LND's middleware chain (PrivacyMapper.Intercept) after being
proxied to LND, while non-LND sub-daemon requests are mapped at the LNC
gRPC interceptor (PrivacyMapper.UnaryInterceptor) before reaching the
sub-daemon. Each flow is illustrated with a mermaid sequence diagram.
@bitromortac
bitromortac force-pushed the 2603-faraday-prep-4 branch from 6f74420 to a75fd7a Compare April 28, 2026 15:04
@ViktorT-11
ViktorT-11 self-requested a review April 30, 2026 15:20

@ViktorT-11 ViktorT-11 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, uTACK LGTM 🔥!

Comment on lines +443 to +454
// Fail-close: non-LND streaming RPCs are not yet supported
// by the privacy mapper. Block them to prevent cleartext
// leaks to LNC sessions with privacy enabled.
log.WarnS(ctx, "Blocking non-LND stream: privacy mapping "+
"not supported for streams", nil,
"method", info.FullMethod,
"session_id", sess.ID)

return status.Errorf(codes.PermissionDenied,
"streaming RPCs for non-LND services are not "+
"supported with privacy mapping")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to check that you've made sure that this can't cause any issues with tapd (and potentially loop) if we merge this as is?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the following:

  • loop out via TW session
  • local unary frcli call
  • local streaming loop call (monitor)
  • payment using accounts via a session

So I'd consider this well tested and will merge.

if p.permsMgr == nil {
return false, fmt.Errorf("permissions manager not initialized")
}
return p.permsMgr.IsSubServerURI(subservers.LND, uri), nil

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: new line before return.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will skip due to CI run

@bitromortac
bitromortac merged commit c9a5dbb into lightninglabs:faraday-forwarding-ability May 6, 2026
29 checks passed
bitromortac added a commit that referenced this pull request Jun 12, 2026
firewall: add interceptor for non-LND calls
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.

5 participants