Repository navigation
fix(router): ignore a cross-origin Referer as navigation context (#1005) - #1019
Merged
Merged
Conversation
commit: |
A partial request without X-RSC-Router-Client-Path fell back to Referer and matched only its pathname, so a foreign URL was treated as a route on this app. A Referer counts only when it stays on the origin pathname matching uses. The same-origin fallback stays. Docs: the ppr skill (no-navigation-context bullet and token table), the intercept skill (where `from` comes from), execution-model.md, a "Navigation Context from Referer" section in security-checklist.md, and an Unreleased CHANGELOG entry with the before/after table and the forwarded-header caveat. Closes #1005.
ivogt
force-pushed
the
fix/1005-cross-origin-referer
branch
from
October 3, 2026 12:10
fd065b7 to
548d41a
Compare
This was referenced Oct 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1005.
Problem
A partial request with no
X-RSC-Router-Client-Pathused theRefererheader as the navigation source, then matched only that URL's pathname. An absolute cross-origin Referer kept its foreign origin, but the pathname was looked up in this app.https://other.example/product/42was matched as/product/42. That source drives the segment diff, intercept selection, and interceptwhen'sfrom.The router's own client always sends
X-RSC-Router-Client-Path. This only affects a partial request made without it. A same-origin Referer is still a useful fallback for those requests.Before / after
Before, the router treated the visitor as coming from this app's
/product/42: that route's params and name were the navigation source. After, that request has no navigation context, the same as a missingReferer. A Referer ofhttps://shop.example/product/42on a request whose match origin ishttps://shop.exampleis still the source.No call-site change. Apps do not adopt a new API.
End-to-end consumer example
A product page is also an intercept target. A monitoring client, or any partial fetch that omits
X-RSC-Router-Client-Path, requests/product/99withReferer: https://news.example/product/42.Before, intercept
when'sfromand the segment diff were computed as if the visitor had been on this shop's/product/42. After, the request is not a navigation from that page. The same fetch withReferer: https://shop.example/cartstill navigates from/cart.Semantics
X-RSC-Router-Client-PathsetresolveNavigationalready rejected it)resolveNavigation, before any shell-store readOrigin/ReferervsHost)The origin compared is
url.origin, the origin pathname matching already uses. It is notrequest.url's origin when those differ.Code-change summary
getNavigationContextHeaderinpackages/rangojs-router/src/router/navigation-snapshot.tsnow takes the match origin. It returnsX-RSC-Router-Client-Pathunchanged. ARefereris returned only whennew URL(referer, requestOrigin)stays on that origin. Both callers passurl.origin:resolveNavigation, andmatchPartialWithPprReplayinpackages/rangojs-router/src/rsc/rsc-rendering.ts. The helper is the shared predicate those two already had to keep in lockstep.src/rsc/origin-guard.tsis not involved.Test plan
packages/rangojs-router/src/router/__tests__/navigation-snapshot.test.ts— 31 passed. New cases, red before the fix where the old fallback returned the foreign URL:treats a cross-origin Referer as no navigation context(red: expectedhttps://other.example/product/42to be null)prefers X-RSC-Router-Client-Path over a cross-origin Refererkeeps a relative Referer that resolves on the match origindrops a Referer whose port differs from the match origindrops a Referer whose scheme differs from the match origindrops a Referer that cannot be parsedcompares Referer to the origin argument, not request.urlpnpm run typecheckpnpm run test:unit:allpnpm run lintpnpm run formatresolveNavigationand the replay gate. The gate was not executed on its own; it calls this helper withurl.origin.Notes
Low priority for apps on the router's client, which always sends
X-RSC-Router-Client-Path. The fallback remains for a same-origin Referer.Docs and skills (added 2026-10-03)
Rebased onto
main(0.20.0 plus #1016) and extended with documentation:skills/ppr/SKILL.md: theno-navigation-contextbullet and token-table row say "noX-RSC-Router-Client-Pathand no same-originReferer".skills/intercept/SKILL.md: wherefromcomes from, and that a request without usable navigation context gets the full page.docs/internal/execution-model.md: the same predicate, named.docs/internal/security-checklist.md: new section "Navigation Context fromReferer", including thatX-Forwarded-Host/X-Forwarded-Protoare not consulted (as withoriginCheck), so a header-less partial request behind a proxy that hands the app an internal URL takes the full match.CHANGELOG.md: an## Unreleasedentry with the before/after table.Gate on the rebased commit:
pnpm run typecheck,pnpm run test:unit:all(router 7100, RSC 440),pnpm run lint,pnpm run format,check:docs-api,check:e2e-bucketing, skills and doc-link guards.