[{"data":1,"prerenderedAt":708},["ShallowReactive",2],{"blog-vulnerabilities/nuxt-route-rules-auth-bypass":3},{"id":4,"title":5,"body":6,"category":678,"date":679,"dateModified":679,"description":680,"draft":681,"extension":682,"faq":683,"featured":681,"headerVariant":692,"image":693,"keywords":694,"meta":695,"navigation":401,"ogDescription":697,"ogTitle":693,"path":698,"readTime":699,"schemaOrg":700,"schemaType":701,"seo":702,"sitemap":703,"stem":704,"tags":705,"twitterCard":706,"__hash__":707},"blog/blog/vulnerabilities/nuxt-route-rules-auth-bypass.md","Is Your Nuxt Admin Route Actually Protected? (2026 Advisory)",{"type":7,"value":8,"toc":669},"minimark",[9,18,26,58,63,66,69,157,160,163,172,175,179,185,191,201,214,218,221,322,354,436,445,449,460,490,493,510,513,519,523,526,529,538,541,544,548,551,581,584,629,653,665],[10,11,12,13,17],"p",{},"If you have a file called ",[14,15,16],"code",{},"pages/Admin.vue"," in a Nuxt app, and you protect it with a route rule that runs auth middleware, that rule may have quietly stopped matching. Not for attackers. For everyone. The page rendered, the middleware did not run, and nothing in your logs said so.",[10,19,20,21,25],{},"That is the substance of a Nuxt advisory published on ",[22,23,24],"strong",{},"2026-07-27",", tracked as GHSA-hxvh-4h3w-prp9 and rated High at CVSS 8.2. As of this writing it has no CVE ID assigned.",[27,28,29],"tldr",{},[10,30,31,32,35,36,39,40,43,44,47,48,50,51,35,54,57],{},"Nuxt versions ",[22,33,34],{},"4.4.7 to 4.5.0"," and ",[22,37,38],{},"3.21.7 to 3.21.9"," silently drop any route rule whose key contains a capital letter. If you use ",[14,41,42],{},"routeRules"," with ",[14,45,46],{},"appMiddleware"," as your authorization gate, and the key has an uppercase character in it, that gate never ran. Capital letters get in by themselves: Nuxt derives rule keys from PascalCase page files like ",[14,49,16],{},". Fixed in ",[22,52,53],{},"4.5.1",[22,55,56],{},"3.21.10",". The two-minute check is below.",[59,60,62],"h2",{"id":61},"the-two-bugs-point-in-opposite-directions","The two bugs point in opposite directions",[10,64,65],{},"This is the part that trips people up, so it is worth getting straight before you check anything.",[10,67,68],{},"There were two bugs, and the second is the botched fix for the first.",[70,71,72,87],"table",{},[73,74,75],"thead",{},[76,77,78,81,84],"tr",{},[79,80],"th",{},[79,82,83],{},"The original bug",[79,85,86],{},"This one",[88,89,90,102,113,124,146],"tbody",{},[76,91,92,96,99],{},[93,94,95],"td",{},"Advisory",[93,97,98],{},"CVE-2026-53721",[93,100,101],{},"GHSA-hxvh-4h3w-prp9",[76,103,104,107,110],{},[93,105,106],{},"What went wrong",[93,108,109],{},"Route rules matched case-sensitively while the router matched case-insensitively",[93,111,112],{},"The fix lowercased the lookup path but left the rule keys as written",[76,114,115,118,121],{},[93,116,117],{},"Who triggers it",[93,119,120],{},"An attacker, by flipping the case in the URL",[93,122,123],{},"Nobody. It is broken for every request",[76,125,126,129,139],{},[93,127,128],{},"Symptom",[93,130,131,132,135,136],{},"Requesting ",[14,133,134],{},"/Admin/dashboard"," skipped the rule for ",[14,137,138],{},"/admin/dashboard",[93,140,141,142,145],{},"A rule keyed ",[14,143,144],{},"/Admin"," never matches anything",[76,147,148,151,154],{},[93,149,150],{},"Fixed in",[93,152,153],{},"4.4.7 / 3.21.7",[93,155,156],{},"4.5.1 / 3.21.10",[10,158,159],{},"In the original, an attacker had to do something: change the capitalisation of a URL to slip past a rule. Nuxt patched that in 4.4.7 by lowercasing the incoming path before looking up a rule.",[10,161,162],{},"The problem is that it only lowercased one side. Rule keys stayed exactly as written. So from 4.4.7 onward, a lowercased lookup path could never equal a key containing a capital letter, and any such rule simply stopped existing as far as the matcher was concerned.",[164,165,166],"danger-box",{},[10,167,168,169,171],{},"If you read about the first bug and \"fixed\" it by being careful with capitalisation in your route rules, you may have walked directly into the second one. A rule keyed ",[14,170,144],{}," is exactly the shape that stopped matching.",[10,173,174],{},"The advisory's own description of the cause: the earlier fix \"lowercased the lookup path before matching route rules, but the route-rule keys compiled into the matcher were left verbatim. As a result, any route rule whose key contains an uppercase character never matches.\"",[59,176,178],{"id":177},"why-you-can-have-this-without-writing-a-capital-letter","Why you can have this without writing a capital letter",[10,180,181,182,184],{},"Most people reading this are thinking \"I would never key a route rule ",[14,183,144],{},".\" Probably true. It does not matter.",[10,186,187,188,190],{},"Nuxt uses file-based routing, and it derives route rules from your page filenames. The advisory calls this out by name: the affected keys include \"the rules Nuxt derives from PascalCase/camelCase page files such as ",[14,189,16],{},".\"",[10,192,193,194,197,198,200],{},"So the sequence that gets you here is entirely ordinary. You name a page component ",[14,195,196],{},"Admin.vue",", because that is how Vue components are conventionally named. Nuxt turns that into a route rule key with a capital A in it. Your ",[14,199,46],{}," gate hangs off that key. The key never matches. Your admin page is now ungated, and you never typed anything unusual.",[10,202,203,204,206,207,206,210,213],{},"This is worth sitting with if you build with AI tools. Ask Cursor, Claude Code, or Bolt to scaffold a Nuxt admin section and you will get ",[14,205,196],{},", ",[14,208,209],{},"Dashboard.vue",[14,211,212],{},"UserSettings.vue",", because PascalCase is the correct convention for Vue single-file components and the model knows that. The convention is right. It just happened to collide with a broken matcher.",[59,215,217],{"id":216},"the-two-minute-check","The two-minute check",[10,219,220],{},"Do this in order. Stop as soon as one of them clears you.",[222,223,225,230,296],"step",{"number":224},"1",[10,226,227],{},[22,228,229],{},"Check your version.",[231,232,234],"code-block",{"label":233},"Terminal",[235,236,241],"pre",{"className":237,"code":238,"language":239,"meta":240,"style":240},"language-bash shiki shiki-themes github-dark","npx nuxt info | grep -i \"nuxt version\"\n# or\ncat node_modules/nuxt/package.json | grep '\"version\"'\n","bash","",[14,242,243,273,280],{"__ignoreMap":240},[244,245,248,252,256,259,263,266,270],"span",{"class":246,"line":247},"line",1,[244,249,251],{"class":250},"svObZ","npx",[244,253,255],{"class":254},"sU2Wk"," nuxt",[244,257,258],{"class":254}," info",[244,260,262],{"class":261},"snl16"," |",[244,264,265],{"class":250}," grep",[244,267,269],{"class":268},"sDLfK"," -i",[244,271,272],{"class":254}," \"nuxt version\"\n",[244,274,276],{"class":246,"line":275},2,[244,277,279],{"class":278},"sAwPA","# or\n",[244,281,283,286,289,291,293],{"class":246,"line":282},3,[244,284,285],{"class":250},"cat",[244,287,288],{"class":254}," node_modules/nuxt/package.json",[244,290,262],{"class":261},[244,292,265],{"class":250},[244,294,295],{"class":254}," '\"version\"'\n",[10,297,298,299,302,303,306,307,302,310,313,314,317,318,321],{},"You are only in scope for Nuxt ",[14,300,301],{},"4.4.7"," through ",[14,304,305],{},"4.5.0",", or ",[14,308,309],{},"3.21.7",[14,311,312],{},"3.21.9",". Anything older than 4.4.7 or 3.21.7 predates the broken fix. Anything at 4.5.1 or 3.21.10 and up is patched. Note that a caret range like ",[14,315,316],{},"\"nuxt\": \"^4.3.1\""," in ",[14,319,320],{},"package.json"," tells you what you asked for, not what is installed, so check the resolved version.",[222,323,325,330,351],{"number":324},"2",[10,326,327],{},[22,328,329],{},"Check whether you gate anything with route rules.",[231,331,332],{"label":233},[235,333,335],{"className":237,"code":334,"language":239,"meta":240,"style":240},"grep -n \"appMiddleware\" nuxt.config.ts\n",[14,336,337],{"__ignoreMap":240},[244,338,339,342,345,348],{"class":246,"line":247},[244,340,341],{"class":250},"grep",[244,343,344],{"class":268}," -n",[244,346,347],{"class":254}," \"appMiddleware\"",[244,349,350],{"class":254}," nuxt.config.ts\n",[10,352,353],{},"No results means this advisory does not reach you. Route rules that only set caching, headers, or prerendering are not authorization gates, and a rule that silently fails to apply a cache header is a performance bug, not an open admin panel.",[222,355,357,362,433],{"number":356},"3",[10,358,359],{},[22,360,361],{},"Look for capital letters, in both places they can come from.",[231,363,364],{"label":233},[235,365,367],{"className":237,"code":366,"language":239,"meta":240,"style":240},"# rule keys you wrote yourself\ngrep -nE \"^\\s*['\\\"]/[^'\\\"]*[A-Z]\" nuxt.config.ts\n\n# page filenames Nuxt will derive keys from\nfind app/pages pages -name '*[A-Z]*' 2>/dev/null\n",[14,368,369,374,397,403,409],{"__ignoreMap":240},[244,370,371],{"class":246,"line":247},[244,372,373],{"class":278},"# rule keys you wrote yourself\n",[244,375,376,378,381,384,387,390,392,395],{"class":246,"line":275},[244,377,341],{"class":250},[244,379,380],{"class":268}," -nE",[244,382,383],{"class":254}," \"^\\s*['",[244,385,386],{"class":268},"\\\"",[244,388,389],{"class":254},"]/[^'",[244,391,386],{"class":268},[244,393,394],{"class":254},"]*[A-Z]\"",[244,396,350],{"class":254},[244,398,399],{"class":246,"line":282},[244,400,402],{"emptyLinePlaceholder":401},true,"\n",[244,404,406],{"class":246,"line":405},4,[244,407,408],{"class":278},"# page filenames Nuxt will derive keys from\n",[244,410,412,415,418,421,424,427,430],{"class":246,"line":411},5,[244,413,414],{"class":250},"find",[244,416,417],{"class":254}," app/pages",[244,419,420],{"class":254}," pages",[244,422,423],{"class":268}," -name",[244,425,426],{"class":254}," '*[A-Z]*'",[244,428,429],{"class":261}," 2>",[244,431,432],{"class":254},"/dev/null\n",[10,434,435],{},"Anything that comes back from either command, and that you are relying on for access control, is the thing to fix.",[10,437,438,439,441,442,444],{},"We ran this on CheckYourVibe while writing the post, since this site is a Nuxt app. It took about two minutes: our resolved version is below the affected range, we have ",[14,440,42],{}," but none of them carry ",[14,443,46],{},", and we have no uppercase page filenames. Three separate reasons to be clear. That is not us saying the bug is rare, it is us saying the check is genuinely quick.",[59,446,448],{"id":447},"fixing-it","Fixing it",[10,450,451,452,455,456,459],{},"Upgrade. ",[14,453,454],{},"nuxt@4.5.1"," or ",[14,457,458],{},"nuxt@3.21.10",", both published 2026-07-27.",[235,461,463],{"className":237,"code":462,"language":239,"meta":240,"style":240},"npm install nuxt@^4.5.1\n# or, on the 3.x line\nnpm install nuxt@^3.21.10\n",[14,464,465,476,481],{"__ignoreMap":240},[244,466,467,470,473],{"class":246,"line":247},[244,468,469],{"class":250},"npm",[244,471,472],{"class":254}," install",[244,474,475],{"class":254}," nuxt@^4.5.1\n",[244,477,478],{"class":246,"line":275},[244,479,480],{"class":278},"# or, on the 3.x line\n",[244,482,483,485,487],{"class":246,"line":282},[244,484,469],{"class":250},[244,486,472],{"class":254},[244,488,489],{"class":254}," nuxt@^3.21.10\n",[10,491,492],{},"The advisory also lists workarounds if you cannot upgrade immediately:",[494,495,496,500,507],"ul",{},[497,498,499],"li",{},"Use lowercase-only route rule keys and page filenames.",[497,501,502,503,506],{},"Turn on case-sensitive routing with ",[14,504,505],{},"router: { options: { sensitive: true } }",".",[497,508,509],{},"Enforce the authorization server-side, independently of route rules.",[10,511,512],{},"Upgrading is the fix. The third workaround is the one to keep afterwards.",[514,515,516],"warning-box",{},[10,517,518],{},"Upgrading is not the end of it. The advisory explicitly recommends auditing any uppercase route rule keys you use for access control after you patch, because the patch makes those rules start matching again. A rule that has not applied in weeks is about to start applying, and you want to know what it does before it does it.",[59,520,522],{"id":521},"the-part-that-outlives-this-advisory","The part that outlives this advisory",[10,524,525],{},"A specific version of a specific framework had a specific matcher bug, and that will be fixed and forgotten. The structural lesson will not be.",[10,527,528],{},"Route-level middleware is a single checkpoint, and it sits far away from your data. It answers one question, \"is someone logged in,\" and it answers it based on a string comparison against a URL. Anything that breaks that string comparison, a matcher bug, a trailing slash, a case mismatch, a new route somebody added last Tuesday, silently removes your only gate.",[530,531],"diagram",{"alt":532,"caption":533,"height":534,"href":535,"src":536,"width":537},"A flowchart. A request from the browser reaches Checkpoint 1, which only establishes who is logged in, then Checkpoint 2, which decides whether this particular post belongs to them. Failing either one refuses the request, while hiding the admin button in the browser stops nothing.","Two checkpoints stand between a request and a row. This advisory removed the first one.",1170,"/blog/diagrams/auth-vs-authz-checkpoints","auth-vs-authz-checkpoints",1326,[10,539,540],{},"An app with an ownership check next to its data, or row-level security in the database, degrades differently. When the route rule stops matching, the request still reaches a handler that asks whether this caller may have this record, and the answer is still no. You lose a layer instead of losing the wall.",[10,542,543],{},"That is the actual takeaway, and it is why this advisory is worth more than a version bump: it is a live demonstration that a check written in your routing config is a check that can be turned off by someone else's refactor.",[59,545,547],{"id":546},"the-rest-of-the-2026-07-27-batch","The rest of the 2026-07-27 batch",[10,549,550],{},"This advisory did not ship alone. Nuxt released 8 GHSAs across 7 listed items that day, patched in the same 4.5.1 and 3.21.10 releases. Two others are worth knowing about:",[494,552,553,565],{},[497,554,555,561,562,564],{},[22,556,557,558],{},"A critical remote code execution in ",[14,559,560],{},"@nuxt/devtools",", GHSA-279x-mwfv-vcqv. Development-only, so it does not affect your deployed site, but it does affect your laptop. Patched in ",[14,563,560],{}," 3.3.1.",[497,566,567,570,571,206,574,306,577,580],{},[22,568,569],{},"Cross-user payload disclosure on cached pages",", GHSA-wm8w-6qjm-cv43, on the 4.x line. If you put ",[14,572,573],{},"cache",[14,575,576],{},"swr",[14,578,579],{},"isr"," route rules on authenticated pages that render user-specific data, one user's rendered data could be served to another.",[10,582,583],{},"That last one deserves its own look if you cache anything behind a login. Caching a page that contains somebody's name is how a personalised page becomes everybody's page.",[585,586,587,599,608,614,623],"faq-section",{},[588,589,591],"faq-item",{"question":590},"How do I know if my Nuxt app is affected?",[10,592,593,594,43,596,598],{},"Three things all have to be true. You are running Nuxt 4.4.7 to 4.5.0, or 3.21.7 to 3.21.9. You use ",[14,595,42],{},[14,597,46],{}," as an authorization gate. And at least one of those rule keys contains a capital letter, either because you wrote it that way or because Nuxt derived it from a PascalCase page file. If any one of those is false, this particular advisory does not apply to you.",[588,600,602],{"question":601},"I never typed an uppercase route rule. Am I safe?",[10,603,604,605,607],{},"Not necessarily. The advisory specifically names the rules Nuxt derives from PascalCase and camelCase page files, such as ",[14,606,16],{},". If you have a page file with a capital letter in its name and you are gating it through route rules, you got an uppercase key without ever writing one.",[588,609,611],{"question":610},"What version fixes it?",[10,612,613],{},"Nuxt 4.5.1 or 3.21.10, both released 2026-07-27. The matcher now case-folds the rule keys the same way it folds the lookup path, so the two are normalised symmetrically. After upgrading, the advisory still recommends auditing any uppercase route rule keys you use for access control.",[588,615,617],{"question":616},"Can I fix it without upgrading right now?",[10,618,619,620,622],{},"The advisory lists three workarounds: use lowercase-only route rule keys and page filenames, turn on case-sensitive routing with ",[14,621,505],{},", or enforce the authorization server-side so it does not depend on route rules at all. The third one is the one worth keeping permanently.",[588,624,626],{"question":625},"Was this the same bug as the earlier Nuxt route rule CVE?",[10,627,628],{},"No, and the direction is opposite, which is what makes it confusing. The earlier bug (CVE-2026-53721) let an attacker flip the case of a URL segment to dodge a rule. This one is the incomplete fix for that: it lowercased the lookup path but left the rule keys alone, so a rule with a capital letter in its key stopped matching anything at all, for every visitor, with no attacker involved.",[630,631,632,638,643,648],"related-articles",{},[633,634],"related-card",{"description":635,"href":636,"title":637},"Where the auth check goes when you stop trusting your routing config to hold it","/blog/how-to/protect-routes","How to Protect Routes Properly",[633,639],{"description":640,"href":641,"title":642},"The category this belongs to, and the other shapes it takes","/blog/vulnerabilities/broken-access-control","Broken Access Control",[633,644],{"description":645,"href":646,"title":647},"What an attacker does once a route gate is missing and IDs are guessable","/blog/vulnerabilities/idor","IDOR Explained",[633,649],{"description":650,"href":651,"title":652},"The same failure shape in Next.js, eight days earlier, from a single i18n locale","/blog/vulnerabilities/nextjs-middleware-bypass","Next.js Middleware Bypass (CVE-2026-64642)",[654,655,658,662],"cta-box",{"href":656,"label":657},"/","Scan Your App Free",[59,659,661],{"id":660},"is-anything-answering-that-shouldnt-be","Is anything answering that shouldn't be?",[10,663,664],{},"A scan requests your routes from outside, signed in as nobody, and reports what comes back. 60 seconds, no signup.",[666,667,668],"style",{},"html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":240,"searchDepth":275,"depth":275,"links":670},[671,672,673,674,675,676,677],{"id":61,"depth":275,"text":62},{"id":177,"depth":275,"text":178},{"id":216,"depth":275,"text":217},{"id":447,"depth":275,"text":448},{"id":521,"depth":275,"text":522},{"id":546,"depth":275,"text":547},{"id":660,"depth":275,"text":661},"vulnerabilities","2026-08-01","A Nuxt advisory published 2026-07-27 means any route rule with a capital letter in its key never matched, so the middleware guarding it never ran. Here is the check.",false,"md",[684,686,688,689,691],{"question":590,"answer":685},"Three things all have to be true. You are running Nuxt 4.4.7 to 4.5.0, or 3.21.7 to 3.21.9. You use routeRules with appMiddleware as an authorization gate. And at least one of those rule keys contains a capital letter, either because you wrote it that way or because Nuxt derived it from a PascalCase page file. If any one of those is false, this particular advisory does not apply to you.",{"question":601,"answer":687},"Not necessarily. The advisory specifically names the rules Nuxt derives from PascalCase and camelCase page files, such as pages/Admin.vue. If you have a page file with a capital letter in its name and you are gating it through route rules, you got an uppercase key without ever writing one.",{"question":610,"answer":613},{"question":616,"answer":690},"The advisory lists three workarounds: use lowercase-only route rule keys and page filenames, turn on case-sensitive routing with router options sensitive set to true, or enforce the authorization server-side so it does not depend on route rules at all. The third one is the one worth keeping permanently.",{"question":625,"answer":628},"red",null,"nuxt route rules not matching, nuxt middleware not running, nuxt admin route not protected, nuxt security advisory 2026, nuxt 4.5.1, nuxt appMiddleware bypass, nuxt routeRules uppercase",{"trendTrigger":696,"trendDate":24},"Nuxt security advisory GHSA-hxvh-4h3w-prp9, route rules with uppercase keys silently dropped, bypassing appMiddleware auth gates","If you gate a Nuxt page with routeRules and appMiddleware, and the rule key has a capital letter in it, the rule never matched. Patched in 4.5.1 and 3.21.10.","/blog/vulnerabilities/nuxt-route-rules-auth-bypass","8 min read","[object Object]","TechArticle",{"title":5,"description":680},{"loc":698},"blog/vulnerabilities/nuxt-route-rules-auth-bypass",[],"summary_large_image","AIQN6cUxB14YQwTNI1qLfwFbHkhtOCa75YIYkZa4aBE",1785700548946]