Summary
createPaymentRequestToken signs a payment request into a JWT but never sets an exp claim, and verifyPaymentRequestToken only honors a JWT exp and never reads the expiresAt field from the payload. A signed payment request therefore verifies successfully forever, no matter how far in the past its expiresAt is.
Should vs reality
Should: a signed payment request carrying expiresAt must fail verification after that instant, so a quote cannot be paid later at a stale amount or routed to a stale recipient.
Reality: the payload is spread into the JWT with no expiresIn/exp (create-payment-request-token.ts), and the verifier passes policies.exp to verifyJwt while expiresAt is only schema-parsed as a date field and never compared to the current time (verify-payment-request-token.ts). Receipt verification additionally disables even JWT expiry on the embedded request token (verifyExpiry: false in verify-payment-receipt.ts), and createPaymentReceipt does not set expirationDate unless the caller passes one.
Reproduction (source level)
- Call
createSignedPaymentRequest with expiresAt in the past and a USDC payment option.
- Call
verifyPaymentRequestToken(token, { resolver }).
- Verification succeeds and returns the original amount and recipient.
The demo/example payment paths (demos/payments payment-service, receipt-service, demos/e2e receipt-issuer, examples/issuer receipts route) never compare paymentRequest.expiresAt to now either. Tests only cover a hand-forged JWT exp claim, which the issue path never emits.
Impact
A payment service using this verifier as its only expiry gate honors expired invoices indefinitely. Amount and recipient stay bound (replay only, no amount forgery), but stale quotes remain payable.
Suggested fix
Set exp from expiresAt at issue time, or compare expiresAt to the current time inside verifyPaymentRequestToken, and drop the verifyExpiry: false override in receipt verification (or bound it to the receipt VC's own expiry semantics).
Environment: agentcommercekit/ack at commit b5b25ce (main as of 2026-09-12). Reviewed at source level; no live systems were tested.
Disclosure note: reported to security@agentcommercekit.com per SECURITY.md on 2026-09-14; a maintainer asked for these to be filed publicly, so they are here.
Summary
createPaymentRequestTokensigns a payment request into a JWT but never sets anexpclaim, andverifyPaymentRequestTokenonly honors a JWTexpand never reads theexpiresAtfield from the payload. A signed payment request therefore verifies successfully forever, no matter how far in the past itsexpiresAtis.Should vs reality
Should: a signed payment request carrying
expiresAtmust fail verification after that instant, so a quote cannot be paid later at a stale amount or routed to a stale recipient.Reality: the payload is spread into the JWT with no
expiresIn/exp(create-payment-request-token.ts), and the verifier passespolicies.exptoverifyJwtwhileexpiresAtis only schema-parsed as a date field and never compared to the current time (verify-payment-request-token.ts). Receipt verification additionally disables even JWT expiry on the embedded request token (verifyExpiry: falsein verify-payment-receipt.ts), andcreatePaymentReceiptdoes not setexpirationDateunless the caller passes one.Reproduction (source level)
createSignedPaymentRequestwithexpiresAtin the past and a USDC payment option.verifyPaymentRequestToken(token, { resolver }).The demo/example payment paths (demos/payments payment-service, receipt-service, demos/e2e receipt-issuer, examples/issuer receipts route) never compare
paymentRequest.expiresAtto now either. Tests only cover a hand-forged JWTexpclaim, which the issue path never emits.Impact
A payment service using this verifier as its only expiry gate honors expired invoices indefinitely. Amount and recipient stay bound (replay only, no amount forgery), but stale quotes remain payable.
Suggested fix
Set
expfromexpiresAtat issue time, or compareexpiresAtto the current time insideverifyPaymentRequestToken, and drop theverifyExpiry: falseoverride in receipt verification (or bound it to the receipt VC's own expiry semantics).Environment: agentcommercekit/ack at commit b5b25ce (main as of 2026-09-12). Reviewed at source level; no live systems were tested.
Disclosure note: reported to security@agentcommercekit.com per SECURITY.md on 2026-09-14; a maintainer asked for these to be filed publicly, so they are here.