mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
fix: reject protocol-relative path from sanitizeAuthRedirect (open redirect)
sanitizeAuthRedirect returned parsed.pathname+search+hash, which `new URL` can resolve to a protocol-relative `//host` (e.g. input `/..//evil.com` normalizes same-origin, passing the origin check, but yields a path starting with `//`). ClientRedirect / login.tsx feed that path to window.location.replace, so it navigates cross-origin — an open redirect. Reject any resolved path that is not a single-leading-slash path (`^/[^/]`), falling back to the default. Adds coverage for `/..//evil.com`, `/.//evil.com`, and `//evil.com`.
This commit is contained in:
parent
179867d120
commit
3daddcd14e
2 changed files with 14 additions and 1 deletions
|
|
@ -50,7 +50,12 @@ export function sanitizeAuthRedirect(
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = `${parsed.pathname}${parsed.search}${parsed.hash}`
|
const path = `${parsed.pathname}${parsed.search}${parsed.hash}`
|
||||||
if (!path.startsWith("/") || isBlockedRedirectPath(path)) return fallback
|
// Reject anything that is not a single-leading-slash path. `new URL` can resolve inputs
|
||||||
|
// like `/..//evil.com` to a protocol-relative `//evil.com` that still matches the origin
|
||||||
|
// check above but, when handed to `window.location.replace`, navigates cross-origin.
|
||||||
|
if (path.startsWith("//") || !/^\/[^/]/.test(path) || isBlockedRedirectPath(path)) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,14 @@ describe("auth redirect helpers", () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("rejects inputs that resolve to a protocol-relative path", () => {
|
||||||
|
// These resolve same-origin (passing the origin check) but yield a `//host`
|
||||||
|
// path that would navigate cross-origin via window.location.replace.
|
||||||
|
expect(sanitizeAuthRedirect("/..//evil.com")).toBe(DEFAULT_AUTH_REDIRECT)
|
||||||
|
expect(sanitizeAuthRedirect("/.//evil.com")).toBe(DEFAULT_AUTH_REDIRECT)
|
||||||
|
expect(sanitizeAuthRedirect("//evil.com")).toBe(DEFAULT_AUTH_REDIRECT)
|
||||||
|
})
|
||||||
|
|
||||||
it("builds a plan sign-in target for the current plan URL", () => {
|
it("builds a plan sign-in target for the current plan URL", () => {
|
||||||
window.history.pushState({}, "", "/agents/thread-1/plan?from=slack")
|
window.history.pushState({}, "", "/agents/thread-1/plan?from=slack")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue