{"record":{"id":"f4e7acc12c1bf195","repo":"vercel/ai","slug":"harnessagent-sandboxconfig-workdir-must-stay-i","errorCode":null,"errorMessage":"'HarnessAgent: `sandboxConfig.workDir` must stay inside the sandbox default working directory.'","messagePattern":"'HarnessAgent: `sandboxConfig\\.workDir` must stay inside the sandbox default working directory\\.'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/harness/src/agent/internal/sandbox-bootstrap.ts","lineNumber":61,"sourceCode":"      'HarnessAgent: `sandboxConfig.workDir` must not contain NUL.',\n    );\n  }\n  if (workDir.includes('\\\\')) {\n    throw new Error(\n      'HarnessAgent: `sandboxConfig.workDir` must use POSIX path separators.',\n    );\n  }\n  if (posix.isAbsolute(workDir)) {\n    throw new Error('HarnessAgent: `sandboxConfig.workDir` must be relative.');\n  }\n\n  const normalized = posix.normalize(workDir);\n  if (\n    normalized === '.' ||\n    normalized === '..' ||\n    normalized.startsWith('../')\n  ) {\n    throw new Error(\n      'HarnessAgent: `sandboxConfig.workDir` must stay inside the sandbox default working directory.',\n    );\n  }\n  return normalized;\n}\n\nexport function resolveSessionWorkDir({\n  defaultWorkingDirectory,\n  harnessId,\n  sessionId,\n  workDir,\n}: {\n  readonly defaultWorkingDirectory: string;\n  readonly harnessId: string;\n  readonly sessionId: string;\n  readonly workDir?: string;\n}): string {\n  return joinSandboxPath({","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness/src/agent/internal/sandbox-bootstrap.ts#L43-L79","documentation":"After posix.normalize, the workDir must not be '.', '..' or start with '../' — i.e. it must stay inside the sandbox's default working directory. Escaping via '..' would let the sandbox touch files outside its designated root, so it is rejected as a safety invariant.","triggerScenarios":"Passing sandboxConfig.workDir values like '..', './..', 'a/../../etc', or any path that normalizes to a parent of the default working directory.","commonSituations":"Trying to share a directory from outside the work root via traversal; computing a relative path from the wrong base so the result escapes upward.","solutions":["Change workDir to a path that stays under the default working directory, e.g. 'runs/latest' instead of '../shared'.","Verify with path.posix.normalize(workDir) in your own code that the result is not '.' / '..' / '../...'.","If you need data from outside, mount/copy it into the sandbox rather than traversing upward."],"exampleFix":"// before\nconst workDir = '../shared-output';\n// after\nconst workDir = 'shared-output'; // stays inside the sandbox default work dir","handlingStrategy":"validation","validationCode":"const n = path.posix.normalize(workDir); if (n === '.' || n === '..' || n.startsWith('../')) throw new Error('workDir escapes sandbox root');","typeGuard":"function isContainedPath(v: string): boolean { const n = path.posix.normalize(v); return n !== '.' && n !== '..' && !n.startsWith('../'); }","tryCatchPattern":"try { await prepareSandboxForHarness({ sandboxConfig: { workDir }, harnesses }); } catch (e) { if (e.message.includes('stay inside')) { /* reject config or clamp path */ } else throw e; }","preventionTips":["Normalize and check every user-supplied path before passing it in","Never construct workDir by joining with '..'","Treat path containment as a security invariant in code review"],"tags":["path","security","validation","sandbox"],"backgroundTag":"path-traversal","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}