Skip to content

fix(backend): require HTTP message signature on GET /outgoing-payment-grant - #3962

Merged
mkurapov merged 2 commits into
interledger:mainfrom
SashaMIT:fix/outgoing-payment-grant-httpsig
Aug 11, 2026
Merged

fix(backend): require HTTP message signature on GET /outgoing-payment-grant#3962
mkurapov merged 2 commits into
interledger:mainfrom
SashaMIT:fix/outgoing-payment-grant-httpsig

Conversation

@SashaMIT

@SashaMIT SashaMIT commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Problem

GET /:tenantId/outgoing-payment-grant is wired with exactly two handlers , createOutgoingPaymentGrantTokenIntrospectionMiddleware() and outgoingPaymentRoutes.getGrantSpentAmounts , while every sibling token-protected Open Payments route in the same router (incoming-payments create/list/get/complete, outgoing-payments create/list/get, quotes create/get) pairs its introspection middleware with httpsigMiddleware.

Open Payments binds the access token to the client's key and requires each request to carry an HTTP Message Signature, so possession of the token alone is not supposed to be sufficient. On this endpoint it was: a leaked GNAP access token for outgoing-payment create access could be replayed bearer-style, with no Signature/Signature-Input headers, to read the grant's spent debit and receive amounts , and the endpoint doubles as an oracle confirming a stolen token is still active.

The intended contract is visible in the repo itself: bruno/collections/Rafiki/Open Payments APIs/Outgoing Payment Grant/Get Outgoing Payment Grant.bru calls scripts.addSignatureHeaders() in its pre-request script , clients sign; the server just never verified.

Fix

  • Add httpsigMiddleware to the route, matching every sibling route.
  • Set ctx.client from the introspection result in createOutgoingPaymentGrantTokenIntrospectionMiddleware (as createTokenIntrospectionMiddleware already does), which httpsigMiddleware needs to resolve the caller's key.
  • Introduce SignedGrantContext (GrantContext & HttpSigContext) and type the route with it, so the wiring is checked at compile time.
  • Extend the middleware's success-path test to assert ctx.client is populated.

Verification

tsc --noEmit on the backend package is at exact parity with the unpatched baseline (10 pre-existing errors, none in touched files). Jest suites require the Postgres/Redis testcontainers environment, which isn't available here; the middleware success-path test was extended to cover the new ctx.client assignment.

If the omission was deliberate (e.g. the endpoint is considered low-sensitivity), happy to close, but the asymmetry with every sibling route and the repo's own signed client request suggest it was an oversight when the endpoint was added.

…-grant

The route was wired with only the grant token introspection middleware,
while every sibling token-protected Open Payments route (incoming-payments,
outgoing-payments, quotes) pairs introspection with httpsigMiddleware.
Open Payments binds the access token to the client's key, so possession of
the token alone is not supposed to be sufficient: the client's own request
definition (bruno Get Outgoing Payment Grant.bru) signs the request, but
the server never verified it.

Without the signature check, a leaked GNAP access token for
outgoing-payment create access can be replayed bearer-style to read the
grant's spent debit/receive amounts, and the endpoint doubles as an oracle
for whether a stolen token is still active.

- add httpsigMiddleware to the route
- set ctx.client from the introspection result in
  createOutgoingPaymentGrantTokenIntrospectionMiddleware (as
  createTokenIntrospectionMiddleware does), which httpsigMiddleware needs
  to resolve the caller's key
- introduce SignedGrantContext and type the route with it
- assert ctx.client is populated in the middleware's success-path test
@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for brilliant-pasca-3e80ec canceled.

Name Link
🔨 Latest commit a4a68d4
🔍 Latest deploy log https://app.netlify.com/projects/brilliant-pasca-3e80ec/deploys/6a7ad543ea3f55000828f8ae

@github-actions github-actions Bot added type: tests Testing related pkg: backend Changes in the backend package. type: source Changes business logic labels Aug 7, 2026

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

Thank you for the contribution, @SashaMIT!
Added minor feedback to tidy up the comments

Comment on lines +150 to +152
// httpsigMiddleware resolves the caller's key from ctx.client; set it
// exactly as createTokenIntrospectionMiddleware does for every sibling
// route.

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.

Suggested change
// httpsigMiddleware resolves the caller's key from ctx.client; set it
// exactly as createTokenIntrospectionMiddleware does for every sibling
// route.
// httpsigMiddleware resolves the caller's key from ctx.client

Comment thread packages/backend/src/open_payments/auth/middleware.test.ts
Comment thread packages/backend/src/app.ts Outdated
Comment on lines +733 to +735
// Every other token-protected Open Payments route pairs introspection
// with HTTP message signature verification; possession of the access
// token alone is not supposed to be sufficient to call this endpoint.

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.

Suggested change
// Every other token-protected Open Payments route pairs introspection
// with HTTP message signature verification; possession of the access
// token alone is not supposed to be sufficient to call this endpoint.

…ring

Address review feedback: keep the ctx.client note brief and drop the
route-level rationale comment now that the middleware pairing is clear.
@SashaMIT

Copy link
Copy Markdown
Contributor Author

Thanks @mkurapov, applied the comment tidy-ups in the latest commit (shortened the ctx.client notes and removed the route-level rationale comment). Happy to adjust further if anything else stands out.

@mkurapov
mkurapov merged commit 5bda456 into interledger:main Aug 11, 2026
37 of 58 checks passed
SashaMIT added a commit to SashaMIT/oss-contributions that referenced this pull request Aug 11, 2026
Merged fix requiring HTTP message signature on GET
/outgoing-payment-grant (Open Payments parity).
SashaMIT added a commit to SashaMIT/oss-contributions that referenced this pull request Aug 12, 2026
Merged fix requiring HTTP message signature on GET
/outgoing-payment-grant (Open Payments parity).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: backend Changes in the backend package. type: source Changes business logic type: tests Testing related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants