Skip to content

fix(auth): require a same-origin call on the loopback bridge callback#1257

Draft
deepme987 wants to merge 1 commit into
mainfrom
deepme987/desktop/bridge-callback-origin
Draft

fix(auth): require a same-origin call on the loopback bridge callback#1257
deepme987 wants to merge 1 commit into
mainfrom
deepme987/desktop/bridge-callback-origin

Conversation

@deepme987

Copy link
Copy Markdown
Collaborator

What

The loopback sign-in bridge's POST /callback accepts a signed-in Firebase user and injects it into the app session. Its only validation was that the request arrived on the loopback socket.

That check can't do the job it looks like it's doing: every page in every browser on the machine also reaches the server from 127.0.0.1. So it cannot distinguish the bridge page we serve from any other page the user happens to have open. The handler read no Origin, no Sec-Fetch-Site, and never enforced a content-type — readJsonBody parses the body regardless.

This makes the endpoint require positive proof that the caller is the page we served: a same-origin JSON fetch, which is exactly what the bridge page already sends.

The checks

  • Content-Type: application/json — the load-bearing one. A cross-site caller can only reach a loopback server without a CORS preflight by staying inside the "simple" content-types (text/plain, form-encoded, multipart). We serve no OPTIONS handler, so demanding JSON forces a preflight that cannot succeed.
  • Sec-Fetch-Site: same-origin when present — a forbidden header, so a page cannot forge it.
  • Origin must be our own loopback origin when present.

Fails closed when neither Origin nor Sec-Fetch-Site attests same-origin.

No behaviour change for the real flow: the bridge page's fetch('/callback', {method:'POST', headers:{'Content-Type':'application/json'}}) already satisfies all three.

Testing

Tests drive the real server over raw node:http — undici strips Origin, so fetch can't express these cases:

Request Result
Cross-site form POST (text/plain) 403, no sign-in
JSON POST from a foreign origin 403, no sign-in
POST with neither Origin nor Sec-Fetch-Site 403, no sign-in
Genuine same-origin JSON call 204, resolves

Each asserts the sign-in flow did not complete, not just the status code. Removing the guard fails all three. Full src/main/auth suite green (41), typecheck + lint clean.

Notes

Found while reviewing #1222. Independent of that PR — this hardens the existing bridge, which #1222 keeps as its fallback path.

The bridge's POST /callback accepts a signed-in Firebase user and injects
it into the app session, but its only check was that the request arrived on
the loopback socket. Every page in every browser on the machine also reaches
us from 127.0.0.1, so that check cannot distinguish our own bridge page from
any other page the user happens to have open. The handler read no Origin, no
Sec-Fetch-Site, and did not enforce a content-type.

Require positive proof the caller is the page we served — a same-origin JSON
fetch, which is exactly what the bridge page already sends:

  - Content-Type: application/json. This is the load-bearing check. A
    cross-site caller can only reach a loopback server without a CORS
    preflight by staying inside the "simple" content-types (text/plain,
    form-encoded, multipart). We serve no OPTIONS handler, so demanding JSON
    forces a preflight that cannot succeed.
  - Sec-Fetch-Site must be same-origin when present (a forbidden header — a
    page cannot forge it).
  - Origin must be our own loopback origin when present.

Fails closed when neither Origin nor Sec-Fetch-Site attests same-origin.

Tests drive the real server over raw node:http (undici strips Origin, so
fetch cannot express the case): a cross-site form POST, a JSON POST from a
foreign origin, and a POST with neither header are all rejected 403 and
complete no sign-in; the genuine same-origin call still returns 204 and
resolves. Removing the guard fails all three.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5cdb1c72-602e-4fc5-9bd9-4ed75682fefb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch deepme987/desktop/bridge-callback-origin
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch deepme987/desktop/bridge-callback-origin

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant