Skip to content

Potential paid-cookie scope issue: auth_token is not bound to route or payment context #24

Description

@chenshj73

Hi, I noticed a possible paid-cookie scope issue in the proxy.

The JWT payload in src/jwt.ts only records that a payment happened and when it expires:

6: export interface JWTPayload {
7:   paid: boolean; // indicates payment was verified
8:   iat: number; // issued at (seconds since epoch)
9:   exp: number; // expires at (seconds since epoch)
10: }

When a JWT is generated, it contains paid: true but no route, amount, recipient, resource, network, or payment method scope:

89:   // JWT Payload
90:   const payload: JWTPayload = {
91:     paid: true,
92:     iat: now,
93:     exp: now + expiresInSeconds,
94:   };
115:   return `${dataToSign}.${encodedSignature}`;

In src/auth.ts, a valid cookie skips payment middleware entirely:

21: export function requirePaymentOrCookie(paymentMw: MiddlewareHandler) {
23:     // Check for valid cookie
24:     const token = getCookie(c, "auth_token");
40:       const payload = await verifyJWT(token, jwtSecret);
42:       // If token is valid, skip payment and go directly to handler
43:       if (payload) {
44:         c.set("auth", payload);
45:         await next(); // Call the handler
46:         return;
47:       }
50:     // No valid cookie - apply payment middleware
51:     return await paymentMw(c, next);

Each protected route can have its own amount/description:

81: export function createProtectedRoute(config: ProtectedRouteConfig) {
100:     const paymentMw = payment(mppx.charge, {
101:       amount: config.amount,
102:       description: config.description,
103:     });
106:     return await requirePaymentOrCookie(paymentMw)(c, next);

In src/index.ts, the token is generated after the protected middleware accepts a new payment and then set as a path-wide cookie:

166:     const result = await protectedMiddleware(c, async () => {
168:       const hasExistingAuth = c.get("auth");
170:       if (!hasExistingAuth) {
171:         // This is a new payment - generate JWT cookie
174:         jwtToken = await generateJWT(c.env.JWT_SECRET, 3600);
175:       }
...
219:       if (jwtToken) {
220:         setCookie(c, "auth_token", jwtToken, {
224:           maxAge: 3600,
225:           path: "/",
226:         });

The resulting authorization path appears to be:

first paid request -> auth_token(paid=true) -> later protected route -> skip payment middleware

I did not see the cookie scoped to the route pattern, amount, recipient, resource, or network that produced it.

This may matter when a deployment has more than one protected route or price. A cookie minted for one protected path could be accepted for another protected path until expiry.

A safer design would include route/payment context claims in the JWT and compare them against the current protected route before skipping payment middleware. I am reporting this as a potential issue rather than a confirmed exploit, since a single-route deployment with one price would reduce the risk.

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