Skip to content

fix(native): send iOS native-mode auth requests through NSURLSession - #475

Merged
Makisuo merged 1 commit into
mainfrom
fix/ios-native-auth-fetch
Nov 12, 2026
Merged

Makisuo merged 1 commit into
mainfrom
fix/ios-native-auth-fetch

Conversation

@Makisuo

@Makisuo Makisuo commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #474, which merged before this commit was pushed.

Problem

Sign-in on a real iPhone (TestFlight build 2968) failed with "Something went wrong". Production traces show POST /auth/browser-login/initiate answering 403. The auth transport admits a native request (x-auth-mode: native) only without Origin, Sec-Fetch-Mode and cookies (@yielded/auth http-operation/security.ts), and WebKit stamps Origin/Sec-Fetch-Mode on every fetch from the hidden page and its worker; no script can remove them. The simulator never hit this because sign-in there ran against a local stand-in.

Confirmed against production: the same native initiate request returns 400 (admitted, empty test body) without Origin and 403 with Origin: https://app.hazel.sh.

Fix

  • src/ios.ts: the worker's fetch hands any x-auth-mode: native request to the host as { fetch } and builds a Response from the host's reply; everything else stays on WebKit's fetch.
  • host/ios/src/http.rs: sends it with an ephemeral, cookieless NSURLSession (no Origin, no Sec-Fetch-*) and posts { id, status, headers, body } or { id, error } back.
  • bridge.rs / page.rs: the fetch request and the __hazel.reply path from host to worker.

Verification

  • cargo check + clippy for aarch64-apple-ios clean; cargo test -p hazel-ios and the native vitest suite pass.
  • Shipped as TestFlight build 0.1.0 (2970), processed VALID and in internal testing; not yet confirmed on a device.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

The API admits a native auth request (`x-auth-mode: native`) only without
`Origin`, `Sec-Fetch-Mode` and cookies, and WebKit puts the first two on every
fetch from the page and its worker, so sign-in on a device got 403 from
`/auth/browser-login/initiate`. The iOS worker now hands those requests to the
host, which sends them with an ephemeral, cookieless NSURLSession and posts
the response back through the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@maple-review-bot

maple-review-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Maple review

🔴 Confidence 4/10 · risky as written. The host transport follows redirects the auth client explicitly forbids, and nothing tests the new routing; that security gap decides the number.
2 warnings −2 · tests missing −2 · risk high −1 · 0/1 new units observable −1

Fixes iOS native-mode sign-in by sending those requests through the host's NSURLSession instead of WebKit's fetch, which stamps Origin. The mechanism is sound, but the host follows redirects the auth transport forbids and none of the new path is tested.

  • globalThis.fetch hands x-auth-mode: native requests to the host and answers from its reply
  • http.rs sends them with an ephemeral, cookieless NSURLSession, replying {id, status, headers, body}
  • The page gains __hazel.reply so host replies reach the worker

Before merge

  • Manual · Confirm sign-in on a physical TestFlight device before release; build 2970 shipped without it

Findings

🟠 Warning · F1 · Host path follows redirects the auth client forbids (redirect: "error")

security · apps/native/src/ios.ts:72

The auth runtime builds its client with redirect: "error" because the session travels in x-auth-request-session headers (apps/web-foldkit/src/platform/desktop-auth.ts:159). The payload sent to the host carries no redirect, and NSURLSession follows a redirect by default, so a 3xx re-sends those credential headers to the Location origin and the caller gets that response instead of a rejection.

Put `redirect: outgoing.redirect` in the payload, and have `http.rs` refuse the redirect (a task delegate answering `None`) so a 3xx surfaces as the load failure the client asked for.
🟠 Warning · F2 · New fetch routing and reply handling are covered by no test

tests · apps/native/src/ios.ts:62-63

apps/native/src tests its other global shims (shims.test.ts, and data-source.ts through offline-backend.test.ts), but nothing exercises this branch: a native request reaching the host, its id matching a late reply, a non-native request on WebKit's fetch, or { error } becoming a TypeError. The change's whole behavior is unverified in CI.

Extract the routing into `src/native-fetch.ts` taking the web fetch, `request` and a reply source, and test it: native routed, others passed through, an error reply throws.
🤖 Prompt to fix all 2 findings with an AI agent
Findings from an automated review of commit e89d8fc92f3ca58e71bc4b8e361ab7900a89f6df. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · security · apps/native/src/ios.ts:72
Host path follows redirects the auth client forbids (`redirect: "error"`)
The auth runtime builds its client with `redirect: "error"` because the session travels in `x-auth-request-session` headers (`apps/web-foldkit/src/platform/desktop-auth.ts:159`). The payload sent to the host carries no `redirect`, and `NSURLSession` follows a redirect by default, so a 3xx re-sends those credential headers to the `Location` origin and the caller gets that response instead of a rejection.
Suggested fix: Put `redirect: outgoing.redirect` in the payload, and have `http.rs` refuse the redirect (a task delegate answering `None`) so a 3xx surfaces as the load failure the client asked for.

---

F2 · Warning · tests · apps/native/src/ios.ts:62-63
New `fetch` routing and reply handling are covered by no test
`apps/native/src` tests its other global shims (`shims.test.ts`, and `data-source.ts` through `offline-backend.test.ts`), but nothing exercises this branch: a native request reaching the host, its `id` matching a late reply, a non-native request on WebKit's fetch, or `{ error }` becoming a `TypeError`. The change's whole behavior is unverified in CI.
Suggested fix: Extract the routing into `src/native-fetch.ts` taking the web fetch, `request` and a reply source, and test it: native routed, others passed through, an error reply throws.

e89d8fc · What was checked, coverage and telemetry details are on the Maple / review check. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@Makisuo
Makisuo merged commit 16aa83e into main Nov 12, 2026
6 of 7 checks passed
@Makisuo
Makisuo deleted the fix/ios-native-auth-fetch branch November 12, 2026 13:48
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Explain Change →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@maple-review-bot

Copy link
Copy Markdown

Merged with 1 step from "Before merge" still open. If it is done, tick it on the review comment; if not, now is the time.

  • Manual · Confirm sign-in on a physical TestFlight device before release; build 2970 shipped without it

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 inline notes from Maple's review. The score and summary are in the review comment above.

Comment thread apps/native/src/ios.ts
reject(outgoing.signal.reason)
})
const headers = Object.fromEntries(outgoing.headers)
request({ fetch: JSON.stringify({ id, url: outgoing.url, method: outgoing.method, headers, body }) })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Host path follows redirects the auth client forbids (redirect: "error")

F1 · Warning · security

The auth runtime builds its client with redirect: "error" because the session travels in x-auth-request-session headers (apps/web-foldkit/src/platform/desktop-auth.ts:159). The payload sent to the host carries no redirect, and NSURLSession follows a redirect by default, so a 3xx re-sends those credential headers to the Location origin and the caller gets that response instead of a rejection.

Put `redirect: outgoing.redirect` in the payload, and have `http.rs` refuse the redirect (a task delegate answering `None`) so a 3xx surfaces as the load failure the client asked for.
🤖 Prompt to fix with an AI agent
In `apps/native/src/ios.ts:72`: Host path follows redirects the auth client forbids (`redirect: "error"`).

The auth runtime builds its client with `redirect: "error"` because the session travels in `x-auth-request-session` headers (`apps/web-foldkit/src/platform/desktop-auth.ts:159`). The payload sent to the host carries no `redirect`, and `NSURLSession` follows a redirect by default, so a 3xx re-sends those credential headers to the `Location` origin and the caller gets that response instead of a rejection.

Suggested fix: Put `redirect: outgoing.redirect` in the payload, and have `http.rs` refuse the redirect (a task delegate answering `None`) so a 3xx surfaces as the load failure the client asked for.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

Comment thread apps/native/src/ios.ts
let nextFetch = 0
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
const outgoing = new Request(input, init)
if (outgoing.headers.get(nativeHeaders.modeHeader) !== "native") return webFetch(outgoing)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

New fetch routing and reply handling are covered by no test

F2 · Warning · tests

apps/native/src tests its other global shims (shims.test.ts, and data-source.ts through offline-backend.test.ts), but nothing exercises this branch: a native request reaching the host, its id matching a late reply, a non-native request on WebKit's fetch, or { error } becoming a TypeError. The change's whole behavior is unverified in CI.

Extract the routing into `src/native-fetch.ts` taking the web fetch, `request` and a reply source, and test it: native routed, others passed through, an error reply throws.
🤖 Prompt to fix with an AI agent
In `apps/native/src/ios.ts:62-63`: New `fetch` routing and reply handling are covered by no test.

`apps/native/src` tests its other global shims (`shims.test.ts`, and `data-source.ts` through `offline-backend.test.ts`), but nothing exercises this branch: a native request reaching the host, its `id` matching a late reply, a non-native request on WebKit's fetch, or `{ error }` becoming a `TypeError`. The change's whole behavior is unverified in CI.

Suggested fix: Extract the routing into `src/native-fetch.ts` taking the web fetch, `request` and a reply source, and test it: native routed, others passed through, an error reply throws.

Verify the problem exists at that location before changing it, and keep the fix to those lines.

@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 73.43% 36182 / 49273
🔵 Statements 72.43% 41144 / 56804
🔵 Functions 61.22% 12995 / 21226
🔵 Branches 60.77% 20505 / 33740
File CoverageNo changed files found.
Generated in workflow #2305 for commit e89d8fc by the Vitest Coverage Report Action

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