Skip to content

Fix (high): sandbox launch-gate rerun bypass, crash cleanup, isolation probe seeding#798

Open
Rigidity wants to merge 1 commit into
appsfrom
fix/apps-sandbox-rerun-gate
Open

Fix (high): sandbox launch-gate rerun bypass, crash cleanup, isolation probe seeding#798
Rigidity wants to merge 1 commit into
appsfrom
fix/apps-sandbox-rerun-gate

Conversation

@Rigidity

@Rigidity Rigidity commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Severity: High (H5 + H6, same subsystem)

H6 — launch gate bypassed during a sandbox rerun (stale baseline)

effective_cap returned the previous baseline for any Pending/Running capability. begin_sandbox_run sets every capability to Running without resetting a previously-Passed baseline, so during a user-triggered rerun the effective state stayed Passed and user apps remained launchable using results that no longer applied. A runner that crashed mid-rerun could leave this "passed but dead" state indefinitely.

Fix:

  • effective_cap now surfaces the live run status (a run is only consulted when current_run is Some, so during a run the gate correctly blocks on Running/Pending until each result is re-established).
  • A SandboxRunCleanup Drop guard resets current_run/running and the baseline (to pending) if sandbox_runner exits abnormally before finalizing, so a crash fails safe (gate blocks) instead of trusting a stale Passed baseline.

H5 — storage-isolation probe passed vacuously

The isolation probes read fixed keys (sage_probe_local_storage, sage_probe_db/sage_probe_key) and passed when absent — and nothing ever wrote them, so a completely broken isolation implementation would still pass.

Fix: before launching the probes, the backend seeds those keys into the host (main Sage) webview's localStorage and IndexedDB via webview.eval, then waits briefly for the async IndexedDB write to commit. A sandbox app that can now observe the seeded values proves an isolation failure; genuine isolation still passes.

Files

  • crates/sage-apps/src/sandbox/state_view.rs
  • crates/sage-apps/src/sandbox/runner.rs
  • crates/sage-apps/src/sandbox/probes/isolation.rs

Notes / follow-ups

  • Seeding is treated as fail-safe: if the host seed can't be written the isolation test errors (blocks launches) rather than passing vacuously. If maintainers prefer a softer failure mode that's an easy tweak.
  • The rerun UX now briefly blocks launches while a manual rerun runs — this is the intended safe behavior called out in the review.

No local build (per request); relying on CI. This gate blocks all user-app launches, so it's worth exercising the sandbox run manually before merge.


Note

High Risk
Touches the gate that blocks all user-app launches and sandbox security probes; behavior changes are intentional but should be manually verified before merge.

Overview
Hardens the sandbox app launch gate and storage-isolation checks so user apps cannot launch on stale or meaningless probe results.

Launch gate during reruns: effective_cap now always reflects the in-flight capability status instead of falling back to a previously Passed baseline while capabilities are Pending/Running. Manual reruns therefore block launches until each check finishes again.

Crash fail-safe: sandbox_runner installs a SandboxRunCleanup Drop guard that clears current_run, resets running, and restores a pending baseline if the runner exits before finalize—avoiding a stuck “passed but no run” state.

Isolation probe seeding: Before isolation probe apps run, the host Sage webview is seeded via eval with matching localStorage and IndexedDB probe keys (plus a short delay for IndexedDB). Probes that read those values now detect real isolation breaks; seed failure surfaces as a test error rather than a vacuous pass.

Reviewed by Cursor Bugbot for commit 445eeed. Bugbot is set up for automated code reviews on this repo. Configure here.

…tion probe

Three related sandbox issues:

- Launch-gate bypass on rerun (H6): effective_cap fell back to the
  previous baseline for any Pending/Running capability, so a user-triggered
  rerun (which sets all caps to Running) kept the gate open using stale,
  previously-Passed results. Effective state now reflects the live run
  status, so the gate blocks until a rerun re-establishes each result.

- Crash cleanup (H6): if the runner task exits abnormally before
  finalizing, a Drop guard now clears current_run/running and resets the
  baseline to pending, so a crashed run can't leave apps launchable against
  a stale baseline.

- Vacuous isolation probe (H5): the isolation probes read fixed keys that
  nothing ever wrote, so the test passed even if isolation were broken. The
  backend now seeds those keys in the host (main Sage) webview's
  localStorage and IndexedDB before launching the probes, so a sandbox app
  that can observe them proves an isolation failure.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 445eeed. Configure here.

tauri::async_runtime::spawn(async move {
let apps_state = app.state::<AppsHostState>();
*apps_state.sandbox.current_run.lock().await = None;
*apps_state.sandbox.baseline.lock().await = build_initial_sandbox_state();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cleanup clears run before baseline

High Severity

When SandboxRunCleanup runs after an abnormal runner exit, it sets current_run to None before resetting baseline. build_effective_state then returns the unchanged pre-rerun baseline (often all Passed) with no live run, so check_gates can allow user app launches even though the crashed rerun had failing or incomplete capability results.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 445eeed. Configure here.

tx.oncomplete = function () {{ db.close(); }};
}} catch (e) {{}}
}};
}} catch (e) {{}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Host seed errors swallowed silently

Medium Severity

The injected host seed script wraps localStorage and IndexedDB writes in empty catch blocks, and Rust treats a successful webview.eval as success. If those writes fail, seed_host_isolation_probe still returns Ok, so isolation probes may see no host data and the test passes even when storage isolation is broken.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 445eeed. Configure here.

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