fix(backend): require HTTP message signature on GET /outgoing-payment-grant - #3962
Merged
mkurapov merged 2 commits intoAug 11, 2026
Merged
Conversation
…-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
✅ Deploy Preview for brilliant-pasca-3e80ec canceled.
|
mkurapov
reviewed
Aug 11, 2026
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. |
Contributor
There was a problem hiding this comment.
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 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. |
Contributor
There was a problem hiding this comment.
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.
Contributor
Author
|
Thanks @mkurapov, applied the comment tidy-ups in the latest commit (shortened the |
mkurapov
approved these changes
Aug 11, 2026
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).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GET /:tenantId/outgoing-payment-grantis wired with exactly two handlers ,createOutgoingPaymentGrantTokenIntrospectionMiddleware()andoutgoingPaymentRoutes.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 withhttpsigMiddleware.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-Inputheaders, 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.brucallsscripts.addSignatureHeaders()in its pre-request script , clients sign; the server just never verified.Fix
httpsigMiddlewareto the route, matching every sibling route.ctx.clientfrom the introspection result increateOutgoingPaymentGrantTokenIntrospectionMiddleware(ascreateTokenIntrospectionMiddlewarealready does), whichhttpsigMiddlewareneeds to resolve the caller's key.SignedGrantContext(GrantContext & HttpSigContext) and type the route with it, so the wiring is checked at compile time.ctx.clientis populated.Verification
tsc --noEmiton 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 newctx.clientassignment.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.