Skip to content

Harden gRPC transport - #266

Open
benthecarman wants to merge 6 commits into
lightningdevkit:mainfrom
benthecarman:codex/loupe-transport-limits
Open

Harden gRPC transport#266
benthecarman wants to merge 6 commits into
lightningdevkit:mainfrom
benthecarman:codex/loupe-transport-limits

Conversation

@benthecarman

@benthecarman benthecarman commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Various things found by project loupe. Nothing critical but all worth doing.

This PR was created with assistance from Codex and Claude Code.

Limit concurrent handshakes and apply a deadline so unauthenticated
connections cannot retain sockets and tasks without bound.

This commit was created with assistance from Codex.
Limit HTTP/2 streams and concurrent body collection so unauthenticated
clients cannot multiply the per-request body allocation without bound.
Reject requests that omit authentication before reading their bodies.

This commit was created with assistance from Codex.
Omit oversized encoded error messages from response headers and trailers
so attacker-controlled paths cannot cause large response allocations.

This commit was created with assistance from Codex.
@ldk-reviews-bot

ldk-reviews-bot commented Sep 1, 2026

Copy link
Copy Markdown

👋 Thanks for assigning @joostjager as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

Mark a stream terminal after an unrecoverable framing, decoding, or
transport error so later reads cannot repeat the same failure forever.

This commit was created with assistance from Codex.
@benthecarman
benthecarman removed the request for review from tnull September 1, 2026 01:45
@benthecarman
benthecarman requested review from joostjager and removed request for tnull September 1, 2026 02:46

@joostjager joostjager 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.

I raised the risk of hand-rolling previously in PR #220. This PR reinforces that. I think we should reconsider this path and consider migrating to tonic.

Comment thread ldk-server/src/main.rs Outdated
runtime.spawn(async move {
match acceptor.accept(stream).await {
Ok(tls_stream) => {
let _handshake_permit = handshake_permit;

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.

[P1] Release this permit once the TLS handshake completes. Because _handshake_permit remains in scope across serve_connection(...).await, it is held for the entire HTTP/2 connection. An unauthenticated peer can complete 64 TLS handshakes, keep those connections idle, and cause every subsequent connection to be rejected indefinitely.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment thread ldk-server/src/service.rs
let shutdown_rx = self.shutdown_rx.clone();
let (request_parts, request_body) = req.into_parts();
let future: Self::Future = Box::pin(async move {
let body_permit = match REQUEST_BODY_SEMAPHORE.try_acquire() {

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.

[P1] Add a mandatory server-side deadline for request-body collection. This global permit is held while read_request_body(...).await waits without a timeout, and only the presence of x-auth has been checked. One unauthenticated client can leave eight HTTP/2 bodies unfinished and force every legitimate RPC to fail with UNAVAILABLE.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

fixed

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.

The 30-second timeout bounds each individual stalled body, but does not prevent starvation. An unauthenticated client can continuously open replacement streams as the old ones expire, repeatedly occupying all eight global try_acquire slots and keeping legitimate RPCs at UNAVAILABLE. Could we prevent one unauthenticated peer from monopolizing this pool, and add a regression test for sustained saturation?

benthecarman and others added 2 commits September 1, 2026 23:50
Release the handshake permit as soon as the TLS handshake completes.
Holding it for the connection's lifetime let an unauthenticated peer
complete 64 handshakes, keep the connections idle, and block every
later connection indefinitely.

This fixup was created with assistance from Claude Code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Apply a server-side deadline to request body collection. The body-read
permit was held while the body was awaited without a timeout, so a
client that never finished sending could hold one of the eight slots
indefinitely and fail every legitimate RPC with UNAVAILABLE.

This fixup was created with assistance from Claude Code.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@benthecarman

Copy link
Copy Markdown
Collaborator Author

I raised the risk of hand-rolling previously in PR #220. This PR reinforces that. I think we should reconsider this path and consider migrating to tonic.

I asked claude to do an analysis on this while it would have fixed some of these, some of these small issues were on our auth schema which still operates outside of it

@joostjager

Copy link
Copy Markdown
Contributor

I asked claude to do an analysis on this while it would have fixed some of these, some of these small issues were on our auth schema which still operates outside of it

Agreed, tonic would not remove all of our custom authentication logic. My concern is that the recurring issues in these PRs are mostly in transport framing, status and trailer handling, stream lifecycle, and resource limits, which are separate from the auth schema. Even if auth remains custom, there is still an avoidable maintenance burden on the rest.

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.

3 participants