Skip to content

feat: /workspace cwd + /home/agentos home + binding facade + all-platform publish#110

Merged
NathanFlurry merged 8 commits into
mainfrom
nathan/workspace-binding-agentos
Jun 23, 2026
Merged

feat: /workspace cwd + /home/agentos home + binding facade + all-platform publish#110
NathanFlurry merged 8 commits into
mainfrom
nathan/workspace-binding-agentos

Conversation

@NathanFlurry

Copy link
Copy Markdown
Member

Implements the secure-exec side of SPEC-bindings-workspace.md.

  • User rename useragentos via a clean Alpine re-snapshot (uid/gid 1000); $HOME=/home/agentos. Zero /home/user residue.
  • /workspace default cwd, decoupled from $HOME: base-fs dir + vm.rs SHADOW_ROOT_BOOTSTRAP_DIRS + cwd default + kernel defaults + shadow-bootstrap allowlist.
  • @secure-exec/core sidecar-client facade restored so consumers (agentOS) keep importing SidecarProcess + the Sidecar* types from it. binding (tool rename) present.
  • All-platform publish: 7-platform matrix (linux x64/arm64 gnu+musl, darwin x64/arm64, windows-x64) + 5 new sidecar packages + resolver musl/win32 .exe. (windows/musl flagged for a CI shake-out.)
  • Sweep of /home/user/home/agentos (HOME) / /workspace (cwd) / agentos (username) across crates, tests, docs, examples.

@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 20:11 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 20:11 Destroyed
abcxff and others added 2 commits June 22, 2026 16:24
These were absolute symlinks pointing at /home/nathan/secure-exec/... — a
local checkout — so they dangle on every other machine and break `pnpm
install` (it cannot create a real node_modules over a stale symlink).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The native stack previously compiled only on Linux. This adds macOS
(aarch64/x86_64) support, gated entirely behind cfg(target_os) so the Linux
paths are untouched.

v8-runtime (per-thread CPU accounting):
- timeout.rs: pthread_getcpuclockid has no macOS equivalent; the CpuBudgetGuard
  watchdog now reads the execution thread's CPU time via the Mach thread port
  (pthread_mach_thread_np + thread_info(THREAD_BASIC_INFO)), readable
  cross-thread like the Linux clockid.
- bridge.rs: RUSAGE_THREAD is Linux-only; process.cpuUsage/resourceUsage get
  per-thread CPU time from thread_info, the rest best-effort from RUSAGE_SELF.

sidecar (host-mount filesystem confinement):
- macos_fs.rs: macOS has no openat2(RESOLVE_BENEATH); resolve-beneath path
  resolution now goes through cap-std (audited userspace walk; openat2 on Linux,
  fd-relative O_NOFOLLOW walk elsewhere). F_GETPATH replaces readlink on
  /proc/self/fd; /dev/fd/N replaces /proc/self/fd/N.
- host_dir.rs + filesystem.rs: open_beneath / mapped-runtime helpers resolve via
  cap-std on macOS; O_PATH -> read-only anchor, O_TMPFILE -> empty. All nine
  mapped-runtime child ops (mkdir/rmdir/unlink/symlink/rename/readlink/lstat/
  lutimes) converted from the Linux /proc/self/fd-join idiom to fd-relative *at
  calls, since /dev/fd/N cannot have children appended. lstat goes through a
  platform-neutral HostStat translator; rename uses renameat with a real-path
  EXDEV fallback. Also fixes a latent dangling-fd bug in the mapped utimes path
  (the resolved handle is now held across the operation on both platforms).
- execution.rs: waitid/WNOWAIT is unavailable in nix on macOS; the child-status
  poll uses waitpid(WNOHANG) there.
- Plus latent macOS type-width fixes (mode_t/dev_t/nlink_t).

All sidecar lib tests pass on macOS, including every host-mount escape test.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@railway-app

railway-app Bot commented Jun 22, 2026

Copy link
Copy Markdown

🚅 Deployed to the secure-exec-pr-110 environment in rivet-frontend

Service Status Web Updated (UTC)
secure-exec 😴 Sleeping (View Logs) Jun 23, 2026 at 11:19 am

🚅 Deployed to the secure-exec-pr-110 environment in secure-exec

Service Status Web Updated (UTC)
secure-exec 😴 Sleeping (View Logs) Web Jun 23, 2026 at 11:22 am

@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 5e29442 to c85b45d Compare June 22, 2026 21:24
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 21:24 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 21:24 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from c85b45d to 4ec4609 Compare June 22, 2026 21:26
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 21:26 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 21:26 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 4ec4609 to e2428fd Compare June 22, 2026 21:37
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 21:37 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 21:37 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from e2428fd to bd51902 Compare June 22, 2026 21:39
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 21:39 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 21:39 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from bd51902 to f4f6a8b Compare June 22, 2026 21:42
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 21:42 Destroyed
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 21:42 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from f4f6a8b to 30d5881 Compare June 22, 2026 21:46
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 21:46 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 21:46 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 30d5881 to 12e8986 Compare June 22, 2026 23:19
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 22, 2026 23:19 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 22, 2026 23:19 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 12e8986 to 88fbdf2 Compare June 23, 2026 01:41
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 01:41 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 01:41 Destroyed
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 02:13 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 02:13 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 8575436 to 312c8b3 Compare June 23, 2026 02:15
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 02:15 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 02:15 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 312c8b3 to 3b83506 Compare June 23, 2026 03:47
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 03:47 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 03:47 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 3b83506 to 264b49d Compare June 23, 2026 08:41
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 08:41 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 08:41 Destroyed
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 264b49d to 8492871 Compare June 23, 2026 08:49
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 08:49 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 08:49 Destroyed
…OS (ABI + env + names)

Also fix publish workspace-leak: bumpCargoVersions... bumpPackageJsons only
converted workspace:* deps on DISCOVERED packages, so a dep on the CI-excluded
@secure-exec/core leaked workspace:* into published tarballs (s3/sandbox/
google-drive), breaking consumers. Now convert any @secure-exec/* workspace dep.
@NathanFlurry
NathanFlurry force-pushed the nathan/workspace-binding-agentos branch from 8492871 to a4e69e3 Compare June 23, 2026 09:07
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 09:07 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 09:07 Destroyed
… (guest readdir/exec)

The macOS port converted every host-mount child *mutation* to fd-relative
`*at` syscalls but left directory enumeration on `fs::read_dir(proc_path())`,
where `proc_path()` is `/dev/fd/N` on darwin. Unlike Linux's
`/proc/self/fd/N`, a directory fd's `/dev/fd/N` entry is not readdir-able and
returns `ENOTDIR (os error 20)`. That single miss cascaded into both guest
failures:

  * guest `readdirSync('/')` -> `ENOTDIR`, and
  * guest `exec("/bin/sh")` (ls/tr/node) -> "refused to compile guest
    WebAssembly module ... shell-shim script": `discover_command_guest_paths`
    reads `/__secure_exec/commands` via the same broken readdir, finds nothing,
    so no commands are registered with the CommandDriver and the exec falls
    through to the raw `#!/bin/sh` kernel-command stub.

Fix: add `AnchoredFd::readdir_path()` and use it at the three directory-
enumeration sites (host_dir `read_dir`/`read_dir_with_types`, mapped-runtime
`fs.readdir`). On Linux it returns `/proc/self/fd/N` unchanged; on macOS it
recovers the fd's real host path via `fcntl(F_GETPATH)` — the same fd->path
recovery already used for anchored fds elsewhere on darwin. File re-opens keep
using `/dev/fd/N` (inode-pinned, works), and child mutations keep using `*at`.
Strictly `#[cfg(target_os = "macos")]`-gated; Linux paths are byte-identical.

Also wire `macos_fs` into the `filesystem` integration test, which `include!`s
`host_dir.rs` and so needs the module resolvable in the test binary on macOS.

Tests on Apple Silicon: `--test filesystem` 10/10 and `--test posix_compliance`
7/7 pass; reverting only the macOS readdir arm reproduces "command not found
... /bin/ls".

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 10:21 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 10:21 Destroyed
NathanFlurry and others added 2 commits June 23, 2026 03:49
Second darwin fix, surfaced by running the full sidecar suite natively (which
the readdir fix made possible). Two parts:

1. Production: `create_vm_shadow_root` used `std::env::temp_dir()` verbatim
   (`/var/folders/…` on macOS), but `/var` is a symlink to `/private/var` and
   macOS fd→path recovery (`fcntl(F_GETPATH)`, used by `mapped_runtime_host_path_from_fd`)
   reports the resolved `/private/var/…` form. The mapped-runtime confinement
   then did `strip_prefix(host_root)` of an F_GETPATH'd child against the
   unresolved `/var` root, which failed for every child — so the `fs.readdir`
   entry-typing loop dropped all entries and guest `readdirSync` of a populated
   directory returned `[]`. Canonicalize the shadow root up front (macOS-only;
   host_dir mounts already canonicalize their root for the same reason). Fixes
   the guest-vs-host-Node divergences in posix_path_repro and builtin_conformance.

2. Test harness: several test targets `include!`/`#[path]`-pull sidecar source
   (`host_dir.rs`, `filesystem.rs` via service.rs) whose macOS-only
   `crate::macos_fs::…` references must resolve in the test binary; wire the
   module in (macOS-only). Also add `crates/sidecar/src/macos_fs.rs` — the
   macOS resolve-beneath chokepoint — to the architecture_guards `FS_ALLOW`
   allowlist (sibling to host_dir.rs/filesystem.rs). These let the sidecar test
   suite compile and run on Apple Silicon at all.

Native Apple-Silicon results after this + the readdir fix: 468 passing. The
remaining failures are environmental (no network/python in the sandbox) or
separate pre-existing macOS-port gaps (host_dir EACCES-vs-EPERM errno mapping,
sandbox_agent `/private/var` path unscoping) tracked separately.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…lize mock sandbox-agent root

Two remaining darwin parity gaps surfaced by the now-runnable macOS suite:

1. host_dir chmod/chown/utimes on a symlink returned EACCES, not EPERM. The
   Linux path opens the final component as an `O_PATH | O_NOFOLLOW` anchor and
   rejects a symlink with EPERM, but macOS has no `O_PATH` and `open(O_NOFOLLOW)`
   on a symlink fails — the resolve-beneath refusal surfaced as EACCES before the
   EPERM check could run. Add a macOS branch that `lstat`s the final component
   through the resolved parent fd (`fstatat(AT_SYMLINK_NOFOLLOW)`) and rejects a
   symlink with EPERM, matching Linux; non-symlinks open as the anchor as before.

2. test-only: the `MockSandboxAgentServer` strips its temp-dir root prefix from
   the `realpath` the process helper returns, but on macOS the root is
   `/var/folders/…` while `realpath` resolves to `/private/var/…`, so stripping
   failed and the absolute host path leaked back (the plugin's own unscoping is
   correct). Canonicalize the mock root, same as the production shadow root.

Native Apple-Silicon: host_dir 7/7 and sandbox_agent 15/15 now pass. Both
changes are `#[cfg(target_os = "macos")]`-gated; Linux is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 11:03 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 11:03 Destroyed
… EINVAL)

Review follow-up. The macOS open_metadata_beneath branch routed every path
through split_parent, which has no final component for the mount root `/` and
returned EINVAL — diverging from Linux, where chmod/chown/utimes on the root
succeed (the O_PATH|O_NOFOLLOW open of the root works; root is never a symlink).
Special-case the root: open it directly as the anchor before the lstat path.
macOS-only; Linux unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-110 June 23, 2026 11:12 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-110 June 23, 2026 11:12 Destroyed
@NathanFlurry
NathanFlurry merged commit 165e5dc into main Jun 23, 2026
3 of 4 checks passed
@NathanFlurry
NathanFlurry deleted the nathan/workspace-binding-agentos branch June 23, 2026 14:41
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.

2 participants