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.
39 lines
1.9 KiB
TypeScript
39 lines
1.9 KiB
TypeScript
/**
|
|
* The per-workspace write identity: a deterministic `S-1-4-x-y` SID derived
|
|
* from the canonical workspace path, whose ACEs form that workspace's write
|
|
* allowlist. Every confined execution of the same workspace — across
|
|
* sessions, server restarts, and calls — carries the SAME write SID, so the
|
|
* workspace-root ACE materializes once per workspace per machine (the
|
|
* grant's exact-ACE skip then makes every later provision O(1)) instead of
|
|
* once per session. The SID's power is defined solely by the ACEs that name
|
|
* it (which exist only on the workspace tree and the session's private temp
|
|
* directory), and only tokens minted for that workspace carry it — the SID
|
|
* string itself is not a secret (the previous per-session SID was likewise
|
|
* logged in the plain).
|
|
*
|
|
* The input MUST be the canonical workspace path (`realpathSync.native` on
|
|
* Windows — the sandbox-policy `resolveWorkspaceRoot` already applies it):
|
|
* canonicalization converges case/alias spellings, so two spellings of one
|
|
* workspace derive one SID; an as-spelled fallback path would mint a second
|
|
* identity for the same directory (self-healing, at the cost of one extra
|
|
* tree propagation). Renaming the workspace directory derives a new SID —
|
|
* the old standing ACEs are inert residue, and the next session re-propagates
|
|
* once.
|
|
* @module @deepseek-ai/dsh-sandbox-windows-acl/workspace-sid
|
|
*/
|
|
|
|
import { createHash } from 'node:crypto'
|
|
|
|
/**
|
|
* Derive the workspace's write SID (`S-1-4-x-y`; subauthorities 30-bit,
|
|
* matching the orphan shape the token and ACE layers already carry).
|
|
* @param workspaceRoot - the canonical workspace path.
|
|
* @returns the SDDL string form.
|
|
*/
|
|
export function workspaceWriteSid(workspaceRoot: string): string {
|
|
const digest = createHash('sha256').update(workspaceRoot, 'utf8').digest()
|
|
const first = (digest.readUInt32LE(0) % (2 ** 30 - 1)) + 1
|
|
const second = (digest.readUInt32LE(4) % (2 ** 30 - 1)) + 1
|
|
return `S-1-4-${first}-${second}`
|
|
}
|