Open external links from HTML Apps in the user's browser - #274
Conversation
HTML Apps render in a sandboxed iframe without allow-popups or allow-top-navigation, so window.open and target="_blank" are no-ops and apps have no way to send the user to an external URL. Add a links.open method to the runtime broker: the injected runtime exposes hubble.links.open(url) / hubble.links.safeOpen(url) in the same style as files.*, and the renderer dispatch validates the URL (http/https only, mirroring the main-process guard) before forwarding to the existing desktop:open-external-url IPC handler. No new IPC surface is added. Closes bholmesdev#262
|
@marcodlk is attempting to deploy a commit to the bholmesdev's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Overview
Routes HTML App external links through the existing validated desktop:open-external-url path: hubble.links.open / safeOpen, plus bubble-phase intercept of absolute http(s) anchor clicks. Sandbox, CSP, and navigation guards stay unchanged; renderer + main both reject non-http(s).
Concerns
None material. Workspace guard correctly skipped for links.open. Runtime uses resolved anchor.href, bubble + defaultPrevented opt-out, and relative/in-page links stay on hubble-asset://. Tests exercise broker validation and real global.js click behavior.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Reviewed by a Warp Factory agent.
The runtime routes anchor clicks on absolute http(s) links through the same links.open broker request the hubble.links API uses, so authored HTML works without calling the API. Relative and in-page links resolve to hubble-asset:// and are untouched; app click handlers can call preventDefault to opt out.
79bff05 to
1b2937f
Compare
There was a problem hiding this comment.
Overview
Routes HTML App external links through the existing validated desktop:open-external-url path: hubble.links.open / safeOpen, plus bubble-phase intercept of absolute http(s) anchor clicks. Sandbox, CSP, and navigation guards stay unchanged; renderer + main both reject non-http(s).
Prior review approved. Delta adds real global.js click tests, ADR plain-anchor docs, changelog issue/link wording, and minor test cleanups — no regressions.
Concerns
None material. Workspace guard correctly skipped for links.open. Runtime uses resolved anchor.href, bubble + defaultPrevented opt-out, and relative/in-page links stay on hubble-asset://. Tests cover broker validation and runtime click behavior.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Reviewed by a Warp Factory agent.
Description
HTML Apps run in a sandboxed iframe, so anchors and
window.open()can't reach external sites. This PR routes external links to the user's default browser while leaving the sandbox untouched, per the discussion in #262.hubble.links.open(url)/safeOpen(url)on the runtime broker, matching thefiles.*style.<a>links work without the API: the runtime intercepts clicks on anchors (including nested elements) whose resolvedhrefis absolute http(s) and routes them throughlinks.open. Relative and in-page links are untouched, and an app's own click handler canpreventDefaultto opt out.Sandboxing is unchanged — same
sandboxattribute, CSP,will-frame-navigateguard, and deny-all window-open handler; no new IPC. URLs are validated twice: a renderer schema rejects anything that isn't http(s), and the existingdesktop:open-external-urlguard rejects again beforeshell.openExternal.links.openskips the workspace guard because it doesn't touch workspace files; allfiles.*methods still hit it first.Considered and rejected: opening URLs from blocked
will-frame-navigateevents in main — that fires on script-driven redirects with no user gesture and modifies the guard this PR aims to preserve.ADR-0007 documents both behaviors.
Closes #262
Type of Change
Testing
New tests:
IframeView.test.ts— broker validation: http(s) resolves{ ok: true }and reachesopenExternalUrl;file:,javascript:, scheme-less, and non-string inputs return{ ok: false }without reaching the desktop API.htmlAppRuntime.test.ts— runs the realpackages/runtime/global.jsin happy-dom at ahubble-asset://document URL: external and nested-element clicks route throughlinks.open, relative and in-page links are untouched,preventDefaultopts out.pnpm --filter @hubble.md/desktop test(221 tests),pnpm build:desktop, and the React Compiler audit pass.Manual Testing Details:
Drove a real sandboxed iframe over CDP: external and nested-element clicks open the system browser with the default prevented;
#sectionlinks stay in-page;hubble.links.safeOpen("file:///etc/passwd")returns{ ok: false, error }without reaching the desktop API.Checklist