mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 16:27:18 +02:00
Preserve sandbox containment without requiring Git for tasks whose project has no configured workspace. Keep explicit workspace strategies and reusable task ownership checks intact. Add the failing startup regression and directory isolation tests. Co-Authored-By: Paperclip <noreply@paperclip.ing>
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
import { lstat, mkdir, realpath } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { parseObject } from "../adapters/utils.js";
|
|
import { resolvePaperclipInstanceRoot } from "../home-paths.js";
|
|
|
|
/** A repository-free sandbox task needs isolation, but has no Git base. */
|
|
export function shouldUseIsolatedTaskDirectory(input: {
|
|
trustPreset: string;
|
|
environmentDriver: string | null;
|
|
mode: string;
|
|
hasProjectWorkspace: boolean;
|
|
projectWorkspaceId: string | null;
|
|
workspaceStrategies: unknown[];
|
|
}): boolean {
|
|
return input.trustPreset === "low_trust_review"
|
|
&& input.environmentDriver === "sandbox"
|
|
&& input.mode === "isolated_workspace"
|
|
&& !input.hasProjectWorkspace
|
|
&& !input.projectWorkspaceId
|
|
// Never discard an explicit repository/branch requirement or setup hook.
|
|
&& input.workspaceStrategies.every((value) => Object.keys(parseObject(value)).length === 0);
|
|
}
|
|
|
|
/** Stable across turns, separate from project roots and shared agent homes. */
|
|
export async function materializeIsolatedTaskDirectory(input: {
|
|
companyId: string;
|
|
agentId: string;
|
|
issueId: string;
|
|
}): Promise<string> {
|
|
const identities = [input.companyId, input.agentId, input.issueId];
|
|
if (identities.some((value) => !/^[a-zA-Z0-9_-]+$/.test(value))) {
|
|
throw new Error("Invalid isolated task workspace identity");
|
|
}
|
|
const root = resolvePaperclipInstanceRoot();
|
|
await mkdir(root, { recursive: true });
|
|
let cwd = await realpath(root);
|
|
for (const segment of ["isolated-workspaces", ...identities]) {
|
|
cwd = path.join(cwd, segment);
|
|
await mkdir(cwd, { mode: 0o700 }).catch((error: NodeJS.ErrnoException) => {
|
|
if (error.code !== "EEXIST") throw error;
|
|
});
|
|
const stat = await lstat(cwd);
|
|
if (!stat.isDirectory() || stat.isSymbolicLink() || await realpath(cwd) !== cwd) {
|
|
throw new Error("Isolated task workspace path is not a private directory");
|
|
}
|
|
}
|
|
return cwd;
|
|
}
|