












@@ -97,7 +97,7 @@ class RemoteShellSandboxFsBridge implements SandboxFsBridge {
9797signal?: AbortSignal;
9898}): Promise<void> {
9999const target = this.resolveTarget(params);
100-this.ensureWritable(target, "write files");
100+await this.ensureRemoteWritable(target, "write files", params.signal);
101101const pinned = await this.resolvePinnedParent({
102102containerPath: target.containerPath,
103103action: "write files",
@@ -126,7 +126,7 @@ class RemoteShellSandboxFsBridge implements SandboxFsBridge {
126126127127async mkdirp(params: { filePath: string; cwd?: string; signal?: AbortSignal }): Promise<void> {
128128const target = this.resolveTarget(params);
129-this.ensureWritable(target, "create directories");
129+await this.ensureRemoteWritable(target, "create directories", params.signal);
130130const relativePath = path.posix.relative(target.mountRootPath, target.containerPath);
131131if (relativePathEscapesContainerRoot(relativePath)) {
132132throw new Error(
@@ -147,7 +147,7 @@ class RemoteShellSandboxFsBridge implements SandboxFsBridge {
147147signal?: AbortSignal;
148148}): Promise<void> {
149149const target = this.resolveTarget(params);
150-this.ensureWritable(target, "remove files");
150+await this.ensureRemoteWritable(target, "remove files", params.signal);
151151const exists = await this.remotePathExists(target.containerPath, params.signal);
152152if (!exists) {
153153if (params.force === false) {
@@ -182,6 +182,8 @@ class RemoteShellSandboxFsBridge implements SandboxFsBridge {
182182signal?: AbortSignal;
183183}): Promise<void> {
184184const { from, to } = this.resolveRenameTargets(params);
185+await this.ensureRemoteWritable(from, "rename files", params.signal);
186+await this.ensureRemoteWritable(to, "rename files", params.signal);
185187const fromPinned = await this.resolvePinnedParent({
186188containerPath: from.containerPath,
187189action: "rename files",
@@ -384,6 +386,44 @@ class RemoteShellSandboxFsBridge implements SandboxFsBridge {
384386}
385387}
386388389+private async ensureRemoteWritable(
390+target: ResolvedRemotePath,
391+action: string,
392+signal?: AbortSignal,
393+): Promise<void> {
394+this.ensureWritable(target, action);
395+const protectedRoot = this.findRemoteProtectedSkillRoot(target.containerPath);
396+if (protectedRoot && (await this.remotePathExists(protectedRoot, signal))) {
397+throw new Error(`Sandbox path is read-only; cannot ${action}: ${target.containerPath}`);
398+}
399+}
400+401+private findRemoteProtectedSkillRoot(containerPath: string): string | null {
402+const roots = this.getRemoteProtectedSkillRoots().toSorted((a, b) => b.length - a.length);
403+for (const root of roots) {
404+if (isPathInsideContainerRoot(root, containerPath)) {
405+return root;
406+}
407+}
408+return null;
409+}
410+411+private getRemoteProtectedSkillRoots(): string[] {
412+const workspaceContainerRoot = normalizeContainerPath(this.runtime.remoteWorkspaceDir);
413+const agentContainerRoot = normalizeContainerPath(this.runtime.remoteAgentWorkspaceDir);
414+const roots = [
415+path.posix.join(workspaceContainerRoot, "skills"),
416+path.posix.join(workspaceContainerRoot, ".agents", "skills"),
417+];
418+if (path.resolve(this.sandbox.agentWorkspaceDir) !== path.resolve(this.sandbox.workspaceDir)) {
419+roots.push(
420+path.posix.join(agentContainerRoot, "skills"),
421+path.posix.join(agentContainerRoot, ".agents", "skills"),
422+);
423+}
424+return roots;
425+}
426+387427private async remotePathExists(containerPath: string, signal?: AbortSignal): Promise<boolean> {
388428const result = await this.runRemoteScript({
389429script: 'if [ -e "$1" ] || [ -L "$1" ]; then printf "1\\n"; else printf "0\\n"; fi',
此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。