Projection-keyed AppContainer SIDs
The Windows OS sandbox mints one AppContainer SID per distinct fs
projection a session confines, so a child’s kernel-checked filesystem
authority is exactly the projection its command declared — never the union
of what other commands in the session stamped. Supersedes
session-scoped-appcontainer;
realised in core/src/sandbox/windows/session.rs.
Context
Under the session-scoped SID, ACEs accumulated across a session’s commands:
a later command could open any path an earlier command’s projection granted
(and stayed blocked on any earlier deny_path), because the OS access check
saw the union of ACEs ever stamped for the one SID. That defeated exactly
the constructs that promise attenuation — a nested grant [fs: …] { … }
running an untrusted tool, and an exarch subagent forked with narrowed
permissions (parent ⊓ base): their in-process narrowing held, but any
permitted external child escaped to the session union at the kernel. On
macOS/Linux the fresh per-command sandbox has no such gap.
Decision
Key the SID by the projection’s content, not by session or by frame instance:
- The key is `bind_spec` identity — deduplicated,
sorted read/write/deny prefix lists, so structural equality is projection
identity.
netis excluded: network authority rides per-spawn capability SIDs, never disk state. - A new projection mints a profile named
ral.sandbox.s<pid>.p<n>— unique by a monotonic counter, never by content hash: a name collision would silently merge two projections’ authority under one derived SID, so it must be impossible rather than unlikely. - Commands sharing one frozen projection share one SID and one set of stamps. Every turn of an agent session under one policy is the same key, so the profile-create and ACE-stamp cost is paid once per distinct projection, not per spawn — content-keying preserves the amortisation a per-frame SID would have forfeited, with identical enforcement (identical authority under one SID is not a security distinction).
- Profile registrations are ledgered before the OS-level create — the same ledger-before-mutation ordering the DACL engine protects for ACEs — and the boot sweep deletes a crashed session’s profiles alongside restoring its ACEs.
- ACEs still revert at session teardown, not at grant-frame exit: a detached worker spawned under a SID legitimately outlives its frame with the authority it was born with (revoking ACEs would break its opens without revoking its token), and a SID with no live child is inert — no other token carries it, and only the session’s own spawns can mint one.
Consequences
- Within-session attenuation holds at the kernel: a narrowed
grantor subagent spawns under a SID the wider paths were never granted to, and an earlier projection’sdeny_pathno longer bleeds into later commands whose projections grant that path. - The per-projection profiles accumulate only until teardown, each carrying only its own projection’s ACEs; a pathological many-projection session costs registry entries and stamps linear in distinct projections.
- One residue inside a single SID: the program-image read-only grant is memoised per projection, so two different binaries run under the same projection each leave an RO stamp on the other’s image path — trivial surface (read+execute on executables), accepted as before.
- Deviation from MXC Tier 3 (which scopes its DACL guard to one execution): ral holds one session ledger and keys authority by projection instead — noted here rather than at a breadcrumb site since it is a lifecycle choice, not a ported unit.
See also
capabilities, capability-enforcement, session-scoped-appcontainer, windows-spawn-boundary, grant, two-enforcers.