Skip to content

Load roost launch policies only from configured role roots - #26

Merged
ehrlich-b merged 1 commit into
mainfrom
fix/roost-runtime-policy
Oct 7, 2026
Merged

ehrlich-b merged 1 commit into
mainfrom
fix/roost-runtime-policy

Conversation

@ehrlich-b

@ehrlich-b ehrlich-b commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Security fix for shared and org roosts (found by an adversarial review of harden/all, reproduced on main a07f698; v0.147 and v0.148 have the same discovery path by source).

Hole: an authenticated member could create egg.yaml in a subdirectory of their writable role path, for example eng/sub/egg.yaml with ro:/opt/wingthing/support, launch with cwd=eng/sub through MCP or headless runs, and mount another role's tree. A role policy's relative deny-write:./egg.yaml also left the root policy writable from a subdirectory session.

Fix: on shared and org roosts, every authenticated launch (browser and fork, native and direct MCP terminal_start/agent_start, headless agent_run/swarm with the policy frozen at submission, continuation) uses only the egg.yaml at the deepest configured wing path root containing the cwd, under strict rules:

  • The member must be authorized for that selected root; ancestors don't count.
  • The root egg.yaml must be a regular file, read no-follow from its descriptor, with base: absent, none, or the built-in default.
  • Every configured root's egg.yaml is write-denied in every roost session, and missing targets aren't created.
  • cwd and roots are compared in on-disk case on macOS.
  • An omitted cwd uses the first accessible root.
  • Admins outside any configured root get the wing default; members are refused. A member root without egg.yaml still says "ask the wing owner".

Personal wings keep full workspace discovery.

Behavior changes to look at:

  • Browser members may start in a subdirectory of their role root, matching MCP; the root's policy applies.
  • Roosts now refuse a symlinked role egg.yaml, file-path base: references, nested configured roots, and roots that are or pass through a symlink. Relative rules in the role policy are anchored to the role root. Slide's Ansible config (sibling /opt/wingthing/<role> roots, regular file, base: none) is unaffected.

Evidence: new tests for each rule (selected-root ACL across browser, MCP, forks, agents and swarms; protection of every root without creating targets; regular-file and base refusal; macOS case; omitted cwd). They fail on the previous version and pass here. A Slide-template compatibility test checks that audit, permission skip, env passthrough and the other-role denies survive. Full egg, eggclient, localmcp, localrelay, wing and config suites, builds and tagged vets pass.

@ehrlich-b
ehrlich-b force-pushed the fix/roost-runtime-policy branch 4 times, most recently from 042788a to 878ddaa Compare October 7, 2026 08:54
@ehrlich-b
ehrlich-b force-pushed the fix/roost-runtime-policy branch from 878ddaa to 8cf1360 Compare October 7, 2026 08:58
@ehrlich-b
ehrlich-b merged commit 8cf1360 into main Oct 7, 2026
9 checks passed
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