CVE Writeup
GHSA-vqx2 | Clerk SDK Espone Ogni Route Protetta da Middleware
Analisi tecnica di GHSA-vqx2-fgx2-5wq9, bypass critico (CVSS 9.1) nel matcher di Clerk per Next.js, Nuxt, Astro. Come riconoscerlo, come mitigarlo, perché il middleware da solo non basta.
Se hai un SaaS in Next.js con Clerk davanti, oggi è il giorno in cui aggiorni il package.json senza aspettare lo standup. L'advisory GHSA-vqx2-fgx2-5wq9 pubblicato il 15 aprile 2026 descrive un bypass del gate di auth a livello middleware in tutti gli SDK ufficiali Clerk per Next.js, Nuxt, Astro e shared. CVSS 9.1. Zero privilegi richiesti, zero interazione utente, rete. Il fix è drop in replacement, ma richiede un bump immediato e qualche controllo extra nei route handler.
Durante un assessment la scorsa settimana su una fintech italiana avevamo marcato come finding medio un'architettura che gestiva l'auth solo nel middleware.ts. Ieri quel medio è diventato un critico senza muovere una riga di codice. Racconto sotto perché, e come verificare se siete esposti.
Contesto
Clerk è il provider auth di riferimento per lo stack moderno Next.js, Nuxt e Astro. La composizione tipica è questa: un middleware al bordo dell'applicazione intercetta ogni richiesta, decide se il path richiede utente autenticato e chiama auth.protect() in quel caso. Tutto il resto (API routes, server components, server actions) si fida del gate e va avanti.
L'advisory è stato aperto il 13 aprile 2026 da Christiaan Swiers con responsible disclosure e chiuso il 15 con le patch pubblicate. Nessun CVE numerico al momento, ma l'identificativo GHSA è già censito da GitHub Security Database e da tutti gli scanner SCA.
Pacchetti e versioni
| Pacchetto | Versioni vulnerabili | Patch |
|---|---|---|
| @clerk/nextjs | da 5.0.0 a 5.7.5, da 6.0.0 a 6.39.1, da 7.0.0 a 7.2.0 | 5.7.6, 6.39.2, 7.2.1 |
| @clerk/nuxt | da 1.1.0 a 1.13.27, da 2.0.0 a 2.2.1 | 1.13.28, 2.2.2 |
| @clerk/astro | da 0.0.1 a 1.5.6, da 2.0.0 a 2.17.9, da 3.0.0 a 3.0.14 | 1.5.7, 2.17.10, 3.0.15 |
| @clerk/shared | da 2.20.17 a 2.22.0, da 3.0.0 a 3.47.3, da 4.0.0 a 4.8.0 | 2.22.1, 3.47.4, 4.8.1 |
Il range è enorme perché la funzione incriminata è in @clerk/shared, trascinata come peer di tutti gli SDK framework. Aggiornare il solo @clerk/nextjs senza bumpare @clerk/shared nel lockfile lascia il problema attivo.
Analisi tecnica
La funzione incriminata
Il cuore del gate è createRouteMatcher, una factory che costruisce una funzione booleana a partire da un elenco di pattern:
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtectedRoute = createRouteMatcher(['/admin(.*)', '/api/internal/(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) {
await auth.protect();
}
});
Il matcher confronta il path della req con i pattern. Se matcha, auth.protect() verifica la sessione. Se non matcha, la richiesta passa senza controlli.
Il disallineamento
Il bug è una classica interpretation conflict (CWE-436). Clerk normalizza il path in un modo. Il router sottostante (Next.js app router, Nuxt router, Astro integration) lo normalizza in un altro. Quando le due normalizzazioni divergono l'attaccante può costruire una richiesta che:
- al matcher Clerk non sembra protetta,
- al router arriva come se fosse la route protetta.
Nei test interni SPECTROSEC abbiamo ricostruito il pattern con tre vettori plausibili che cadono dentro il profilo "interpretation conflict":
- Percent encoding parziale:
/%61dmin/dashboard| il matcher non riconosce/adminperché vede%61, il router decodifica e risolve/admin/dashboard - Slash terminante + query:
/admin/./?| normalizzazione divergente tra regex matcher e router - Prefisso di route group: gruppi Next.js tipo
(dashboard)che il matcher non tocca ma che il router risolve
L'advisory ufficiale non pubblica il payload esatto per responsabilità verso chi non ha ancora aggiornato. Il concetto è questo: qualunque normalizzazione del path che il vostro matcher fa in modo diverso dal vostro router è potenzialmente un bypass.
Cosa resta protetto
Un punto importante, spesso sottovalutato in letture superficiali. L'advisory è chiaro: la session integrity non è compromessa, l'impersonation non è possibile, i token JWT sono validati correttamente quando vengono effettivamente controllati. Se dentro il route handler chiamate:
// app/admin/dashboard/page.tsx
import { auth } from '@clerk/nextjs/server';
export default async function Page() {
const { userId } = await auth();
if (!userId) throw new Error('Unauthorized');
// ...
}
quella verifica continua a funzionare. Il problema è che la maggior parte dei progetti SaaS che abbiamo visto delega l'auth esclusivamente al middleware perché Clerk stesso lo vende come il punto elegante. Meno codice, meno duplicazione, più pulizia.
Nel momento in cui il middleware può essere bypassato, tutto il codice dietro diventa esposto.
Proof of concept
Lo schema per replicare in locale senza toccare produzione è semplice:
# 1. Crea un progetto Next.js con Clerk
npx create-next-app@latest clerk-lab && cd clerk-lab
npm install @clerk/nextjs@6.39.1
# 2. Configura un route protetto solo da middleware
cat > middleware.ts <<'EOF'
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isProtected = createRouteMatcher(['/admin(.*)']);
export default clerkMiddleware(async (auth, req) => {
if (isProtected(req)) await auth.protect();
});
EOF
# 3. Crea la route /admin senza auth() ridondante
mkdir -p app/admin
cat > app/admin/page.tsx <<'EOF'
export default function Admin() {
return <pre>secret area</pre>;
}
EOF
# 4. Avvia e prova vettori di path crafting
npm run dev
curl -s http://localhost:3000/admin # blocca (atteso)
curl -s http://localhost:3000/%61dmin # verifica se bypassa
La varianza esatta dipende dalla versione di Next.js e dagli hook Clerk attivi. Il lavoro di pentest serio è enumerare sistematicamente i pattern normalization del router sul cliente specifico, non fare uno script universale.
Come verificare un SaaS in produzione senza exploit attivo
Durante un assessment passivo basta guardare tre cose nel repo del cliente:
- Pin delle versioni:
cat package-lock.json | jq '.packages["node_modules/@clerk/shared"].version'. Qualunque cosa nel range vulnerabile è un problema. - Assenza di auth ridondante:
grep -rn "auth()" app/ src/ | grep -v middleware. Se il progetto ha molto middleware e pochissime chiamateauth()dentro le route, il rischio è alto. - Shape del matcher:
grep -rn "createRouteMatcher" .. Pattern molto ampi come['(.*)']o['/api/(.*)']amplificano la superficie.
Questa triade la infiliamo in ogni checklist di code review per progetti SaaS auth-first dal lunedì in poi.
Impatto reale
Gli scenari concreti che dovete immaginare dipendono dal prodotto. Tre esempi dal nostro portfolio di clienti recenti.
Fintech italiana B2B. Il middleware proteggeva /api/internal/* che include gli endpoint di payout. Senza fix, un attaccante con un path crafted può interrogare saldo dei merchant e forse iniziare un payout. Con auth() duplicato nell'handler il payout richiede comunque validazione, ma l'information disclosure sui saldi è critica.
Piattaforma e-learning per un'azienda di formazione corporate. Il middleware proteggeva /admin/users/* da cui si possono dumpare i dati anagrafici dei dipendenti corsisti. Violazione GDPR Art. 32 se sfruttata, segnalazione al Garante plausibile.
SaaS per studi legali. Middleware gate su /admin/clients/*. I dati lì dentro sono copie di fascicoli giudiziari. CVSS confidentiality alto è un eufemismo.
La lezione, che ripetiamo da anni agli sviluppatori, è che il perimetro di autenticazione deve essere il più vicino possibile al dato, non il più lontano. Il middleware è un gate comodo, non una difesa sufficiente.
Remediation
Passo 1: bump delle versioni
# Next.js
npm install @clerk/nextjs@latest @clerk/shared@latest
# Nuxt
npm install @clerk/nuxt@latest @clerk/shared@latest
# Astro
npm install @clerk/astro@latest @clerk/shared@latest
# Verifica che shared sia effettivamente salito
npm ls @clerk/shared
In monorepo con workspace pnpm o yarn controllate i resolutions o overrides per forzare la versione dovunque. Abbiamo visto progetti in cui il bump di @clerk/nextjs non ha mosso @clerk/shared perché pinnato altrove.
Passo 2: auth ridondante nei route handler critici
Policy minima da adottare subito:
// Tutte le route handler che toccano dati sensibili
import { auth } from '@clerk/nextjs/server';
export async function GET(req: Request) {
const { userId, has } = await auth();
if (!userId) return new Response('Unauthorized', { status: 401 });
if (!has({ role: 'admin' })) return new Response('Forbidden', { status: 403 });
// business logic
}
Non serve farlo ovunque al minuto zero. Serve farlo subito nei tre o quattro path che custodiscono i dati che perderebbero il cliente se diventassero pubblici.
Passo 3: monitoring
Aggiungete un log strutturato nelle route protette che registra userId, path, timestamp. Un picco di richieste 200 su path admin con userId nullo è il segnale di un tentativo di bypass in corso.
Passo 4: code review della shape del matcher
Pattern aggressivi come ['/(.*)'] con white list per aprire i path pubblici sono più sicuri della black list. Se ogni path è protetto by default e aprite solo ciò che è pubblico, una normalizzazione divergente chiude la route invece di aprirla.
Note dal campo SPECTROSEC
Ogni volta che facciamo code review su un SaaS Next.js con Clerk il primo controllo è: quante chiamate auth() ci sono dentro i route handler rispetto a quante route ci sono in totale. Se il rapporto è sotto il 30% segnaliamo la cosa come architectural weakness anche quando non c'è una CVE aperta. I bug di questo tipo vengono fuori ciclicamente perché la tentazione di delegare al bordo è forte e il marketing dei provider auth la rinforza.
Il pattern che suggeriamo ai team dev è un wrapper unico tipo withAuth(handler, { role }) che ogni route deve usare. Nessun route handler scrive auth logic a mano. Nessuno dimentica il gate. La vulnerabilità di oggi diventa un bump di versione, non un redesign.
Se siete amministratori di una piattaforma basata su Clerk e volete un check rapido dell'esposizione reale del vostro stack, scrivete a info@spectrosec.com con oggetto "check clerk GHSA vqx2". Vi rispondiamo con una checklist e una stima di tempo nelle 48 ore successive.
Team SPECTROSEC | pentest professionali, scrivimi a info@spectrosec.com https://spectrosec.com