Skip to content

sys.unixShellCommand can exec a host GUI browser past the #891 headless openUrl guard -- decision needed #894

Open

Description

@jsavin

Rationale

#891 stopped the headless build from launching a host GUI browser: sys.openUrl now logs the URL and records it at system.temp.Frontier.pendingBrowserUrl instead of forking a launcher, unless an explicit --browser allow-gui opt-in is given.

That guard is placed at the sys.openUrl kernel verb, which is the single choke point for every webBrowser.openUrl caller. It is not a general guarantee that a headless process will not reach the host desktop, because sys.unixShellCommand is in the CLI build and can do the same thing directly:

sys.unixShellCommand("open http://127.0.0.1:5336/setupFrontier")

This was identified during #891 and deliberately left unguarded there — guarding it is a separate decision with its own trade-offs, and #891 was scoped to the onboarding path.

Current State

  • Common/source/sysshellcall.c:232 / :324 — popen into /bin/sh; this file IS in the CLI build
  • tests/headless_sys_verbs.c:812 — case sysv_unixshellcommand, the headless twin (see the duplication issue for the link-time ambiguity between these two)
  • No headless-awareness in either path: a shell command runs whatever it is given

Scope note: launch.* verbs are already no-ops in headless (tests/headless_launch_verbs.c:41-54), so they are not a third door.

The actual decision

sys.unixShellCommand is a general-purpose shell escape. Unlike sys.openUrl — which has exactly one purpose, so restricting it costs nothing — a shell verb has no narrow contract to enforce. Any guard is a policy choice about what a headless Frontier process may do to its host, and needs to be made deliberately:

  1. Leave it. Scripts in the ODB are trusted code; a shell verb is expected to be able to run shell commands. The Headless webBrowser.openUrl should log setup URL for remote operators, not auto-launch a host GUI browser (preserve first-run onboarding intent) #891 guard then covers the onboarding path only, and that is stated honestly rather than implied to be airtight.
  2. Pattern-guard the launchers. Reject or neutralize open / xdg-open invocations in headless mode. Cheap, but trivially bypassed (/usr/bin/open, sh -c, a variable) — real risk of a guard that reads as protection while providing little.
  3. Sandbox or policy-gate the verb. An explicit opt-in for shell execution in headless/server contexts, in the same shape as --browser allow-gui. Most defensible, largest surface, and would need its own design pass.

Option 2 is the tempting one and probably the worst: it adds the appearance of enforcement without the substance.

Desired Outcome

A recorded decision on which of the above applies, and — whichever is chosen — documentation that accurately states the boundary of the #891 guard, so nobody reads "headless will not launch a browser" as stronger than it is.

Exit Criteria

Related Context

Activity

  1. added
    priority/p2Medium priority - nice to have
    scope/medium1-2 weeks, requires design work
    status/decision-neededArchitectural decision required before proceeding
    workstream/runtime-coreRuntime architecture, memory, lifecycle, and global context
    on Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority/p2Medium priority - nice to havescope/medium1-2 weeks, requires design workstatus/decision-neededArchitectural decision required before proceedingtype/securitySecurity-related workworkstream/runtime-coreRuntime architecture, memory, lifecycle, and global context

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions