1
0
Эх сурвалжийг харах

fix(sandbox): resolve source preload independently of command cwd

Tianyi Cui 1 сар өмнө
parent
commit
5bebc7c504

+ 2 - 2
packages/sandbox/sandbox-local/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # 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:
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/sandbox/sandbox-local/README.md
 #   pnpm run verify-translation-pairing --write packages/sandbox/sandbox-local/README.md
-README.md: 33853b68407c2d3294ad1323e47b5473419b0e12
-README.zh.md: 6abef684c38d3c60caa59d06eb3ac721a2e531fe
+README.md: ab6182ffe86b658c59eebcffbc479097ed1cb25b
+README.zh.md: ca1e4142f10bbdc44ccfc8f143617a5d9b58fcd3

+ 2 - 0
packages/sandbox/sandbox-local/README.md

@@ -80,6 +80,8 @@ The Seatbelt profile is allow-default with `(deny file-write*)` plus write allow
 
 
 The Windows rung keeps one deterministic write SID and standing ACE per workspace, while every live session/workspace pair gets a random private temp directory with a distinct SID and revocable ACE — sessions sharing a workspace share its intended write authority without inheriting one another's temp authority. A fresh provider always chooses a new temp path and SID, so crash residue cannot block or authorize a resumed session. The rung reports `partial` enforcement because the restricted token must retain Everyone and NTFS hard links alias one file object across paths.
 The Windows rung keeps one deterministic write SID and standing ACE per workspace, while every live session/workspace pair gets a random private temp directory with a distinct SID and revocable ACE — sessions sharing a workspace share its intended write authority without inheriting one another's temp authority. A fresh provider always chooses a new temp path and SID, so crash residue cannot block or authorize a resumed session. The rung reports `partial` enforcement because the restricted token must retain Everyone and NTFS hard links alias one file object across paths.
 
 
+When the built ACL runner is absent, source launch uses the `tsx/esm` module URL resolved from this installation. The command's working directory does not need a Harness checkout or its own loader dependency.
+
 ### Denial and runner-failure dialects
 ### Denial and runner-failure dialects
 
 
 Each runner's kernel speaks its own denial dialect, carried on every wrap as `denialSignatures`, and `runnerFailureRules` give each runner's fatal signature, so consumers classify a runner refusal before checking denial signatures. The exact strings and exit codes live in [`src/index.ts`](src/index.ts).
 Each runner's kernel speaks its own denial dialect, carried on every wrap as `denialSignatures`, and `runnerFailureRules` give each runner's fatal signature, so consumers classify a runner refusal before checking denial signatures. The exact strings and exit codes live in [`src/index.ts`](src/index.ts).

+ 2 - 0
packages/sandbox/sandbox-local/README.zh.md

@@ -80,6 +80,8 @@ Seatbelt profile 默认允许,带 `(deny file-write*)` 与来自共享 `writab
 
 
 Windows 档为每个工作区保留一个确定性写入 SID 和常驻 ACE,同时为每个活跃的会话/工作区对分配一个随机私有临时目录,以及不同的 SID 和可撤销 ACE——共享工作区的会话共享其预期写权限,却不会继承彼此的临时目录权限。新的提供方总会选择新的临时路径和 SID,因此崩溃残留既无法阻止恢复的会话,也无法向其授权。该档报告 `partial` 强制执行,因为受限令牌必须保留 Everyone,且 NTFS 硬链接会把同一文件对象别名为多个路径。
 Windows 档为每个工作区保留一个确定性写入 SID 和常驻 ACE,同时为每个活跃的会话/工作区对分配一个随机私有临时目录,以及不同的 SID 和可撤销 ACE——共享工作区的会话共享其预期写权限,却不会继承彼此的临时目录权限。新的提供方总会选择新的临时路径和 SID,因此崩溃残留既无法阻止恢复的会话,也无法向其授权。该档报告 `partial` 强制执行,因为受限令牌必须保留 Everyone,且 NTFS 硬链接会把同一文件对象别名为多个路径。
 
 
+构建后的 ACL runner 缺失时,源码启动使用从本安装目录解析出的 `tsx/esm` 模块 URL。命令的工作目录不需要包含 Harness 源码检出或独立的加载器依赖。
+
 ### 拒绝与 runner 失败方言
 ### 拒绝与 runner 失败方言
 
 
 每个 runner 的内核都有自己的拒绝方言,随每次包装以 `denialSignatures` 携带,`runnerFailureRules` 则给出每个 runner 的致命签名,因此消费方先分类 runner 拒绝,再检查拒绝签名。精确的字符串与退出码位于 [`src/index.ts`](src/index.ts)。
 每个 runner 的内核都有自己的拒绝方言,随每次包装以 `denialSignatures` 携带,`runnerFailureRules` 则给出每个 runner 的致命签名,因此消费方先分类 runner 拒绝,再检查拒绝签名。精确的字符串与退出码位于 [`src/index.ts`](src/index.ts)。

+ 2 - 1
packages/sandbox/sandbox-local/src/index.ts

@@ -551,6 +551,7 @@ export class LocalSandboxProvider extends SandboxProvider {
   /**
   /**
    * The windows-acl runner argv prefix: the built lib/runner.js entry when
    * The windows-acl runner argv prefix: the built lib/runner.js entry when
    * present (production), else the package source through tsx (development).
    * present (production), else the package source through tsx (development).
+   * Resolve the source preload in this installation, independently of target cwd.
    * The prefix stays `[node, runner, ...]` — a future native-exe runner keeps
    * The prefix stays `[node, runner, ...]` — a future native-exe runner keeps
    * the same argv contract and only swaps these entries.
    * the same argv contract and only swaps these entries.
    */
    */
@@ -560,7 +561,7 @@ export class LocalSandboxProvider extends SandboxProvider {
     const builtEntry = this.internals.windowsAclRunnerEntry ?? fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-sandbox-windows-acl/runner'))
     const builtEntry = this.internals.windowsAclRunnerEntry ?? fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-sandbox-windows-acl/runner'))
     if (existsSync(builtEntry)) return [process.execPath, builtEntry]
     if (existsSync(builtEntry)) return [process.execPath, builtEntry]
     const sourceEntry = fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-sandbox-windows-acl/src/runner.ts'))
     const sourceEntry = fileURLToPath(import.meta.resolve('@deepseek-ai/dsh-sandbox-windows-acl/src/runner.ts'))
-    return [process.execPath, '--import', 'tsx/esm', sourceEntry]
+    return [process.execPath, '--import', import.meta.resolve('tsx/esm'), sourceEntry]
   }
   }
 }
 }
 
 

+ 22 - 1
packages/sandbox/sandbox-local/tests/local.spec.ts

@@ -7,6 +7,7 @@
  * are all exercised through the real `confine()` path.
  * are all exercised through the real `confine()` path.
  */
  */
 
 
+import { spawnSync } from 'node:child_process'
 import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
 import { mkdtempSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
 import { tmpdir } from 'node:os'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { join } from 'node:path'
@@ -438,10 +439,30 @@ describe('the windows-acl probe (runner invocation contract)', () => {
       windowsAclRunnerEntry: absentRunnerEntry(),
       windowsAclRunnerEntry: absentRunnerEntry(),
     })
     })
     const confined = sandbox.confine(['true'], RO)
     const confined = sandbox.confine(['true'], RO)
-    expect(confined.argv.slice(0, 3)).toEqual([process.execPath, '--import', 'tsx/esm'])
+    expect(confined.argv.slice(0, 3)).toEqual([process.execPath, '--import', import.meta.resolve('tsx/esm')])
     expect(confined.argv[3]).toMatch(/runner\.ts$/)
     expect(confined.argv[3]).toMatch(/runner\.ts$/)
   })
   })
 
 
+  it('loads the source preload from a cwd outside the checkout', async () => {
+    const cwd = mkdtempSync(join(tmpdir(), 'dsh-acl-source-cwd-'))
+    tempDirs.push(cwd)
+    const { sandbox } = await setup({}, {
+      chain: ['windows-acl', 'bwrap'],
+      probeWindowsAcl: () => true,
+      windowsAclRunnerEntry: absentRunnerEntry(),
+    })
+    const { argv } = sandbox.confine(['true'], RO)
+    const result = spawnSync(argv[0]!, [...argv.slice(1, 3), '-e', 'process.stdout.write("source-loader-ready")'], {
+      cwd,
+      encoding: 'utf8',
+      timeout: 5000,
+      env: { ...process.env, NODE_OPTIONS: undefined, TSX_TSCONFIG_PATH: undefined },
+    })
+    expect(result.error).toBeUndefined()
+    expect(result.status, result.stderr).toBe(0)
+    expect(result.stdout).toBe('source-loader-ready')
+  }, 10_000)
+
   it('reads an empty runner invocation as unusable (the probe\'s empty-argv guard)', async () => {
   it('reads an empty runner invocation as unusable (the probe\'s empty-argv guard)', async () => {
     // windowsAclRunnerInvocation always yields [node, ...] in product; an
     // windowsAclRunnerInvocation always yields [node, ...] in product; an
     // override returning [] exercises the default probe's empty-argv guard.
     // override returning [] exercises the default probe's empty-argv guard.