Skip to content

feat(runtime): launch and drive a desktop electron app in a sandbox - #56

Merged
winlp4ever merged 4 commits into
epic/electron-targetsfrom
feat/electron-runtime
Oct 8, 2026
Merged

winlp4ever merged 4 commits into
epic/electron-targetsfrom
feat/electron-runtime

Conversation

@winlp4ever

@winlp4ever winlp4ever commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

PR 2 of the Electron targets epic (#55). Design: OBJECT-MODEL §0.9 "Electron apps", reviewed by an architect agent, then redesigned after three review rounds and reviewed again.

Launch (`launchElectron`)

  • The app's executable starts in a fresh sandbox: its own HOME (with `CFFIXED_USER_HOME`), XDG folders, TMPDIR and `--user-data-dir`.
  • The environment is built from an allowlist (PATH, locale, Linux display variables), so Kiframe's own variables, keys and `NODE_OPTIONS` never reach the app.
  • It runs in its own process group, attached over `--remote-debugging-port=0` and `connectOverCDP`, with an endpoint accepted only when read in full on 127.0.0.1.
  • One deadline and the stop cover the whole launch, attach included. A moved app or a failed spawn gives a clear error and never crashes the host.

Confinement: a guard sealed at launch

  • Once the app settles, its main window is chosen, and its own scheme or dev server, plus the https frames it embeds as shipped, are sealed. A run can never add to them.
  • A guard then checks every navigation of every frame of every window as it happens. A page that isn't the app's own is stopped at once: the launch window is sent back, another window closed, an iframe blanked. It's then recorded, and the step fails off-app.
  • The app's own pages:
    • local `file:` paths in its bundle or sandbox (decoded, then normalized);
    • its sealed scheme or dev server;
    • its listed https sites (`sameApp`, never plain http);
    • blobs of those.
  • One app per run: no goto, URL condition, web preset or web interrupt rule (skipped, with a warning). It's confined while grounding too, and keeps no sign-in between runs yet.

Windows:

  • Windows the main process opens are followed as popups are, each sized to the scene first.
  • A window that opens blank gets a moment to load.
  • A frame stopped in a window the run isn't driving is reported as a warning only.

Shutdown

  • The process group is killed, and only while it's known to be alive: its leader runs, its windows are still connected, or the group has lost its leader and no live process holds the leader's id. A recycled process id is never signalled.
  • Processes pointing at the sandbox are swept (`ps` arguments, `lsof` cwd).
  • A state file lets the next start sweep what a crash left. It only ever touches folders this module made, and a sweep keeps launches noted meanwhile.
  • Closing twice is the same ending.

Measured:

  • Isolation holds for HOME, XDG and the profile. Electron's `appData` and macOS preferences (`cfprefsd`) stay the user's, so PR 3's trial launch checks both.
  • Recording, clicks and the screencast work with the window hidden.
  • Same as web runs, and stated: a page loads before the guard stops it, and the runner's navigation events name it.

Known limits (the user's decision, to settle on real apps in PR 3 and PR 5):

  • a splash screen that stays up longer than 1.5 s is taken for the main window;
  • a hidden helper window opened during a step may be followed;
  • only the launch window is sent back, any other is closed;
  • a stop that lands after its step ended counts against the next step.

The handling of a window that the guard closes while it's being prepared is a timing race, untested.

Reviews: six `/code-review high` rounds plus two architect reviews. Rounds 1 to 3 found leaks, which led to the redesign around the continuous guard. Rounds 4 to 6 found no leaks, only fixes and the window questions above.

Tests: 32 Electron tests on a fixture app run with the repo's Electron 44.

  • Sandbox and environment.
  • Driving at the emulated size, filming.
  • The guard: a site's window closed, a user's file iframe blanked, the main window sent back, a failed load reported as one.
  • Embeds at launch (a local https server with a throwaway certificate; plain http refused).
  • Windows: splash, swap, Preferences, DevTools, opening blank.
  • One app per run, sessions, interrupt rules.
  • Shutdown: the group, a detached helper, a dead leader, a crash sweep, the state-file race.
  • Launch errors.

Bug-backs: about 60 mutations across the rounds. All are caught, except where one exposed dead code (removed) or a missing test (added).

CI: the unit tests run under `xvfb-run`, with the user-namespace setting moved before them. The batch suite gets the 30 s budget other browser suites use. The fixture app is excluded from lint.

Linux CI (three follow-up commits). On the Linux runner, closing a launched app took about 30 s. Shutdown timings were logged in CI (KIFRAME_ELECTRON_TIMING) to find the cause.

  • Close: closing the CDP connection hangs there. It's now capped at 2 s, because the process group is killed right after anyway.
  • Sweep: it reads working folders from /proc on Linux instead of a system-wide lsof.
  • Quitting: an app that quits as it's attached, or before showing a window, is reported as quitting at once. On Linux the connection drops before the exit is seen, so the launch now waits briefly for the exit before deciding.
  • Timeouts: the cleanup hook gets 30 s. An unrelated timing-ratio test in the studio gets 30 s under the Electron tests' load; its assertion is unchanged.

@codecov

codecov Bot commented Oct 8, 2026

Copy link
Copy Markdown

@winlp4ever
winlp4ever merged commit 2dc9c32 into epic/electron-targets Oct 8, 2026
2 checks passed
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