docs(sandbox): attribute the named-pipe default SD to the Win32-layer user-mode template
CreateNamedPipeW with NULL security attributes does not install a kernel template: KernelBase builds the documented 5-ACE default SD in user mode and passes it down; the kernel itself (a raw SD-null create) applies the token default DACL. Correct the claim in both README sides and the runner.spec pin comment, and link the MS template documentation.
This commit is contained in:
@@ -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 packages/sandbox/sandbox-windows-acl/README.md
|
||||
README.md: 835b7dace0baf10eec78b35172109a37688d99c2
|
||||
README.zh.md: 82a890d75f857b766c7ae47cef7a374edb5ecd63
|
||||
README.md: 6f649f5dcc4ecf5c2cbbe90a687af25e0c6ecc9d
|
||||
README.zh.md: e688218b2c12a529981433777a1163e3648c918a
|
||||
@@ -83,7 +83,7 @@ None directly; the denial surface belongs to the tool layer.
|
||||
- **Cleanup is best-effort by design** — `dispose()` attempts every temp revocation and aggregates failures into an `AggregateError`; a cleanup failure leaves a standing (but write-SID-only) temp ACE that this process's next `init()`/`dispose()` cycle or `icacls` (via the ACE, not the trustee name) can still remove.
|
||||
- **Standing workspace ACEs are invisible residue.** Renaming a workspace derives a new SID; the old ACEs on the old path stay (inert, write-SID-only). A future cleanup command may reap them; nothing re-propagates because of them.
|
||||
- **NULL-DACL directories are not identity-preserving under grant+revoke.** A directory with a NULL DACL (rare — Windows-created directories carry real DACLs) means "everyone full control"; `grantWrite` builds the new ACL from that null, and the revoke round-trip leaves an EMPTY (deny-all) DACL rather than the original NULL DACL. The POC shares the behavior; real workspace and temp directories carry real DACLs, so this stays a documented edge rather than a guarded path.
|
||||
- **Piped stdio capture is impossible for confined grandchildren (the named-pipe default SD template).** libuv's pipe stdio uses NAMED pipes; `CreateNamedPipeW` without security attributes takes the kernel's PUBLIC default SD template (owner/SYSTEM/Admins full, Everyone/ANONYMOUS read-only) — NOT the token default DACL — so the client-end open requests write access no restricting SID is granted: `spawn(..., { stdio: 'pipe' })` inside a confined process fails with EPERM, the POC-documented "no output redirection" boundary of WRITE_RESTRICTED tokens. Inherited (`inherit`/fd) and ignored (`ignore`) stdio spawns work, and anonymous pipes (CreatePipe — a token-default-DACL consumer, e.g. PowerShell pipelines) work because the restricted token's default DACL carries a full-access restricting-SID ACE (set at init). A confined process therefore cannot capture a grandchild's output through a pipe; tools that must capture output cannot run confined.
|
||||
- **Piped stdio capture is impossible for confined grandchildren (the named-pipe default SD template).** libuv's pipe stdio uses NAMED pipes; `CreateNamedPipeW` without security attributes installs the Win32 layer's user-mode default SD template (built by KernelBase — owner/SYSTEM/Admins full, Everyone/ANONYMOUS read-only, the fixed template [MS documents](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights)) — NOT the token default DACL, which is what the kernel applies to a raw SD-null create — so the client-end open requests write access no restricting SID is granted: `spawn(..., { stdio: 'pipe' })` inside a confined process fails with EPERM, the POC-documented "no output redirection" boundary of WRITE_RESTRICTED tokens. Inherited (`inherit`/fd) and ignored (`ignore`) stdio spawns work, and anonymous pipes (CreatePipe — a token-default-DACL consumer, e.g. PowerShell pipelines) work because the restricted token's default DACL carries a full-access restricting-SID ACE (set at init). A confined process therefore cannot capture a grandchild's output through a pipe; tools that must capture output cannot run confined.
|
||||
- **Grant materialization is an eager full-tree propagation.** `SetNamedSecurityInfoW` on a directory with inheritable ACEs walks every descendant immediately (NOT lazily per access — measured at tens of seconds on large workspace trees plus the real temp root). The per-workspace identity pays it once per workspace per machine (lazily at the first confined execution ever, skipped entirely on every later provision when the exact ACE stands). If a workspace is huge, the first confined write on this host is correspondingly slow.
|
||||
- **Resuming one session concurrently in two server processes races the record.** The durable record lives in the session log; both processes read or provision it independently — the derived write SID is identical, the per-path lock keeps the DACL merges consistent, and the private temp dir race resolves by the last-written record winning for future resumes. Single-writer session usage (the normal deployment) never sees this.
|
||||
- **Read-side confinement and network policy are out of scope** — `WRITE_RESTRICTED` intersects write accesses only; pair this backend with a read-side policy for stronger confinement.
|
||||
|
||||
@@ -83,7 +83,7 @@ koffi 结构体定义在模块加载时对照探针断言其大小,因此头
|
||||
- **清理尽力而为** —— `dispose()` 会尝试全部临时撤销并把失败聚合为 `AggregateError`;清理失败只会留下仅含写入 SID 的临时 ACE,本进程下次 `init()`/`dispose()` 循环或 `icacls`(按 ACE 而非受托者名)仍可清除。
|
||||
- **常驻工作区 ACE 是不可见残留。** 工作区改名会派生新的 SID;旧路径上的旧 ACE 留在原地(失效、仅含写入 SID)。未来的清理命令可以回收它们;它们不会引起任何重新传播。
|
||||
- **NULL-DACL 目录在 grant+revoke 往返下不保持身份。** 带 NULL DACL 的目录(罕见——Windows 创建的目录都带真实 DACL)意味着「所有人完全控制」;`grantWrite` 从该 null 构建新 ACL,撤销往返后留下的是 EMPTY(全部拒绝)DACL 而非原始 NULL DACL。POC 行为相同;真实工作区与临时目录都带真实 DACL,因此这仍是记录在案的边界情形而非守护路径。
|
||||
- **受限孙进程的管道 stdio 捕获不可用(named pipe 的默认 SD 模板)。** libuv 的管道 stdio 用的是 NAMED pipe;不带安全属性调用 `CreateNamedPipeW` 时,其默认安全描述符是内核的**公共模板**(owner/SYSTEM/Admins 全权,Everyone/ANONYMOUS 只读)——**不是**令牌默认 DACL——因此 client 端打开所请求的写访问没有任何 restricting SID 被授予:受限进程内 `spawn(..., { stdio: 'pipe' })` 以 EPERM 失败,这是 POC 记载的 WRITE_RESTRICTED「无法重定向输出」边界。继承(`inherit`/fd)与忽略(`ignore`)stdio 的 spawn 可用;匿名管道(CreatePipe——令牌默认 DACL 的消费者,例如 PowerShell 的管道)因受限令牌默认 DACL 携带 restricting SID 全权 ACE(init 时写入)而可用。受限进程因此无法用管道捕获孙进程输出;必须捕获输出的工具无法在受限下运行。
|
||||
- **受限孙进程的管道 stdio 捕获不可用(named pipe 的默认 SD 模板)。** libuv 的管道 stdio 用的是 NAMED pipe;不带安全属性调用 `CreateNamedPipeW` 时,其默认安全描述符不是内核的模板,而是 Win32 层在用户态安装的默认 SD 模板(由 KernelBase 构建——owner/SYSTEM/Admins 全权,Everyone/ANONYMOUS 只读,即 [MS 文档](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-security-and-access-rights)记载的固定模板)——**不是**令牌默认 DACL(后者才是内核在原始 SD-null 创建时应用的)——因此 client 端打开所请求的写访问没有任何 restricting SID 被授予:受限进程内 `spawn(..., { stdio: 'pipe' })` 以 EPERM 失败,这是 POC 记载的 WRITE_RESTRICTED「无法重定向输出」边界。继承(`inherit`/fd)与忽略(`ignore`)stdio 的 spawn 可用;匿名管道(CreatePipe——令牌默认 DACL 的消费者,例如 PowerShell 的管道)因受限令牌默认 DACL 携带 restricting SID 全权 ACE(init 时写入)而可用。受限进程因此无法用管道捕获孙进程输出;必须捕获输出的工具无法在受限下运行。
|
||||
- **授权物化是急切的全树传播。** 在带可继承 ACE 的目录上调用 `SetNamedSecurityInfoW` 会立即遍历每个后代(**不是**按访问惰性进行——大型工作区树上实测数十秒,加上真实临时根目录)。按工作区身份每台机器每个工作区只付一次(在首次受限执行时惰性进行,之后每次供给在精确 ACE 常驻时完全跳过)。如果工作区巨大,该主机上的第一次受限写入相应变慢。
|
||||
- **两个服务器进程并发恢复同一会话会竞争记录。** 持久记录在会话日志中;两个进程独立读取或供给它——派生出的写入 SID 相同,每路径锁保持 DACL 合并一致,私有临时目录的竞争以后写记录对后续恢复生效而解决。单写者会话用法(常规部署)永远不会遇到。
|
||||
- **读侧隔离与网络策略不在范围内** —— `WRITE_RESTRICTED` 只交叉检查写访问;将此后端与读侧策略配对以获得更强隔离。
|
||||
|
||||
@@ -195,9 +195,11 @@ describe.skipIf(!isWin32 || !pwshAvailable())('windows-acl runner', () => {
|
||||
// so ANONYMOUS pipe creation (CreatePipe — the token-default-DACL
|
||||
// consumer) works and inherited/ignored stdio spawns succeed;
|
||||
// - libuv's pipe-stdio uses NAMED pipes, whose default security
|
||||
// descriptor is the kernel's PUBLIC template (owner/SYSTEM/Admins
|
||||
// full, Everyone read-only) — NOT the token default DACL — so the
|
||||
// client-end open requests write access no restricting SID is
|
||||
// descriptor is the Win32 layer's user-mode default SD template
|
||||
// (built by KernelBase — owner/SYSTEM/Admins full, Everyone/ANONYMOUS
|
||||
// read-only) — NOT the token default DACL, which is what the kernel
|
||||
// applies to a raw SD-null create — so the client-end open requests
|
||||
// write access no restricting SID is
|
||||
// granted: ERROR_ACCESS_DENIED, surfaced as spawn EPERM. That is the
|
||||
// POC-documented "no output redirection" boundary of WRITE_RESTRICTED
|
||||
// tokens; piped capture cannot work and is pinned as DENIED.
|
||||
|
||||
Reference in New Issue
Block a user