cloudflare / cloudflare/mpp-proxy
Potential paid-cookie scope issue: auth_token is not bound to route or payment context
- Dominant language
- TypeScript
- Stars
- 65
- Forks
- 11
- PR merge metrics
- No merged PRs in 30d
Description
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:
```ts
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:
```ts
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:
```ts
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:
```ts
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:
```ts
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:
```text
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.
Contributor guide
Assessment
This issue has not been assessed yet.