Skip to content

ACK-Pay: signed payment request tokens never expire (expiresAt is never enforced) #222

Description

@byfor8

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)

  1. Call createSignedPaymentRequest with expiresAt in the past and a USDC payment option.
  2. Call verifyPaymentRequestToken(token, { resolver }).
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions