Skip to content

shell: a process app's program stays readable inside the OctoSense home (G6 regression) - #178

Merged
ymote merged 4 commits into
mainfrom
fix/sandbox-program-inside-private
Sep 30, 2026
Merged

ymote merged 4 commits into
mainfrom
fix/sandbox-program-inside-private

Conversation

@ymote

@ymote ymote commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

On macOS main (7abf34d) the Terminal, a native module hosted as a process on the desktop, crashes at start: Terminal exited before opening a window (signal: 11 (SIGSEGV)). There are 5 crash reports, all in MTLCreateSystemDefaultDevice → IOSurface → CFBundleCopyBundleURL (null bundle).

Cause. G6 (#156) closes the OctoSense home last in the process sandbox. Desktop builds put process apps' programs in <OctoSense home>/build, so the private deny (last match wins) takes away the Terminal's read access to its own program and bundle.

Reproduced with the generated profile: sandbox-exec -f <profile> terminal exits 139. With the program re-allowed after the private rules, it passes Metal init and exits 0 (no hub in the test).

Fix:

  • macOS: after the private deny and the app's own jail, re-allow read-only the program paths that lie inside a private directory.
  • Linux (Landlock): program rules are no longer dropped inside private directories; the same bug would hit a program under the OctoSense home.
  • Test: a_program_inside_the_octosense_home_stays_readable checks that the program is readable after the deny, is never writable, and that nothing else is reopened.

Local checks: cargo test --locked --features mobile-apps -p octosense-shell sandbox: 12 passed, including the real macOS sandbox test home_rw_never_reaches_the_octosense_home_or_the_kernel.

🤖 Generated with Claude Code

…me (G6 regression)

G6 (#156) closes the OctoSense home last in the process sandbox. Desktop
builds put process apps' programs in <OctoSense home>/build, so the
Terminal lost read access to its own bundle: Metal's device setup then
crashed at start (SIGSEGV in IOSurface/CFBundleCopyBundleURL, 5 crash
reports on macOS). Reproduced with the generated profile (exit 139) and
fixed by re-allowing the program read-only after the private rules
(exit 0).

- macOS: after the private deny and the app's own jail, re-allow read
  only the program paths that lie inside a private directory.
- Linux (Landlock): program rules are no longer dropped inside private
  directories, like the app's own jail and secrets.
- Test: a program inside the OctoSense home is readable after the deny,
  read-only, and nothing else is reopened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote

ymote commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

I reviewed this (I wrote the G6 code). The diagnosis is right and the fix is right in shape: reopen the program read-only after the private deny, and on Linux keep program rules in around_private. Please add one guard before merging.

Guard: never reopen a program path that equals or contains a private-sensitive dir.

  • Policy::program also holds "the checkout it was built from" and cargo's source cache. Nothing currently stops an entry from being broad.
  • For example, a dev setup that points the checkout or CARGO_TARGET_DIR at ~/.octosense itself (or at ~/.octosense/..) would reopen the whole OctoSense home read-only: peer host tokens, other apps' secrets, kernel data. That's the exact thing G6 closes.
  • The filter only checks p.starts_with(root) ("inside a private dir"). A path that equals a private root passes it too.

Suggested rule, the same on both platforms:

  • only reopen a program path strictly inside a private root;
  • skip it (and log) if it equals a private root, or if any of these is at or under it: the apps root, the secrets root, the kernel's core dir, or the peers/profile dirs.

Tests to add:

  • a program equal to the OctoSense home is not reopened;
  • a program at <home>/.octosense/build/x is reopened, but <home>/.octosense/secrets stays denied (assert that no allow after the deny covers secrets/ or apps/).

Linux. policy.program.contains(&rule.path) keeps the program rule with whatever rights rules() gave it. Please confirm those are read (plus execute), never write, since the macOS side is explicitly read-only. The same guard applies there.

Minor: the new test builds the path from home_rw_with_octosense_home. A comment naming which private roots that helper sets would make the "nothing else reopened" assertion easier to follow.

Otherwise LGTM. The rest can merge once CI passes and the guard is in.

🤖 Generated with Claude Code

Review on #178: `program` also carries the checkout and the cargo cache,
and `starts_with` matched a path equal to a private root, so a dev setup
whose checkout or target dir is the OctoSense home would have reopened
the whole home read-only (peer tokens, secrets, other apps' jails).

- sandbox::program_reopenable: only a path strictly inside a private
  directory that neither is nor contains one, and is not inside a
  sensitive one (apps, secrets, the kernel's home and core dir,
  app-peers).
- macOS: a program path inside a private dir that is not reopenable is
  skipped, logged, and recorded in the profile.
- Linux: the kept program rule is masked to read+execute (never write,
  even under cargo); a skipped one is logged.
- Tests: every unsafe shape is refused and nothing is reopened; the
  Linux rules keep read+execute only (Linux-only test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ymote

ymote commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

Re-reviewed at 3619f0d. program_reopenable covers the cases I raised: strictly inside a private root; not equal to or containing a private dir; not inside apps/secrets/core or the kernel's .octos home; not under app-peers. Linux masks the kept rule to read+execute. One thing to keep: both sides of the comparison must be resolved paths (symlinks), as private is. If program entries aren't resolved the same way, a symlinked checkout could bypass the check, so please confirm they go through the same resolved(). LGTM once CI is green.

🤖 Generated with Claude Code

ymote and others added 2 commits September 29, 2026 16:58
Review on #178: macOS already resolves program and private paths before
comparing; Linux compared raw spellings. Landlock follows links when it
opens a rule's path, so a linked checkout or grant into the OctoSense
home could slip past the containment check (this held for every rule in
around_private, not only programs). The split step now resolves the rule
paths, the program paths, the app's own jail and secrets, and the
private dirs before comparing.

Test: a checkout linked to the OctoSense home reopens nothing on macOS
(and, on Linux, Landlock never grants the home through the link); a link
to the build dir itself still resolves strictly inside and is reopened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the program fix the Terminal starts, but every settings save failed:
'could not save settings to ~/.octosense/terminal/settings.conf:
Operation not permitted'. The shell hands every child MAKEPAD_HOME =
the OctoSense home, which G6 closes to process apps.

A sandboxed process app now gets MAKEPAD_HOME = its jail (ADR 0004 §11),
so the Terminal keeps settings in ~/.octosense/apps/terminal/terminal/.
Its old folder (<OctoSense home>/<app id>) is copied into the jail once,
by the host, before launch; the old copy stays.

Test: the legacy folder is copied once, never over a newer jail copy,
and nothing is created when there is nothing to adopt. Shell tests
578/578.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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