Skip to content

Write prewarm secret files under the data root, not Dir.tmpdir - #173

Merged
JPDuchesne merged 2 commits into
mainfrom
secret-files-colima-visible
Sep 16, 2026
Merged

JPDuchesne merged 2 commits into
mainfrom
secret-files-colima-visible

Conversation

@JPDuchesne

Copy link
Copy Markdown
Contributor

On macOS + colima the VM shares $HOME and /Users/Shared but not /var/folders (Dir.tmpdir) — and docker silently turns a bind mount from an unshared host path into an empty directory. The prewarm container then reads an empty /run/secrets/WWISE_TOKEN and fails far from the cause (hit for real in snappy's image rebuild today; probe: mounting a mktemp file into alpine yields an empty dir, mounting files under the data root works).

Secret temp files now live under <data-root>/tmp, VM-visible on every supported layout. Sticky world-writable dir (data root is shared between human and agent), files stay 0600, caller still deletes them. Test-first: the new test pins files under Dev::DataRoot.path and off Dir.tmpdir.

Made with Cursor

On macOS + colima the VM shares $HOME and /Users/Shared but not
/var/folders (Dir.tmpdir), and docker turns a bind mount from an
unshared host path into an empty directory — the prewarm container
read an empty /run/secrets/WWISE_TOKEN and failed far from the cause.

Secret temp files now live under <data-root>/tmp, which is VM-visible
on every supported layout. The dir is sticky world-writable (the data
root is shared between the human and agent users); files stay 0600
and are deleted by the caller as before.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Audit finding on the data-root move: a shared 1777 dir's owner can unlink
and replace anyone's secret files between write and bind mount (sticky
only stops non-owners), and on an unprovisioned machine /Users/Shared is
world-writable, so any local user can pre-own the tree. Replace the
shared dir with secrets-<uid> directly under the data root (no
world-writable parent that could rename it post-check), created 0700 and
lstat-verified: a real directory, owned by the current uid, or
SecretDirCompromisedError — never a fallback. Also sweep day-old
dev-secret-* leftovers (SIGKILL strands them; macOS never purges the
data root).

Co-authored-by: Cursor <cursoragent@cursor.com>
@JPDuchesne

Copy link
Copy Markdown
Contributor Author

Addressed the security audit in 2a0adc1. Went with the per-identity dir, with one design change from the audit's recommendation: the dirs live directly under the data root (secrets-<uid>, 0700) rather than nested in a shared tmp/ — a shared parent reintroduces the hole one level up, since a world-writable (or attacker-owned) parent's owner can rename a verified dir out from under us after the lstat check. With the dir directly under the data root, the parent chain is the provisioned dev/ (775, group-writable only) under sticky /Users/Shared, so no world-writable link remains.

  • Dir.mkdir 0700 + File.lstat verification (real directory, owned by current uid) → typed SecretDirCompromisedError, never a fallback. The blind rescue Errno::EPERM is gone.
  • Mode normalized to 0700 only when we own the dir (covers Linux setgid propagation from the data root).
  • Day-old dev-secret-* sweep on entry (SIGKILL strands files; macOS never purges the data root, unlike /var/folders).
  • Tests pin the lstat properties and cover the symlink-plant, foreign-owner, and sweep paths.

Severity context for the record: on a provisioned machine the original attack required being inside the trust boundary already (dev/ is 775 human:ai, and sticky stops non-owners unlinking others' files) — the live exposure was the unprovisioned-machine pre-own case and the unverified-assumption code smell. Both closed.

@JPDuchesne
JPDuchesne merged commit 1f5edb4 into main Sep 16, 2026
5 checks passed
@JPDuchesne
JPDuchesne deleted the secret-files-colima-visible branch September 16, 2026 15:33
JPDuchesne added a commit that referenced this pull request Sep 16, 2026
Agent-isolation and emulated-Mac fixes: install-deps failure isolation with typed errors (#170), brew escalation to the prefix owner + AgentBootstrap sudoers edge (#171), BuildKit secret files in a verified per-uid dir under the data root — colima-visible and substitution-proof (#173), build-watcher guest-load liveness signal + 15m stall window (#174), sigpending ulimit for Rosetta signal-queue aborts (#175), and brew trust before tap in docker-install-build-deps (tap-rot fix).
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