Skip to content

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

@chenshj73

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions