Skip to content

effect/http: Url.make fails for every URL in documents with an opaque origin (sandboxed iframes) #9196

Closed

Description

@elliason

What version of Effect is running?

effect 4.0.4 (the code is unchanged on main as of 2026-10-15)

What steps can reproduce the bug?

Run any HttpClient request (for example RpcClient.layerProtocolHttp or HttpClient.get("https://example.com/api")) inside a sandboxed iframe whose origin is opaque, such as an <iframe sandbox="allow-scripts" srcdoc="…">. MCP Apps hosts (Claude, ChatGPT, VS Code) load app views exactly like that.

effect/http Url.make builds every request URL with new URL(url, baseUrl()), and baseUrl() returns location.origin + location.pathname whenever location exists:

https://github.com/Effect-TS/effect/blob/main/packages/effect/src/http/Url.ts

In a sandboxed srcdoc frame location.origin is the string "null" and location.pathname is "srcdoc", so the base is "nullsrcdoc". The WHATWG URL parser rejects an unparsable base even when the first argument is absolute:

// inside <iframe sandbox="allow-scripts" srcdoc="...">
location.origin   // "null"
location.pathname // "srcdoc"
new URL("https://example.com/api/rpc", location.origin + location.pathname)
// TypeError: Failed to construct 'URL': Invalid base URL

(Chrome for Testing 153 headless; reproduced with a page that appends such an iframe and posts the result back to the parent.)

What is the expected behavior?

Absolute URLs work in any document, and relative URLs resolve against the document when it has a usable base.

What do you see instead?

Every request fails before it is sent with HttpClientError → InvalidUrlError (UrlError → TypeError: Failed to construct 'URL': Invalid base URL), including requests to absolute URLs.

Additional information

A possible fix is to use the document's base only when it parses, for example location.href (which is about:srcdoc here and accepts absolute URLs), or to skip the base when location.origin === "null":

const baseUrl = (): string | undefined => {
  if ("location" in globalThis && globalThis.location !== undefined && globalThis.location.origin !== "null") {
    return location.origin + location.pathname
  }
  return undefined
}

We worked around it by sending RPC requests through a custom RpcClient.Protocol instead of layerProtocolHttp.

Activity

  1. self-assigned this
    on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions