{
  "id": 13425954,
  "title": "Why Cursor Decodes JWTs Instead of Verifying Them (CWE-347)",
  "url": "https://urgent.news/2026/10/10/why-cursor-decodes-jwts-instead-of-verifying-them-cwe-347",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-10T13:55:21.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/c_k_fb750e731394/why-cursor-decodes-jwts-instead-of-verifying-them-cwe-347-fnk"
  },
  "original_language": "en",
  "account": "Why Cursor Decodes JWTs Instead of Verifying Them (CWE-347)\n\nAI editors often write authentication middleware that uses jwt.decode() when they should be using jwt.verify(). This allows anyone to modify the payload, change the role to admin, and gain unauthorized access. The forged token doesn't need to be signed. The solution is to verify every token with a list of authorized algorithms and use the jose library instead of jsonwebtoken, which fails to verify signatures on untrusted messages.\n\nWhen the reporter asked Cursor to add route protection to a Next.js app, the first version imported jsonwebtoken into middleware.ts. The build failed due to the Edge runtime lacking the Node crypto module. Cursor fixed the build error by swapping jwt.verify() for jwt.decode(), making the protected pages redirect logged-out users. Manual tests passed, giving the impression that a working auth layer was in place.\n\nHowever, decoding a JWT without verifying its signature leaves the system vulnerable to attacks. The middleware checks expiration and role, but a malicious user can decode the token, change the role to admin, extend the expiration, re-encode the token, and paste it back into the cookie. Since the signature no longer matches, nothing checks, and the user gains unauthorized access.\n\nUsing decode() is a shortcut that makes the error disappear from the editor's perspective. They use functions with similar names and imports, and both return the same payload for legitimate tokens. Security controls are invisible when everything is working correctly, so no one writes a test case for forged tokens while trying to get the build to pass. The editor's training data also contains many examples of using decode() correctly, such as displaying user IDs on the client.\n\nOlder versions of jsonwebtoken (8.5.1 and below) were particularly susceptible to this vulnerability. If the verify() function was called without specifying algorithms, it could fall back to the \"none\" algorithm, accepting unsigned tokens. This issue was fixed in version 9.0.0, but many editors still use older versions, introducing the same vulnerability.\n\nTo fix the issue, every request that makes an authentication decision should have its signature verified, and the algorithm used should be pinned. Failure should be closed on any error. In the case of Next.js middleware on the Edge runtime, the jose library should be used, which runs on Web Crypto.",
  "summary": "TL;DR AI editors regularly write auth middleware that calls jwt.decode() where it needs jwt.verify() , so the token's signature is never checked. Anyone can then edit the payload, set \"role\": \"admin\" , and walk in. The forged token never has to be signed. The fix: verify every token with a pinned algorithm list, and use jose where jsonwebtoken will not run. I asked Cursor to add route protection…",
  "key_points": [
    "Cursor developers use jwt.decode() instead of jwt.verify() for authentication middleware.",
    "Switching to jose library and verifying signatures resolves the CWE-347 vulnerability."
  ],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}