M9 S5: guard the login ?redirect= against open redirect
LoginView handed route.query.redirect straight to router.replace, so a crafted link like /login?redirect=//evil.com (or a backslash variant) could bounce a just-authenticated user off-site. safeRedirect() now only follows an in-app absolute path — a single leading slash, rejecting "//host" and "/\\host" (and anything without a leading slash, i.e. absolute/scheme URLs) → falls back to "/". Frontend-only; CI vue-tsc is the gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -17,13 +17,21 @@ const password = ref("");
|
||||
const error = ref("");
|
||||
const loading = ref(false);
|
||||
|
||||
// Only follow an in-app absolute path from ?redirect= — reject protocol-relative
|
||||
// ("//host") and backslash ("/\\host") forms a browser may treat as an off-site URL,
|
||||
// so a crafted login link can't bounce the user elsewhere after they sign in.
|
||||
function safeRedirect(raw: unknown): string {
|
||||
if (typeof raw !== "string" || !raw.startsWith("/")) return "/";
|
||||
if (raw.startsWith("//") || raw.startsWith("/\\")) return "/";
|
||||
return raw;
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
error.value = "";
|
||||
loading.value = true;
|
||||
try {
|
||||
await session.login(email.value, password.value);
|
||||
const redirect = typeof route.query.redirect === "string" ? route.query.redirect : "/";
|
||||
await router.replace(redirect);
|
||||
await router.replace(safeRedirect(route.query.redirect));
|
||||
} catch (e) {
|
||||
error.value = (e as ApiError).error ?? "Could not sign in.";
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user