feat(sandbox): derive the windows-acl write SID per workspace, not per session

The per-session random write SID forced a full tree propagation per
session per server lifetime (minutes on large workspaces). The write
SID is now the per-workspace identity derived from the canonical
workspace path (workspaceWriteSid: sha256 -> S-1-4-x-y), stored
nowhere: the workspace-root ACE materializes once per workspace per
machine and every later provision hits the exact-ACE skip.

- workspace ACEs are STANDING (never revoked - the reuse cache); temp
  ACEs stay revocable (disposed with the provider), so an inheritable
  ACE never outlives its session's temp dir on the ambient temp root
- AclSandbox requires the write SID under workspace-write; read-only
  parses/grants nothing; the runner derives the SID itself (the
  --write-sid flag's presence still marks the seam-managed contract)
- the acl-session record drops writeSid (sessionId/workspace/tempDir
  remain): the SID-tamper surface and its validation are gone
- sandbox-local holds two grant maps: standing workspace grants and
  revocable per-session temp grants

Docs (README pair, design note pair, catalogs, type-equiv) and the
acl-session/grant/acl/probe/runner suites updated; workspace-sid.spec
pins the derivation contract.
This commit is contained in:
Huanqi Cao
2026-08-09 10:44:35 +08:00
parent d18dc76b89
commit 5fea4b7c4b
24 changed files with 543 additions and 330 deletions

View File

@@ -1231,7 +1231,7 @@ export interface Config {
}
```
Source: [`packages/sandbox/sandbox-local/src/index.ts:39`](../packages/sandbox/sandbox-local/src/index.ts)
Source: [`packages/sandbox/sandbox-local/src/index.ts:43`](../packages/sandbox/sandbox-local/src/index.ts)
## `@deepseek-ai/dsh-sandbox-policy`

View File

@@ -498,16 +498,16 @@ Source: [`packages/core/session/src/types.ts:276`](../packages/core/session/src/
```ts persistence-catalog
/**
* The session's windows-acl write identity was provisioned — log-only
* The session's windows-acl write record was provisioned — log-only
* (like `sandbox/mode`; NOT a surface event, carries no `surfaceOp`):
* durable and replayable, never in the model transcript. The LAST such
* event owned by the session is its record ({@link sessionAclRecord});
* the provider appends exactly one on the session's first Windows
* confined execution.
* confined execution. The write SID itself is NOT stored — it is the
* per-workspace identity derived from `workspace`
* (`workspaceWriteSid`).
*/
'sandbox/acl-session': {
/** The orphan write SID (`S-1-4-x-y`) whose ACEs form the session's write allowlist. */
writeSid: string
/** The owning session — the binding a fork's copied event cannot satisfy. */
sessionId: SessionId
/** The workspace root the grant applies to (the session's immutable cwd, as resolved). */
@@ -517,7 +517,7 @@ Source: [`packages/core/session/src/types.ts:276`](../packages/core/session/src/
}
```
Source: [`packages/sandbox/sandbox-local/src/acl-session.ts:36`](../packages/sandbox/sandbox-local/src/acl-session.ts)
Source: [`packages/sandbox/sandbox-local/src/acl-session.ts:43`](../packages/sandbox/sandbox-local/src/acl-session.ts)
#### `sandbox/mode` — log-only

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/sandbox.md
sandbox.md: 37aba27c589be80d8a3ea9201fd8d13c7b259eda
sandbox.zh.md: 244e351a5db1bfb0d2c9069e8d07bd52f9d15bb0
sandbox.md: 20e0f36a5edb211ea409208d4e5e4a9be2e91d46
sandbox.zh.md: 0619046f2fa98a89275e2dad85f0eadaf8e4a231

View File

@@ -56,8 +56,9 @@ interface SandboxExecutionPolicy {
/**
* Opaque identity of the calling session (the branded `dsh-session`
* SessionId). Backends key per-session state off it (e.g. the windows-acl
* per-session write grant and private temp subdirectory); absent for
* agentless calls, which fall back to per-call backend state.
* per-session private temp subdirectory — the write grant itself is
* per-workspace, derived from the workspace root); absent for agentless
* calls, which fall back to per-call backend state.
*/
sessionId?: SessionId
}
@@ -183,7 +184,7 @@ Abstract process-sandbox service. confine must return enforcing argv or fail clo
abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv
```
Source: [`packages/sandbox/sandbox/src/index.ts:157`](../../packages/sandbox/sandbox/src/index.ts)
Source: [`packages/sandbox/sandbox/src/index.ts:158`](../../packages/sandbox/sandbox/src/index.ts)
<a id="ctxsandboxpolicy--sandboxpolicyservice"></a>

View File

@@ -56,8 +56,9 @@ interface SandboxExecutionPolicy {
/**
* Opaque identity of the calling session (the branded `dsh-session`
* SessionId). Backends key per-session state off it (e.g. the windows-acl
* per-session write grant and private temp subdirectory); absent for
* agentless calls, which fall back to per-call backend state.
* per-session private temp subdirectory — the write grant itself is
* per-workspace, derived from the workspace root); absent for agentless
* calls, which fall back to per-call backend state.
*/
sessionId?: SessionId
}
@@ -183,7 +184,7 @@ Abstract process-sandbox service. confine must return enforcing argv or fail clo
abstract confine(argv: readonly string[], policy: SandboxPolicy): ConfinedArgv
```
Source: [`packages/sandbox/sandbox/src/index.ts:157`](../../packages/sandbox/sandbox/src/index.ts)
Source: [`packages/sandbox/sandbox/src/index.ts:158`](../../packages/sandbox/sandbox/src/index.ts)
<a id="ctxsandboxpolicy--sandboxpolicyservice"></a>