{"record":{"id":"071193e4c290b2c9","repo":"mastra-ai/mastra","slug":"eacces-071193","errorCode":"EACCES","errorMessage":"Permission denied: ${operation} on ${path}","messagePattern":"Permission denied: (.+?) on (.+?)","errorType":"exception","errorClass":"PermissionError","httpStatus":null,"severity":"error","filePath":"packages/core/src/workspace/filesystem/local-filesystem.ts","lineNumber":278,"sourceCode":"\n    return roots.some(root => {\n      const resolvedRoot = this._resolvePathForContainment(root);\n      return resolvedRoot ? this._isWithinRoot(resolvedPath, resolvedRoot) : false;\n    });\n  }\n\n  private toBuffer(content: FileContent): Buffer {\n    if (Buffer.isBuffer(content)) return content;\n    if (content instanceof Uint8Array) return Buffer.from(content);\n    return Buffer.from(content, 'utf-8');\n  }\n\n  private resolvePath(inputPath: string): string {\n    const absolutePath = resolveToBasePath(this._basePath, inputPath);\n\n    if (this._contained) {\n      if (!this._isWithinAnyRoot(absolutePath)) {\n        throw new PermissionError(inputPath, this._accessOperationHint(inputPath));\n      }\n    }\n\n    return absolutePath;\n  }\n\n  /**\n   * Build the operation string for a containment-violation `PermissionError`.\n   *\n   * When the caller passed an absolute path, suggest a concrete relative form\n   * only when that suffix names an existing entry under the workspace (e.g.\n   * `/src/app.ts` → `src/app.ts` if `<basePath>/src` exists). Otherwise emit a\n   * soft hint that doesn't lie about specific paths — agents that mistake `/`\n   * for the workspace root learn the workspace is sandboxed without us\n   * inventing a fictitious in-workspace location for `/etc/passwd`.\n   */\n  private _accessOperationHint(inputPath: string): string {\n    if (!nodePath.isAbsolute(inputPath)) return 'access';","sourceCodeStart":260,"sourceCodeEnd":296,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workspace/filesystem/local-filesystem.ts#L260-L296","documentation":"LocalFilesystem.resolvePath resolves an input path against the configured basePath and, when the filesystem is created with contained mode, verifies the result stays within the allowed access roots. If the resolved absolute path escapes every permitted root, it throws PermissionError with \"Permission denied: <operation> on <path>\" (EACCES semantics) to enforce path containment.","triggerScenarios":"Any operation (read/write/stat/...) on a contained LocalFilesystem whose resolved path escapes the base/allowed roots: '../' traversal, absolute paths outside the base, symlinks resolving outside, or passing an already-absolute disk path where a base-relative path is expected.","commonSituations":"Path traversal from user- or LLM-supplied filenames ('../../etc/passwd'); switching from uncontained to contained mode in a refactor; misconfigured basePath so legitimate files resolve outside roots; symlinks in the workspace pointing to external locations.","solutions":["Sanitize/normalize the input path and remove '..' segments so it stays under the configured base path.","If broader access is intentional, adjust the LocalFilesystem basePath or containment roots configuration.","Resolve symlinks or pass real paths if a symlink causes the escape; log the resolved absolute path to debug which root check failed."],"exampleFix":"// before\nawait fs.writeFile(userInput, data); // userInput = '../../outside.txt'\n// after\nconst safe = posix.join('.', posix.normalize(userInput.replace(/^(\\.\\.\\/)+/, '')));\nawait fs.writeFile(safe, data); // stays inside the contained base","handlingStrategy":"try-catch","validationCode":"const resolved = posix.resolve(basePath, inputPath);\nif (!resolved.startsWith(basePath + '/') && resolved !== basePath) throw new Error(`Path escapes workspace: ${inputPath}`);","typeGuard":null,"tryCatchPattern":"try {\n  await fs.writeFile(path, data);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Permission denied:')) {\n    throw new Error(`Refusing to access '${path}': outside the workspace sandbox`);\n  }\n  throw e;\n}","preventionTips":["Normalize and strip '..' segments from any user- or LLM-supplied path before passing it in.","Never pass absolute disk paths to a contained LocalFilesystem; pass base-relative paths.","Keep containment enabled and add tests asserting traversal attempts are rejected.","Resolve symlinks in workspaces that may point outside the allowed roots."],"tags":["filesystem","permissions","path-traversal","containment"],"backgroundTag":"permission-denied-path-escape","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}