{"record":{"id":"2be01e11a377fcec","repo":"paperclipai/paperclip","slug":"local-process-sandbox-requires-a-filesystem-or-net","errorCode":null,"errorMessage":"Local process sandbox requires a filesystem or network scope.","messagePattern":"Local process sandbox requires a filesystem or network scope\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/adapter-utils/src/local-process-sandbox.ts","lineNumber":352,"sourceCode":"    else process.exit(code == null ? 1 : code);\n  }));\n});\n`;\n  return source.trimStart();\n}\n\nexport async function buildLocalProcessSandboxSpawnTarget(input: {\n  executable: string;\n  args: string[];\n  cwd: string;\n  options: LocalProcessSandboxOptions;\n}): Promise<LocalProcessSandboxSpawnTarget> {\n  if (process.platform !== \"linux\") {\n    throw new Error(\"Local process filesystem and network scopes are currently supported only on Linux.\");\n  }\n  const filesystemScope = input.options.filesystemScope ?? null;\n  const networkScope = input.options.networkScope ?? null;\n  if (!filesystemScope && !networkScope) throw new Error(\"Local process sandbox requires a filesystem or network scope.\");\n\n  const workspaceDir = normalizeAbsolutePath(input.options.workspaceDir, \"Sandbox workspaceDir\");\n  const cwd = normalizeAbsolutePath(input.cwd, \"Sandbox cwd\");\n  if (filesystemScope === \"workspace\") {\n    const relativeCwd = path.relative(workspaceDir, cwd);\n    if (relativeCwd.startsWith(\"..\") || path.isAbsolute(relativeCwd)) {\n      throw new Error(`Sandbox cwd \"${cwd}\" must be inside workspaceDir \"${workspaceDir}\".`);\n    }\n    const outboundRestorePaths = (input.options.outboundRestorePaths ?? []).map((candidate, index) =>\n      normalizeAbsolutePath(candidate, `Sandbox outboundRestorePaths[${index}]`));\n    for (const [index, extraPath] of (input.options.extraPaths ?? []).entries()) {\n      if (extraPath.access !== \"rw\") continue;\n      const normalizedExtraPath = normalizeAbsolutePath(extraPath.path, `Sandbox extraPaths[${index}].path`);\n      const relativeToWorkspace = path.relative(workspaceDir, normalizedExtraPath);\n      const synchronized = !relativeToWorkspace.startsWith(\"..\") && !path.isAbsolute(relativeToWorkspace);\n      const restored = outboundRestorePaths.some((restorePath) => {\n        const relative = path.relative(restorePath, normalizedExtraPath);\n        return !relative.startsWith(\"..\") && !path.isAbsolute(relative);","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/adapter-utils/src/local-process-sandbox.ts#L334-L370","documentation":"Thrown by buildLocalProcessSandboxSpawnTarget when both filesystemScope and networkScope resolve to null. The sandbox exists to restrict at least one axis (filesystem or network); if neither is configured there is nothing for bubblewrap to do, so the function refuses to construct an empty sandbox that would give a false sense of isolation.","triggerScenarios":"Calling buildLocalProcessSandboxSpawnTarget with options where both filesystemScope and networkScope are null, undefined, or empty string. The check at local-process-sandbox.ts:352 runs after the platform guard, so this is the second precondition before any path validation.","commonSituations":"Config layer defaults both options to null but still routes through buildLocalProcessSandboxSpawnTarget instead of the unsandboxed spawn path; a UI that lets the user clear both scope selectors but still calls the sandbox builder; or a feature flag that was supposed to enable one scope but is disabled, leaving both empty.","solutions":["Set at least one scope: filesystemScope: \"workspace\" and/or networkScope: \"deny\" or \"allowlist\".","If you genuinely want no sandboxing, skip buildLocalProcessSandboxSpawnTarget entirely and spawn the executable directly — do not call it with both scopes unset.","Add a config assertion upstream: if (filesystemScope == null && networkScope == null) throw new Error(\"configure at least one sandbox scope\"); to surface the issue earlier with your own message.","Audit the default option object in your adapter to ensure it always sets one scope when sandboxing is enabled."],"exampleFix":"// before\nbuildLocalProcessSandboxSpawnTarget({\n  ...input,\n  options: { ...input.options, filesystemScope: null, networkScope: null },\n});\n\n// after\nbuildLocalProcessSandboxSpawnTarget({\n  ...input,\n  options: { ...input.options, filesystemScope: \"workspace\", networkScope: \"deny\" },\n});","handlingStrategy":"validation","validationCode":"function assertSandboxScope(opts: { filesystemScope: string | null; networkScope: string | null }): void {\n  if (!opts.filesystemScope && !opts.networkScope) {\n    throw new Error(\"At least one sandbox scope (filesystemScope or networkScope) must be set; otherwise skip the sandbox builder.\");\n  }\n}\n\nassertSandboxScope({ filesystemScope, networkScope });\nconst target = await buildLocalProcessSandboxSpawnTarget(input);","typeGuard":"function hasAnySandboxScope(opts: { filesystemScope: string | null; networkScope: string | null }): boolean {\n  return Boolean(opts.filesystemScope) || Boolean(opts.networkScope);\n}","tryCatchPattern":"try {\n  return await buildLocalProcessSandboxSpawnTarget(input);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"requires a filesystem or network scope\")) {\n    return { command: input.executable, args: input.args, cwd: input.cwd, env: process.env, cleanup: undefined };\n  }\n  throw error;\n}","preventionTips":["Decide upstream whether sandboxing is on or off; do not route through the sandbox builder with both scopes empty.","Add a config assertion that rejects both-scopes-null when sandboxing is enabled.","Make your adapter's default option object always set one scope in sandbox mode."],"tags":["sandbox","config-validation"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}