feat: allowed-origins credential boundary — don't attach credentials to third-party 401s#22
Draft
jeswr wants to merge 1 commit into
Draft
feat: allowed-origins credential boundary — don't attach credentials to third-party 401s#22jeswr wants to merge 1 commit into
jeswr wants to merge 1 commit into
Conversation
…to third-party 401s When the reactive layer patches global `fetch`, every request the page makes — including to third-party origins (a CDN, image host, analytics beacon, or a URL embedded in fetched data) — flows through it. Today ANY origin that answers 401 triggers a provider `upgrade` and receives a retry carrying the user's Solid credentials (the `Authorization` access token minted for that origin's URL). That hands the user's identity/credential to origins they never chose to trust. Add an opt-in `allowedOrigins` credential boundary on `ReactiveFetchManager` and `ReactiveAuthenticationClient`: a 401 from an out-of-boundary origin is returned untouched — no provider consulted, no credentials attached. The `CredentialBoundary` compares canonical origins (not URL substrings, so a look-alike host cannot slip past) and can be widened in place after construction without disrupting a live session. Backwards-compatible: omitting `allowedOrigins` preserves the previous "upgrade every 401" behaviour and logs a one-time warning recommending it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
The problem
ReactiveFetchManager.registerGlobally()replacesglobalThis.fetch, so every fetch the page makes flows through the reactive layer — not just requests to the user's pod, but also requests to third-party origins: a CDN, an image host, an analytics beacon, or a URL embedded in fetched data (afoaf:img, an<img>a profile links to, anrdfs:seeAlso).Today the manager (and
ReactiveAuthenticationClient) upgrade any401:So an origin the app never intended to authenticate to can answer
401and receive a retry carrying the user's Solid credentials — theAuthorizationaccess token (a bearer credential carrying the user's WebID/identity) minted for that origin's URL. That is a credential/identity-leak vector: a hostile401harvests the user's token.The fix
An opt-in
allowedOriginscredential boundary — the allow-list of origins the consumer trusts with credentials (typically their pod/storage origins). A401from an out-of-boundary origin is now returned untouched: no provider is consulted, noupgraderuns, no credentials are attached.allowedOriginsaccepts aCredentialBoundary(hold the ref to widen it later) or any iterable of origins / origin-predicates.new URL(url).origin), not URL substrings — sohttps://pod.example.attacker.testandhttp://pod.exampleare correctly not trusted (a look-alike host or downgraded scheme can't slip past).ReactiveFetchManagerandReactiveAuthenticationClient(the latter backs the service-worker path).Compatibility
Fully backwards-compatible. Omitting
allowedOriginspreserves the existing "upgrade every401" behaviour and logs a one-time warning recommending the boundary. No breaking change to the constructor (the options arg is optional).Suite evidence
This is the allowed-origins credential-boundary model the
@jeswrapp suite built into its shared login core after apps that patch globalfetchwere observed sending pod credentials onto foreign origins reached transitively from fetched RDF. The suite treats the origins-that-may-receive-credentials as an explicit, widenable trust boundary; this PR is that model adapted toreactive-authentication's API.Verification
npx tsc— clean (the CI check).test/CredentialBoundary.test.ts(11 cases,npm test/ vitest): out-of-boundary401is passed through withprovider.upgradenever called (no token minted); in-boundary401upgrades;add()reArm widens a live boundary; canonical-origin comparison rejects look-alike hosts; the no-boundary legacy path still upgrades everything and warns exactly once. (Note: this repo's CI currently runstsconly, as with the other draft PRs that added vitest; the tests run locally vianpm test.)🤖 PSS agent — @jeswr's agent for the Solid app suite; proposing hardening the suite built in
@jeswr/solid-auth-core. Draft for review — happy to adjust the API shape (e.g. auto-widening on a successful upgrade, or making the boundary required in a future major).