fix(sandbox): report Windows ACL limits
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-local/README.md
|
||||
README.md: 23d3a32451c105c71c0a7399ed051288b70753f3
|
||||
README.zh.md: 1890771faf8cab6b1842f973a999c7a9cf2dbb11
|
||||
README.md: 2d36802305ebe34676d896404913b4686d318b97
|
||||
README.zh.md: 565910781dc0f43635dd22d08fefb32f7f4f27e3
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Local implementation of the [`dsh-sandbox`](../sandbox/) seam. It selects and caches one platform runner: Linux prefers a working `bwrap` then Landlock; macOS uses Seatbelt. Multiple candidates are probed in order, while a sole candidate is selected directly.
|
||||
Local implementation of the [`dsh-sandbox`](../sandbox/) seam. It selects and caches one platform runner: Linux prefers a working `bwrap` then Landlock; macOS uses Seatbelt; Windows uses the ACL restricted-token runner. Multiple candidates are probed in order, while a sole candidate is selected directly.
|
||||
|
||||
The package root exports the default and named `LocalSandboxProvider` plugin and `Config`; platform profile builders stay internal.
|
||||
|
||||
@@ -31,7 +31,7 @@ No direct invalidation; the named consumer owns any request-prefix changes.
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Windows has no runner** — `win32` fails closed with `SANDBOX_UNAVAILABLE`; an AppContainer-family backend is deferred.
|
||||
- **Windows ACL enforcement is partial** — the restricted token must retain Everyone for process initialization, so external objects granting Everyone write access remain writable; NTFS hard links also alias one file object across workspace and external paths. The provider reports `enforcement: 'partial'` rather than overstating that boundary as full.
|
||||
- **Landlock may be partial** — older supported kernel ABIs confine only the access classes they expose, reported as `enforcement: 'partial'` rather than overstated as full.
|
||||
- **Seatbelt depends on deprecated `sandbox-exec`** — macOS still ships it, but this provider cannot replace or probe that private policy engine if Apple removes it.
|
||||
- **Runner selection is cached for the provider lifetime** — installing, removing, or repairing a runner requires reloading the plugin before selection changes.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
[`dsh-sandbox`](../sandbox/) seam 的本地实现。它选择并缓存一个平台 runner:Linux 优先选择可工作的 `bwrap`,否则选择 Landlock;macOS 使用 Seatbelt。多个候选项会按顺序探测,只有一个候选项时则直接选择。
|
||||
[`dsh-sandbox`](../sandbox/) seam 的本地实现。它选择并缓存一个平台 runner:Linux 优先选择可工作的 `bwrap`,否则选择 Landlock;macOS 使用 Seatbelt;Windows 使用 ACL 受限令牌 runner。多个候选项会按顺序探测,只有一个候选项时则直接选择。
|
||||
|
||||
包根目录导出默认及命名的 `LocalSandboxProvider` 插件和 `Config`;平台 profile builder 仍为内部实现。
|
||||
|
||||
@@ -31,7 +31,7 @@ Seatbelt profile 默认允许,但带 `(deny file-write*)` 和写入 allow-list
|
||||
|
||||
## 已知限制与暂缓事项
|
||||
|
||||
- **Windows 没有 runner**:`win32` 以 `SANDBOX_UNAVAILABLE` 拒绝执行;AppContainer 家族后端暂缓实现。
|
||||
- **Windows ACL 只能实现部分强制执行**:受限令牌必须保留 Everyone 以完成进程初始化,因此授予 Everyone 写访问的外部对象仍可写;NTFS 硬链接也会使工作区路径与外部路径指向同一个文件对象。提供方报告 `enforcement: 'partial'`,而不会把该边界夸大为完整强制执行。
|
||||
- **Landlock 可能只实现部分强制执行**:较旧且受支持的内核 ABI 只能限制自身公开的访问类别,因此报告 `enforcement: 'partial'`,不会夸大为完整强制执行。
|
||||
- **Seatbelt 依赖已弃用的 `sandbox-exec`**:macOS 仍会提供它,但若 Apple 移除该私有策略引擎,该提供方无法替换或探测。
|
||||
- **runner 选择在提供方生命周期内缓存**:安装、移除或修复 runner 后,必须重载插件才能改变选择。
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
* every later provision O(1) instead of re-propagating the tree per
|
||||
* session); the private-temp ACEs are revoked on dispose. The runner
|
||||
* receives `--write-sid` (the derived identity; its presence marks the
|
||||
* seam-managed contract) and stops managing DACLs itself.
|
||||
* seam-managed contract) and stops managing DACLs itself. The rung reports
|
||||
* partial enforcement because WRITE_RESTRICTED must retain Everyone in its
|
||||
* restricting list and NTFS hard links alias one file object across paths.
|
||||
* @module @deepseek-ai/dsh-sandbox-local
|
||||
*/
|
||||
|
||||
@@ -189,13 +191,12 @@ const STATIC_ENFORCEMENT: Record<SelectedRunner['runner'], SandboxEnforcement> =
|
||||
bwrap: 'full',
|
||||
landlock: 'full',
|
||||
seatbelt: 'full',
|
||||
// 'full' is the SUPPORTED-SURFACE promise: on NTFS both restricting lists
|
||||
// close every ambient write (INTERACTIVE/LOCAL and Authenticated Users are
|
||||
// absent from both — pinned by the runner's Public-probe and CIM-denial
|
||||
// regressions). FAT-class (non-ACL) targets are declared unsupported
|
||||
// (warn-only) in the backend README — outside the promise, not an
|
||||
// exception to it.
|
||||
'windows-acl': 'full',
|
||||
// WRITE_RESTRICTED needs Everyone in both restricting lists for process
|
||||
// initialization. An external object that grants Everyone write access
|
||||
// therefore remains writable, and NTFS hard links can alias a granted
|
||||
// workspace file to a path outside it. The backend enforces the remaining
|
||||
// ACL-addressable surface but must not advertise the absolute promise.
|
||||
'windows-acl': 'partial',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,8 +530,9 @@ export class LocalSandboxProvider extends SandboxProvider {
|
||||
private probeRunner(runner: SelectedRunner['runner']): SandboxEnforcement | 'unusable' {
|
||||
// bwrap's mount profile and Seatbelt's deny-file-write* profile govern
|
||||
// every promised file effect by construction, so their passing probes
|
||||
// are always full enforcement; only the Landlock launcher's probe report
|
||||
// distinguishes full from per-ABI-partial.
|
||||
// are always full enforcement; the Landlock launcher's probe report
|
||||
// distinguishes full from per-ABI-partial, while windows-acl is always
|
||||
// partial for its documented Everyone and hard-link boundaries.
|
||||
switch (runner) {
|
||||
case 'bwrap': {
|
||||
const probe = this.internals.probeBwrap ?? (() => defaultProbeBwrap(this.probeTimeoutMs))
|
||||
@@ -547,7 +549,7 @@ export class LocalSandboxProvider extends SandboxProvider {
|
||||
case 'windows-acl': {
|
||||
const probe = this.internals.probeWindowsAcl
|
||||
?? (() => defaultProbeWindowsAcl(this.windowsAclRunnerInvocation(), this.probeTimeoutMs))
|
||||
return probe() ? 'full' : 'unusable'
|
||||
return probe() ? 'partial' : 'unusable'
|
||||
}
|
||||
default: return assertNever(runner)
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ describe('the windows-acl probe (runner invocation contract)', () => {
|
||||
const confined = sandbox.confine(['true'], RO)
|
||||
expect(probeWindowsAcl).toHaveBeenCalledTimes(1)
|
||||
expect(confined.argv.slice(-4)).toEqual(['--mode', 'read-only', '--', 'true'])
|
||||
expect(confined.enforcement).toBe('full')
|
||||
expect(confined.enforcement).toBe('partial')
|
||||
expect(confined.denialSignatures).toEqual(['access is denied', 'access to the path', 'permission denied'])
|
||||
expect(confined.runnerFailureRules).toEqual([{ allowedExitCodes: [127], fatalSignatures: ['windows-acl-run: '] }])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user