T09 · Insecure Skill Coding Practices
Error
- Location
- references/templates/auth-slice.md:27
- Finding
- Unvalidated Post-Authentication Navigation Target<![CDATA[ ## Vulnerability Details **File Location**: `references/templates/auth-slice.md:27-33` **Additional Locations**: `references/templates/form-with-zod.md:152-168`, `references/templates/root-layout.md:149-165` **Vulnerability Type**: Unvalidated redirect/navigation target **Risk Level**: High ### Vulnerable Code ```tsx const res = await signIn('credentials', { email, password, redirect: false }); if (res?.error) { // surface "Invalid email or password." — don't leak which field was wrong return; } router.replace(searchParams.get('returnTo') ?? '/app/dashboard'); router.refresh(); ``` The equivalent full data flow in `references/templates/form-with-zod.md` is: ```tsx const returnTo = searchParams.get('returnTo') ?? '/app/dashboard'; async function onSubmit(values: LoginValues) { const res = await signIn('credentials', { ...values, redirect: false }); if (!res || res.error) { setError('Invalid email or password.'); return; } router.replace(returnTo); router.refresh(); } ``` ### Technical Analysis The generated login form reads `returnTo` directly from the URL query string and passes it to `router.replace()` without confirming that it is a safe, local application path. Although the middleware-generated value is normally a pathname, clients are not required to reach the login page through middleware. An attacker can construct a login URL containing an arbitrary `returnTo` value. Next.js explicitly treats untrusted values passed to router navigation methods as unsafe; behavior for absolute URLs and dangerous URI schemes can also vary by framework/browser version. The application must not rely on middleware as validation because the query parameter remains attacker-controlled at the navigation sink. ### Attack Path 1. An attacker creates a URL such as `/auth/login?returnTo=<attacker-controlled-target>`. 2. The attacker sends the URL to a victim. 3. The victim enters valid credentials and authentication succeeds. 4. The gene ...[truncated 996 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Centralize validation in a helper and permit only local absolute paths: ```ts export function safeReturnTo( value: string | null, fallback = '/app/dashboard', ): string { if (!value) return fallback; if (!value.startsWith('/') || value.startsWith('//')) return fallback; if (value.includes('\\') || /[\u0000-\u001F\u007F]/.test(value)) return fallback; try { const parsed = new URL(value, window.location.origin); if (parsed.origin !== window.location.origin) return fallback; return `${parsed.pathname}${parsed.search}${parsed.hash}`; } catch { return fallback; } } ``` Use the validated result at every navigation sink: ```tsx const returnTo = safeReturnTo(searchParams.get('returnTo')); router.replace(returnTo); ``` Additional hardening: - Apply the same helper in `auth-slice.md`, `form-with-zod.md`, and `root-layout.md`. - Restrict destinations further to known authenticated route prefixes such as `/app/` and `/admin/` where appropriate. - Add tests covering absolute external URLs, protocol-relative URLs, backslashes, control characters, encoded variants, and dangerous URI schemes. - Keep the fallback fixed and application-owned. ]]>
