GHSA-gj2h-2fpw-fhv9
@nuxt/ui: UAuthForm / UForm SSR markup omits `method`, leaking credentials via GET if submitted before hydration
Blast Radius
Weekly download volume for affected packages — a proxy for how broadly this vulnerability is deployed.
@nuxt/uinpmDescription
Summary
UForm and UAuthForm render a server-side <form> element with no method and no action attribute, relying on a hydrated @submit.prevent handler to intercept submission. If a user submits the form before Vue hydration has attached the handler (autofill plus Enter on a slow network, JS bundle blocked by CSP or CDN failure, etc.), the browser performs the native default: a GET to the current URL with every named field, including <input type="password">, serialised into the query string.
Details
src/runtime/components/Form.vue (around the template's <form> element) emits:
<component
:is="parentBus ? 'div' : 'form'"
:id="formId"
ref="formRef"
:class="ui({ class: [uiProp?.base, props.class] })"
@submit.prevent="onSubmitWrapper"
>
No method, no action. @submit.prevent is the only thing stopping native submission, and it only exists after hydration. UAuthForm composes UForm and inherits the same shape.
The SSR snapshot of UAuthForm (test/components/__snapshots__/AuthForm.spec.ts.snap) shows the rendered markup, with <input type="password" name="password"> inside a <form> that has no method.
Proof of concept
Reported by @nimonian:
- Create a minimal Nuxt app with a
UAuthForm. - Build for production and visit in a browser with network throttling at 4G or slower.
- Enter credentials.
- Submit (or let autofill + Enter fire before hydration).
The URL becomes /login?email=…&password=…. Reproducible deterministically in Playwright by triggering submit immediately on load.
Impact
Any application using UAuthForm (or UForm with credential-shaped fields) as documented. The cleartext password lands in:
- the address bar,
window.history,- the
Refererheader of every same-origin subresource fetched from the resulting URL, - access logs of any reverse proxy, CDN, or WAF that records request URLs.
Patch
Default the rendered <form> to method="post" so the pre-hydration fallback submits as POST rather than GET. Vue's @submit.prevent still intercepts the hydrated case; the attribute only matters in the race window. Applications that explicitly want native GET submission can opt back in by passing method="get".
Credit
Reported by @nimonian. Originally filed as GHSA-92g7-2fpq-hmq8 against nuxt/nuxt; moved here because the affected code lives in @nuxt/ui.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | @nuxt/ui | all versions | No fix |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @nuxt/ui. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Remediation status
No patched version of @nuxt/ui has shipped for GHSA-gj2h-2fpw-fhv9 yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.
Mitigate without a patch
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-gj2h-2fpw-fhv9 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-gj2h-2fpw-fhv9. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-gj2h-2fpw-fhv9 in your dependencies?
O3 detects GHSA-gj2h-2fpw-fhv9 across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.