























@@ -171,6 +171,81 @@ describe("workspace skills bridge mount policy", () => {
171171},
172172);
173173174+it.runIf(process.platform !== "win32")(
175+"rejects remote bridge writes through symlinks into skill roots",
176+async () => {
177+await withTempDir("openclaw-skills-remote-link-", async (stateDir) => {
178+const workspaceDir = path.join(stateDir, "workspace");
179+const remoteWorkspaceDir = path.join(stateDir, "remote-workspace");
180+await fs.mkdir(workspaceDir, { recursive: true });
181+await fs.mkdir(path.join(remoteWorkspaceDir, "skills", "demo"), { recursive: true });
182+await fs.symlink("skills", path.join(remoteWorkspaceDir, "link"), "dir");
183+const canonicalWorkspaceDir = await fs.realpath(workspaceDir);
184+const canonicalRemoteWorkspaceDir = await fs.realpath(remoteWorkspaceDir);
185+186+const bridge = createRemoteShellSandboxFsBridge({
187+sandbox: createSandbox({
188+workspaceDir: canonicalWorkspaceDir,
189+agentWorkspaceDir: canonicalWorkspaceDir,
190+}),
191+runtime: {
192+remoteWorkspaceDir: canonicalRemoteWorkspaceDir,
193+remoteAgentWorkspaceDir: canonicalRemoteWorkspaceDir,
194+runRemoteShellScript: async (command) => {
195+const result = command.script.includes('python3 /dev/fd/3 "$@" 3<<')
196+ ? spawnSync(
197+"python3",
198+["-c", SANDBOX_PINNED_MUTATION_PYTHON, ...(command.args ?? [])],
199+{
200+input: command.stdin,
201+encoding: "buffer",
202+stdio: ["pipe", "pipe", "pipe"],
203+},
204+)
205+ : spawnSync(
206+"sh",
207+["-c", command.script, "openclaw-test", ...(command.args ?? [])],
208+{
209+input: command.stdin,
210+encoding: "buffer",
211+stdio: ["pipe", "pipe", "pipe"],
212+},
213+);
214+const stdout = Buffer.isBuffer(result.stdout)
215+ ? result.stdout
216+ : Buffer.from(result.stdout ?? []);
217+const stderr = Buffer.isBuffer(result.stderr)
218+ ? result.stderr
219+ : Buffer.from(result.stderr ?? []);
220+const code = result.status ?? (result.signal ? 128 : 1);
221+if (result.error) {
222+throw result.error;
223+}
224+if (code !== 0 && !command.allowFailure) {
225+throw Object.assign(
226+new Error(stderr.toString("utf8").trim() || `shell exited with code ${code}`),
227+{ code, stdout, stderr },
228+);
229+}
230+return { stdout, stderr, code };
231+},
232+},
233+});
234+235+await expect(
236+bridge.writeFile({
237+filePath: "link/demo/SKILL.md",
238+cwd: canonicalRemoteWorkspaceDir,
239+data: "# Demo\n",
240+}),
241+).rejects.toThrow(/read-only/);
242+await expect(
243+fs.stat(path.join(canonicalRemoteWorkspaceDir, "skills", "demo", "SKILL.md")),
244+).rejects.toMatchObject({ code: "ENOENT" });
245+});
246+},
247+);
248+174249it.runIf(process.platform !== "win32")(
175250"rejects remote bridge mkdirp under skill roots from container cwd",
176251async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。