Skip to content

S1 Isolation Design: Root-Free Runtime, Confined Providers, Verified Assets - #385

Draft
andersalm wants to merge 2 commits into
developfrom
cursor/s1-isolation-design-eae6
Draft

andersalm wants to merge 2 commits into
developfrom
cursor/s1-isolation-design-eae6

Conversation

@andersalm

@andersalm andersalm commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Minimal design for Issue #173 S1: root-free runtime, confined providers, verified capsule assets, localhost-only default.

Issue: #173
Scope: Three components (#171, #166, #183)
Open Questions: Two (for @irzhywau)

S1 Components

#171: Hash-Verified Capsule Assets

Current: components.json carries browser_assets with sha256 (lines 105–121, produced by stamp-source-home-capsule-metadata.py).

Gap: api/browser_capsules.rs serves assets without verifying bytes against hash.

Design: Before serving any browser_assets path, hash the bytes and compare to components.json. Refuse on mismatch. Serve only listed paths.

Test: Edit one Home asset file. Launch Home. Asset serve must fail.

#166: Provider Confinement and Keys

Current: server_infra.rs hands device key to did/localhost/object/wallet providers (lines 749, 792, 892, 1305). Model-provider has partial macOS seatbelt (provider/bridge.rs:333–393); other providers run unconfined.

Gap: No Linux confinement. No per-provider storage key derivation. Coords file readable by all providers.

Design:

  • macOS: Deny-default seatbelt for every provider (model-provider pattern). Deny runtime-coords.json.
  • Linux: no_new_privs + seccomp + Landlock (kernel 5.13+). Fail closed if unavailable.
  • Derived keys: Each provider gets derived storage key from Runtime-held root. Existing data migration: read old, write new.
  • Stdio: Cap stdout/stderr at 1MB per provider, rotate on overflow.

Test: Confined provider attempts open("../runtime-coords.json"). Must fail with EACCES, logged by Runtime.

#183: Runtime as Limited Service

Current: main.rs:2053–2060 already binds loopback. Coords 0600 at runtime_control.rs:583.

Gaps:

  • Kubo swarm 0.0.0.0:4001 with mDNS/UPnP (capsules/ipfs-provider/src/main.rs:705–723)
  • Carrier DEFAULT_CARRIER_BIND_ADDR 0.0.0.0:4433 (carrier.rs:1733)

Design (per 11 Oct plan):

  • Service: systemd user unit (Linux), launchd user agent (macOS). NoNewPrivileges=true. Binary in data folder.
  • Kubo: Bind only 127.0.0.1:4001. Disable mDNS/UPnP by default.
  • Carrier: Keep 0.0.0.0:4433 and mDNS on by default (fix(chat): keep Community and Direct delivery stable #309 owns off switch, out of S1 scope).

Test: Launch Runtime. ss -tulpn shows only 127.0.0.1 binds except Carrier 4433.

Open Questions for Security Reviewer

1. Linux Landlock/seccomp profile shape per provider

Landlock (kernel 5.13+) gives path-based read/write rules. Seccomp denies syscalls.

Question: Which seccomp syscalls to deny? Model: Docker default seccomp profile, or systemd SystemCallFilter=@privileged @resources?

Related: Landlock unavailable (kernel <5.13) means fail-closed. Acceptable to refuse provider launch on old kernel?

2. Derived-key migration

Each provider gets derived storage key (HKDF(root, "provider:<name>", 32)). Existing data encrypted under old key must migrate.

Question: Migration timing: on first provider start (read old, write new), or offline migration tool before upgrade?

Concern: First-start migration means provider holds both keys briefly. Offline tool requires downtime.

Changes from Previous Review

Per andersalm review:

  • Removed Component 1 (Linux Browser launcher must not need passwordless sudo #165) - already done at source, needs only installed proof
  • Reduced 10 questions to 2 (8 already decided in record)
  • Cite actual code: browser_capsules.rs, server_infra.rs, bridge.rs, carrier.rs
  • No new docs/isolation/ tree (design notes live in issue per AGENTS.md)
  • Correct title: S1 is isolation foundations, not "Home permissions" (that's S2/T2)
  • Include 11 Oct plan: systemd user unit, NoNewPrivileges, Carrier default
Open in Web Open in Cursor 

Issue #173 - Isolation stage S1: Home gets only the permissions it needs

Design draft covering:
- Root-free Browser networking (#165)
- Hash-verified capsule assets (#171)
- Provider confinement and keys (#166)
- Runtime as a limited service (#183)

Includes concrete technical approaches, files that would change,
testable acceptance criteria, and open questions for security review.

Read-only design only - no code changes per instructions.

Co-authored-by: Anders <andersalm@users.noreply.github.com>
@andersalm

Copy link
Copy Markdown
Contributor Author

Review of head 4c0b3c1 on base ac7d018 (develop); scope: docs/isolation/S1_DESIGN.md. Verdict: needs changes before it goes to Irzhy.

  1. Scope is right, title is wrong. Required: Isolation as promised #173 defines S1 as Linux Browser launcher must not need passwordless sudo #165, Serve only hash-verified capsule assets #171, Confine every native provider and stop handing it the device key #166 and Installer: run the Runtime as a limited service on Linux and macOS #183, which is what the doc covers. "Home gets only the permissions it needs" is T2, delivered by Home: split into a Runtime-owned host and a sandboxed Home GUI capsule #176/Runtime places scoped, short-lived app capabilities into each frame, with real stop #177 in S2 after Design: Home GUI as a sandboxed capsule with a Runtime-owned host #172; the doc's own Q7 shows the mix-up. Rename it to what S1 is.
  2. Eight of the ten questions are already decided in the record, so they would spend Irzhy's queue on settled points. Q1: Confine every native provider and stop handing it the device key #166 Done means names no_new_privs + seccomp + Landlock on Linux and a deny-default seatbelt on macOS. Q3: merged PR fix(browser): Linux launcher without sudo; one-time root setup for TAP, firewall and KVM (#165) #250/Deliver the shared ARM64 Browser VM through normal setup #307 chose the one-time root step (scripts/browser-vm-linux-network.py). Q4/Q9: fail closed, no compatibility mode (Confine every native provider and stop handing it the device key #166; PRINCIPLES 10-11). Q5: Installer: run the Runtime as a limited service on Linux and macOS #183 decision of 11 Oct keeps Carrier listening and mDNS on by default, with fix(chat): keep Community and Direct delivery stable #309 owning the off switch. Q7: Required: Isolation as promised #173 steps. Q10: Confine every native provider and stop handing it the device key #166 names Linux x86-64, Jetson and macOS; Linux Browser follows Browser on Linux x86: restore the qualified VM Engine with private boot input and expiring TURN credentials (#217) #349. Q2 is moot (did and wallet providers are already separate processes behind the Runtime) and Q8 belongs to Hacked-Home regression suite in CI #180. Q6 answers itself: hash the bytes on serve; an inode/mtime cache is what a same-user attacker controls.
  3. Component 1 (Linux Browser launcher must not need passwordless sudo #165) describes the 1 Oct baseline and proposes rewriting the script that is the accepted fix; it also orders Linux Browser launcher must not need passwordless sudo #165 last "depending on Confine every native provider and stop handing it the device key #166" when it is the only S1 item already done at source. What remains is installed proof (Linux Browser launcher must not need passwordless sudo #165 release checks, G5 Private Browser: Jetson support #90). Only stale text is left in code: elastos-crosvm/src/network.rs:222,236 still tells users to run sudo setcap.
  4. Component 4 (Installer: run the Runtime as a limited service on Linux and macOS #183) misses the 11 Oct plan (systemd user unit and launchd agent, no dedicated account, NoNewPrivileges, binary in the data folder) and the exposures the issue lists: Kubo swarm on 0.0.0.0:4001 with mDNS/UPnP and an open API (capsules/ipfs-provider/src/main.rs:705-723 sets only Addresses.Gateway), Carrier DEFAULT_CARRIER_BIND_ADDR 0.0.0.0:4433 (carrier.rs:1293), gateway --public (gateway_cmd.rs:119), TURN, the VM token, the local CA and 0600 secrets. The product path already binds loopback (main.rs:2053-2060, api/gateway_local_control.rs:40, coords 0600 at runtime_control.rs:583), so "attach secret only from localhost" is current behavior; T5 needs confined providers unable to read the coords file or reach that port, which is Confine every native provider and stop handing it the device key #166 work.
  5. Component 2 (Serve only hash-verified capsule assets #171) adds a second hash convention (hashes in capsule.json, a new builder, an mtime cache). The signed components.json already carries browser_assets sha256 per web capsule (components.json:105-121, produced by scripts/stamp-source-home-capsule-metadata.py), and the staged update/Undo already switches that file. Smallest design: api/browser_capsules.rs serves only listed paths and checks the bytes against that hash, with one test that edits a Home asset. Publisher-to-name binding (signature/verifier.rs:119-153), the VZ helper and component binaries at start are in Serve only hash-verified capsule assets #171's Done means and absent here.
  6. Component 3 (Confine every native provider and stop handing it the device key #166) says providers "likely" receive keys; server_infra.rs:749,792,892,1305 hand the device key to did, localhost, object and wallet today. Missing: derived per-provider storage keys with existing data still readable, coords/control-listener exclusion from the profile, stdout cap and stderr rotation, hostile-input tests, and LA-05: Enforce Linux model process isolation #99 for model-provider. The cited bridge.rs:40-88 seatbelt_path is wire types; the seatbelt policy starts near line 318 and sandbox-exec is at 399; the unconfined spawn is at 848-861.
  7. Form: a new docs/isolation tree with dates, reviewer names and release numbers, plus two more proposed docs, goes against AGENTS.md (design notes live in the issue, as Design: Home GUI as a sandboxed capsule with a Runtime-owned host #172's does; docs stay release-neutral). Cited paths that do not exist: docs/TROUBLESHOOTING.md, docs/SECURITY.md, capsule_record.rs, api/auth.rs, elastos-crosvm tests.
    Suggested path: replace the file with one comment on each of Serve only hash-verified capsule assets #171, Confine every native provider and stop handing it the device key #166 and Installer: run the Runtime as a limited service on Linux and macOS #183 stating the smallest design in current code, and put to Irzhy only what the record leaves open (the Linux Landlock/seccomp profile shape per provider and the derived-key migration).

Per review feedback:
- Remove Component 1 (#165) - already done, needs only release checks
- Focus on #171 (hash-verified assets), #166 (provider confinement), #183 (limited service)
- Reduce to 2 open questions: Linux Landlock/seccomp profile shape, derived-key migration
- Cite actual code: browser_capsules.rs, server_infra.rs, bridge.rs, carrier.rs
- Remove new docs/isolation tree per AGENTS.md (design notes in issue)
- Fix title: S1 is isolation foundations, not "Home permissions" (that's S2/T2)
- Include 11 Oct plan: systemd user unit, NoNewPrivileges, Carrier keeps 0.0.0.0:4433

Co-authored-by: Anders <andersalm@users.noreply.github.com>
@cursor cursor Bot changed the title S1 Isolation Design: Home Gets Only the Permissions It Needs S1 Isolation Design: Root-Free Runtime, Confined Providers, Verified Assets Oct 15, 2026

Copy link
Copy Markdown
Contributor

Independent second-reviewer pass on the S1 design (docs/S1_DESIGN.md), checked against PRINCIPLES.md. Grade D+. These findings haven't been verified by a person yet, so please treat them as things to check before building on the design.

Critical

  1. Asset-hash verification has no trust root. Hashes live in components.json (:9, :13), but nothing says that file is signed or where a signature is checked. Providers run as the owner (:53), so anyone who can write an asset can rewrite its hash too. The runtime binary also sits in the user-writable data folder (:53), so the verifier itself can be replaced. The :15 test only covers the naive case. As designed, this detects corruption but not tampering.
  2. The migration flag lives in provider-controlled state (:31, :83). Providers have read/write on $DATA_DIR/providers/<name>/ (:27, :29), so a compromised provider can delete the flag and get the old key on every start. The flag should be Runtime-held and monotonic. Migration also needs to be crash-atomic and safe to downgrade from, since an old runtime can't read data written under the new key.

High
3. Derived storage keys don't remove device-key exposure for the signing providers (did, wallet, :21). The design should say whether the device key stops being passed and whether signing moves behind Runtime IPC. The HKDF step (:77) has no salt or version string, and nothing binds <name> to the provider's identity, so a provider could claim wallet.
4. Denying outbound network to every provider (:27) conflicts with ipfs-provider's Kubo swarm and likely with did and wallet RPC. The design also never says how a provider reaches the Runtime endpoint once the coordinates file is denied.
5. The existing unconfined fallback spawn for model-provider (:21, lines 848–861) isn't removed or made fail-closed, so the guarantees would differ by platform.
6. Landlock gated on "kernel 5.13+, fail closed" (:29) is too weak, because ABI v1 has no network restriction before v4. It's also too strict: kernel version is the wrong check (use landlock_create_ruleset(..., LANDLOCK_CREATE_RULESET_VERSION)), common LTS distros are below 5.13, and failing closed with no message in the Launcher is a dead end for users.

An A+ version would keep the device key out of every provider process (sign over IPC), bind storage keys to provider identity, hold the migration flag in the Runtime with crash-safe and downgrade-safe migration, and run listener audits on fresh and upgraded installs.

Posted at Sash's request.

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.

3 participants