返回 DeepSeek-Reasonix
safe-next.js
根目录 / site / src / scripts / safe-next.js
1 // A post-login `next` redirect target is honoured only if it resolves — via
2 // full URL parsing against the real page origin, so control characters and
3 // protocol-relative tricks get the same normalization a browser applies
4 // before navigating — to a same-origin path, or to an absolute https URL
5 // under reasonix.io. That lets a subdomain (e.g. crash.reasonix.io) return
6 // here after sign-in without opening a redirect to an arbitrary host.
7 //
8 // Validating a raw string prefix (e.g. checking it starts with "/") is not
9 // enough: URLSearchParams decodes percent-encoding, so a value like
10 // "/%09/evil.example" arrives as "/\t/evil.example", which passes a naive
11 // prefix check but the URL parser strips the tab during navigation, leaving
12 // "//evil.example" — a protocol-relative redirect off-site. Parsing with
13 // `new URL()` first and checking the *parsed* origin/host closes that gap
14 // because it uses the same normalization the browser itself applies.
15 //
16 // Both branches return the fully-serialized `u.href`, never a relative
17 // fragment. Returning `u.pathname` would reopen the hole: dot-segment inputs
18 // such as "/.//evil.example" or "/a/..//evil.example" resolve to a
19 // same-origin URL whose *pathname* is "//evil.example", and handing that
20 // bare pathname back to `location.href` re-parses it as a protocol-relative
21 // URL to evil.example. `u.href` keeps the origin attached, so assigning it
22 // can never leave reasonix.io.
23 export function safeNext(next, origin) {
24 if (!next) return null;
25 let u;
26 try {
27 u = new URL(next, origin);
28 } catch {
29 return null;
30 }
31 if (u.origin === origin) return u.href;
32 if (u.protocol === "https:" && (u.host === "reasonix.io" || u.host.endsWith(".reasonix.io"))) return u.href;
33 return null;
34 }
35
35 lines JAVASCRIPT