Skip to content

feat: enforce invoiceRateLimit on 402 challenge path (4.7.0)#35

Merged
TheCryptoDonkey merged 13 commits into
mainfrom
feat/invoice-rate-limit-402-path
May 23, 2026
Merged

feat: enforce invoiceRateLimit on 402 challenge path (4.7.0)#35
TheCryptoDonkey merged 13 commits into
mainfrom
feat/invoice-rate-limit-402-path

Conversation

@TheCryptoDonkey
Copy link
Copy Markdown
Member

Summary

The invoiceRateLimit.maxPendingPerIp cap previously only fired in the explicit POST /create-invoice handler. Any priced endpoint could still mint unbounded unpaid invoices via the 402 challenge code path — confirmed in the wild: a single IP hammered /route on routing.trotters.cc and created 253 unpaid invoices in 35 seconds.

This branch extends the cap to the 402 path, hoists the check before backend.createInvoice is called (so rate-limited IPs do not even touch the LSP), and exposes INVOICE_MAX_AGE_MS on the valhalla-proxy example for faster auto-prune.

Changes

  • TollBoothCoreConfig gains invoiceRateLimit?: { maxPendingPerIp: number } — forwarded from BoothConfig.
  • issueChallenge() short-circuits with HTTP 429 + Retry-After: 3600 when pendingInvoiceCount(ipHash) >= maxPendingPerIp. Same error string as the existing /create-invoice 429.
  • Express, Web-Standard, and Hono adapters now forward result.status instead of hard-coding 402, so the 429 actually reaches the wire.
  • Public TollBoothResult challenge variant: status: 401 | 402 | 429.
  • examples/valhalla-proxy/server.ts reads INVOICE_MAX_AGE_MS (default 1h in the example; library default unchanged at 24h).
  • README + CLAUDE.md updated.
  • 4 new engine tests + 1 backend-not-called test + 3 adapter tests.

Test plan

  • npm run typecheck — clean
  • npm run build — clean
  • npm test — 860 passing, 32 skipped, 0 failing
  • CI passes
  • Deploy to routing.trotters.cc; verify 11th request from same IP returns 429

Adds a call-count assertion to the invoiceRateLimit suite: after
maxPendingPerIp requests, the (N+1)th is blocked with 429 and
backend.createInvoice call count remains exactly N.
…ing 402

Express, Web-Standard, and Hono adapters now use result.status from the
discriminated union instead of a literal 402, so rate-limited IPs receive
HTTP 429 as the engine intends. Adds a sibling 429 test in each adapter
test file to cover the invoice-rate-limit challenge path.
- CHANGELOG: note TollBoothResult challenge status union widened to include 429
- CHANGELOG: clarify INVOICE_MAX_AGE_MS default is 1 hour in the example;
  library default remains 24 hours
- CLAUDE.md: same INVOICE_MAX_AGE_MS scope clarification in env-vars table
Add a sentence to the production checklist noting that
invoiceRateLimit.maxPendingPerIp caps both /create-invoice and the
inline 402 challenge, returning 429 with Retry-After when exceeded.
@TheCryptoDonkey TheCryptoDonkey merged commit 68b1ff9 into main May 23, 2026
7 checks passed
@TheCryptoDonkey TheCryptoDonkey deleted the feat/invoice-rate-limit-402-path branch May 23, 2026 14:14
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.

1 participant