-
Notifications
You must be signed in to change notification settings - Fork 3
plat 449
| Coordination | Value |
|---|---|
| State | fixed on main (2026-10-04), not deployed; regression tests added; the Crew/<id> half is covered by the Crew move (PLAT-442 step 4) |
| Priority | P1 |
| Date | 2026-10-04 |
| Owner | security-sandbox |
Review of AgentWorks a04b393c9, especially PLAT-442 commits 9901c8b94
and 5dad1a36c, with provider ae8e204 and mcpagent ffc32d7.
product.json is user-editable project data, but resolveProjectOwner trusts
its owner_id ahead of the physical owner, logs disagreements and still
returns that identity. decideTurnRunAs uses it to select a Linux slot.
Meanwhile resolveCrewProjectBinding admits the caller as owner based on their
own project tree. These authorities can disagree.
An isolated temporary test used newMultiUserFixture and the actual proxy,
project binding, CLI working-directory and run-as decision helpers:
- User A's PUT of their Code's
product.jsonpasses the proxy (status 0). - Change only
owner_idto B, retaining every other manifest field. - A still resolves the project with
OwnedByCaller=true. -
decideTurnRunAsreturns B and B'sslot09, despite A being the caller and the project still being under A's private tree.
The probe passed, logging [OWNER_MISMATCH] followed by the wrong launch
identity. No real CLI was launched and no live account/files were touched;
this confirms wrong identity selection, not a demonstrated Linux exploit.
PLAT-451 records a separate tmux fallback when that script and folder disagree.
Sources: agent_go/cmd/server/product_owner.go:203, run_as.go:120,
crew_access.go:145, workspace_proxy_policy.go:104 (server-owned protections
exclude project owner metadata).
Make ownership authoritative server-controlled metadata rather than accepting
an arbitrary user-writable owner_id for an OS identity. An authenticated
transfer, if supported, must validate and update all authorities together.
For private Code, reject mismatches with its admitted owner before launch.
Cover both browser/proxy writes and native edits; protecting only the UI is
insufficient. Add regression coverage before deploying PLAT-442.
-
Server-controlled owner registry (
project_owner_registry.go):<state root>/ownership/projects.json, directory 0700, file 0600, app-owned, outside the docs root (so neither the workspace proxy nor a CLI folder guard can reach it), keyed by product and folder name, atomic temp-file + rename writes serialized by an exclusive file lock (the server and the migration command both write), reads refresh when another process changed the file, never rewrites an unreadable file, never changes a registered owner (errProjectOwnerConflict), never writes through a symlink. A test binary never touches the real state area. -
Resolution (
resolveProjectOwner): the registry entry; else the owner the PHYSICAL PATH names; else (a project atCrew/with no entry) nobody.product.jsonis never read for ownership; itsowner_idis still written, as information.resolveCrewPath,readCrewManifestOwner(the shared-root owner) anddecideTurnRunAsuse it. -
Writers: the startup scan (
migrateProductOwners), the owner opening a project (ensureProjectOwnerID) and server-side Crew creation (writeCrewCreationManifests) register the owner (from the path / the creating user); the Crew move registers what it moves. -
Admission: a project found in the caller's own tree that is registered to someone else (a copy or planted folder) is refused (
resolveCrewProjectBinding,[OWNER_MISMATCH]). A private Code launch whose resolved owner is not the admitted caller is refused BEFORE any CLI starts, with an explicit error (checkProjectLaunchOwner,errProjectOwnerMismatch), no fallback. -
Tests first/with:
multiuser_identity_test.goTestEditedManifestOwnerNeverChangesOwnershipOrLaunchIdentity(A edits their Code's owner_id to B through the proxy gate and by a native edit; A still opens it, the launch identity stays A's, B's slot is never selected, B cannot open it; B plants a copy of A's Code in B's tree: refused, no launch; the same for a Crew in its owner's tree and for a Crew atCrew/<id>),product_owner_test.go,project_owner_registry_test.go.
- Ownership transfer is not supported and must not be added without updating the registry and the project's location together (DECISIONS 2026-10-04).
- UI-created Crews and Codes get a registry entry on first open or the next start (path owner); until then the path is the authority, which is the same owner.
- Review the registry after the first deploy:
[OWNER_BACKFILL] ... conflicts=Nand[OWNER_MISMATCH]lines list folders registered to one owner that also exist in another tree; do not "fix" them on live data without confirming the intended owner.
Auto-synced from docs/ on main. Edit there, not here.