{"record":{"id":"b7bd28e972ec3dc7","repo":"paperclipai/paperclip","slug":"sandbox-path-alias-aliaspath-must-target-the","errorCode":null,"errorMessage":"Sandbox path alias \"${aliasPath}\" must target the synchronized workspace \"${workspaceDir}\".","messagePattern":"Sandbox path alias \"(.+?)\" must target the synchronized workspace \"(.+?)\"\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/local-process-sandbox.ts","lineNumber":418,"sourceCode":"      addParentDirectories(args, created, normalized);\n      args.push(access === \"rw\" ? \"--bind\" : \"--ro-bind\", normalized, normalized);\n      mounted.add(normalized);\n      created.add(normalized);\n    };\n    for (const systemPath of SYSTEM_READ_PATHS) await mount(systemPath, \"ro\");\n    for (const executablePath of await executableReadPaths(input.executable)) await mount(executablePath, \"ro\");\n    if (networkScope === \"allowlist\") {\n      for (const nodePath of await executableReadPaths(process.execPath)) await mount(nodePath, \"ro\");\n    }\n    for (const managedPath of input.options.managedPaths ?? []) await mount(managedPath.path, managedPath.access);\n    for (const extraPath of input.options.extraPaths ?? []) await mount(extraPath.path, extraPath.access);\n    await mount(workspaceDir, \"rw\");\n    for (const [index, alias] of (input.options.pathAliases ?? []).entries()) {\n      const aliasPath = normalizeAbsolutePath(alias.path, `Sandbox pathAliases[${index}].path`);\n      const aliasTarget = normalizeAbsolutePath(alias.target, `Sandbox pathAliases[${index}].target`);\n      const relativeTarget = path.relative(workspaceDir, aliasTarget);\n      if (relativeTarget.startsWith(\"..\") || path.isAbsolute(relativeTarget)) {\n        throw new Error(\n          `Sandbox path alias \"${aliasPath}\" must target the synchronized workspace \"${workspaceDir}\".`,\n        );\n      }\n      if (!(await pathExists(aliasTarget))) {\n        throw new Error(`Sandbox path alias target \"${aliasTarget}\" does not exist.`);\n      }\n      addParentDirectories(args, created, aliasPath);\n      args.push(\"--bind\", aliasTarget, aliasPath);\n      created.add(aliasPath);\n    }\n\n    if (networkScope === \"allowlist\") {\n      const tempDir = await createNetworkProxyTempDir();\n      const socketPath = path.join(tempDir, \"proxy.sock\");\n      const bridgePath = path.join(tempDir, \"bridge.cjs\");\n      await fs.writeFile(bridgePath, await createNetworkProxyBridge(), { mode: 0o500 });\n      const proxy = await startNetworkAllowlistProxy(\n        input.options.networkAllowlist ?? [],","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/local-process-sandbox.ts#L400-L436","documentation":"Thrown in the pathAliases loop of buildLocalProcessSandboxSpawnTarget when path.relative(workspaceDir, alias.target) escapes the workspace. Path aliases bind a host path (alias.target) to an in-sandbox path (alias.path) via bwrap --bind; only targets inside the synchronized workspace are allowed, because anything else would either duplicate content the sandbox cannot see or diverge from the synced tree.","triggerScenarios":"pathAliases: [{ path: \"/work/node_modules\", target: \"/host/home/user/node_modules\" }] with workspaceDir: \"/srv/work\". The check at local-process-sandbox.ts:417-421 fires for each alias whose target is outside the workspace (starts with \"..\" or is absolute-relative).","commonSituations":"Adapters that try to alias host home-directory dotfiles, system-wide caches, or symlinked dependencies that live outside the project. Also seen when workspaceDir is set to a subdirectory but aliases target files in the project root one level up.","solutions":["Move the alias target inside workspaceDir, or expand workspaceDir to cover the target's parent.","If the target must live outside the workspace and is read-only, expose it via extraPaths with access \"ro\" instead of pathAliases (extraPaths is not bound to the workspace-only-target rule).","Resolve symlinks on alias.target before sandbox spawn — an in-workspace symlink that resolves outside is the typical surprise.","Re-check each alias.target against path.relative(workspaceDir, aliasTarget) in your config validator so the failure surfaces at load time."],"exampleFix":"// before\npathAliases: [{ path: \"/work/.env\", target: \"/home/user/.env\" }]\n// workspaceDir = \"/srv/work\"\n\n// after: copy .env into the workspace before sandboxing\nawait fs.copyFile(\"/home/user/.env\", \"/srv/work/.env\");\npathAliases: [{ path: \"/work/.env\", target: \"/srv/work/.env\" }]","handlingStrategy":"validation","validationCode":"function assertPathAliasesInWorkspace(workspaceDir: string, aliases: { path: string; target: string }[]): void {\n  for (const alias of aliases) {\n    const rel = path.relative(workspaceDir, alias.target);\n    if (rel.startsWith(\"..\") || path.isAbsolute(rel)) {\n      throw new Error(`Alias ${alias.path} target ${alias.target} outside workspace ${workspaceDir}`);\n    }\n  }\n}\n\nconst realWorkspace = await fs.realpath(workspaceDir);\nconst realAliases = await Promise.all(aliases.map(async (a) => ({ ...a, target: await fs.realpath(a.target) })));\nassertPathAliasesInWorkspace(realWorkspace, realAliases);","typeGuard":null,"tryCatchPattern":"try {\n  return await buildLocalProcessSandboxSpawnTarget(input);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"must target the synchronized workspace\")) {\n    throw new ConfigError(`Path alias target outside workspace. Move target inside ${workspaceDir} or use extraPaths with access ro.`, { cause: error });\n  }\n  throw error;\n}","preventionTips":["Resolve alias.target symlinks before sandbox spawn.","Prefer extraPaths with access \"ro\" for read-only out-of-workspace paths.","Validate every alias target's relative path against the workspace at config load."],"tags":["filesystem","sandbox","config-validation","paths"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}