· via dev.to (home feed)
Cursor build fix swaps jwt.verify() for jwt.decode(), letting forged admin tokens through
A developer on dev.to shows how Cursor resolved an Edge-runtime build error by replacing jwt.verify() with jwt.decode(), producing middleware that never checks signatures and accepts forged admin tokens.

A developer writing on dev.to has documented how a one-word change from Cursor produced a serious authentication flaw: asked to fix a failed build, the editor replaced jwt.verify() with jwt.decode() in Next.js middleware, and the app began accepting session tokens whose signatures were never checked.
How the bug appeared
According to the post, the developer asked Cursor to add route protection to a small Next.js app. Cursor's first attempt imported the webtoken package into middleware.ts, and the build failed because the Edge runtime that executes Next.js middleware does not provide Node's crypto module. Asked to make the error go away, Cursor swapped in decode(). The resulting middleware still checked token expiry and still compared the payload's role claim before allowing access to admin routes — but it read those claims with a function that performs no signature validation.
The post classifies this as CWE-347, improper verification of a cryptographic signature. jwt.decode() returns the payload as written, so any authorization decision built on it trusts data the client fully controls. Exploitation requires no cryptographic skill: a JWT is three base64url segments, and the middle one is readable and editable by anyone. Take a genuine session cookie, change "role": "user" to "admin", push the expiry out a year, re-encode it, and paste it back. The trailing signature no longer matches the tampered payload, and nothing in the middleware inspects it.
The author notes that the webtoken README itself warns that decode performs no signature validation and must not be used on untrusted input — and that every cookie qualifies as untrusted. A Python variant shows up when a developer hits a PyJWT DecodeError and asks an editor to resolve it: the editor emits jwt.decode(token, options={"verify_signature": False}), an option PyJWT documents for reading claims you never intend to trust.
Why the swap keeps happening
The post attributes the recurring pattern to three reinforcing causes. The runtime constraint is real: webtoken cannot bundle for the Edge runtime, and the correct response — switching to the Web Crypto-based jose library — is more work than calling a different function in the same package. Ordinary tests cannot tell the calls apart: a test using a legitimately signed token gets an identical payload from decode() and verify(), and they diverge only on forged tokens, which nobody writes while trying to get a build passing. And the training data contains abundant legitimate uses of decode(), such as client-side snippets that display a username or log a token ID, so the model learns the call without the context that keeps it safe. More broadly, the author argues, editors optimize for making the specific error in front of them disappear, and this swap is the shortest path from a throwing call to a running one.
The post also links the pattern to two older advisories in webtoken 8.5.1 and below. CVE-2022-23540 allowed verify() called without an algorithms option and with a falsy key — an unset environment variable, for example — to fall back to the none algorithm and accept unsigned tokens. CVE-2022-23541 let RS256 tokens be verified as HS256 when a single key-lookup function served both key types. Both were fixed in 9.0.0, but an editor that pins ^8.5.1 in package. reintroduces them.
The recommended fix
The author's guidance is direct: every request that makes an authorization decision must verify the signature, pin the algorithm explicitly, and fail closed on any error. For Next.js Edge middleware that means jose and its jwtVerify(), with issuer and audience checks, plus a hard failure at startup when the secret is unset. In ordinary Node route handlers, webtoken 9.x with a pinned algorithms list is fine; in Python, pass the key and an explicit algorithm list rather than disabling verification. Decoding is for display, never for access control.
Two practices stand out. Throwing on a missing secret removes the falsy-key path that CVE-2022-23540 required. And the forged-token test — mutate one claim on a valid token, re-encode it, assert a 401 or a redirect — is the only test that can distinguish decode() from verify(). As a quick audit, the author suggests searching a repository for decode( and verify_signature in any file that makes access decisions.
Why it matters
This is a security failure shaped by AI-assisted development specifically. The change is small and plausible, it resolves the error that was reported, it survives every reasonable test, and it silently removes a control whose absence is invisible when things work. As editors gain autonomy over build failures, the gap between a green build and auth code that actually verifies anything becomes a systematic risk rather than a one-off mistake — and the forged-token test is currently the only reliable tripwire.
- #security
- #jwt
- #ai-code-generation
- #cursor
- #next-js