{"record":{"id":"57b0cedea6563aa1","repo":"mastra-ai/mastra","slug":"experiment-worker-artifacts-cannot-contain-escapin","errorCode":null,"errorMessage":"Experiment worker artifacts cannot contain escaping symlinks: ${artifactPath}","messagePattern":"Experiment worker artifacts cannot contain escaping symlinks: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/commands/experiment/ExperimentBundler.ts","lineNumber":277,"sourceCode":"      const stats = await lstat(fullPath);\n      if (stats.isDirectory()) {\n        await visit(fullPath);\n      } else if (stats.isFile()) {\n        files.push({\n          path: artifactPath,\n          sha256: createHash('sha256')\n            .update(await readFile(fullPath))\n            .digest('hex'),\n        });\n      } else if (stats.isSymbolicLink()) {\n        const target = await readlink(fullPath);\n        if (isAbsolute(target)) {\n          throw new Error(`Experiment worker artifacts cannot contain absolute symlinks: ${artifactPath}`);\n        }\n        const resolvedTarget = resolve(dirname(fullPath), target);\n        const artifactTarget = relative(root, resolvedTarget);\n        if (artifactTarget === '..' || artifactTarget.startsWith(`..${sep}`) || isAbsolute(artifactTarget)) {\n          throw new Error(`Experiment worker artifacts cannot contain escaping symlinks: ${artifactPath}`);\n        }\n        files.push({\n          path: artifactPath,\n          type: 'symlink',\n          target,\n          sha256: createHash('sha256').update(target).digest('hex'),\n        });\n      } else {\n        throw new Error(`Unsupported artifact file type: ${artifactPath}`);\n      }\n    }\n  };\n  await visit(root);\n  return files;\n}\n","sourceCodeStart":259,"sourceCodeEnd":293,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/cli/src/commands/experiment/ExperimentBundler.ts#L259-L293","documentation":"During artifact digesting, a symlink whose relative target resolves outside the artifact root (relative(root, resolved) escapes with '..' or lands on an absolute path) is rejected. Such links would make the unpacked artifact read or point to files outside its own tree — a security and reproducibility hazard — so the build aborts naming the offending link.","triggerScenarios":"collectFileDigests finds a symlink in the output directory whose target, resolved against the link's directory, escapes the artifact root, e.g. `../../../etc/something` or `../../shared-lib`.","commonSituations":"Monorepo setups where packages symlink to sibling workspace packages outside the output; vendored code linking to a shared assets dir; pnpm-style virtual-store links escaping the bundle; copying source trees that contain parent-relative links.","solutions":["Locate the listed symlink and retarget it to a path inside the artifact (or replace it with a real copy).","Bundle the linked dependency's contents instead of linking (e.g. rely on the bundler's dynamicPackages/externals handling rather than filesystem links).","If it comes from a dependency's install script, patch or vendor the dependency.","Clean the output directory and rebuild to remove stale links."],"exampleFix":"// before\nln -s ../../shared/transforms ./transforms\n// after\ncp -r ../../shared/transforms ./transforms   # real copy inside the artifact","handlingStrategy":"validation","validationCode":"import { lstat, readlink, readdir } from 'node:fs/promises';\nimport { resolve, dirname, relative, isAbsolute, sep, join } from 'node:path';\nasync function assertNoEscapingSymlinks(root: string, dir = root) {\n  for (const e of await readdir(dir, { withFileTypes: true })) {\n    const p = join(dir, e.name);\n    if (e.isDirectory()) await assertNoEscapingSymlinks(root, p);\n    else if (e.isSymbolicLink()) {\n      const t = relative(root, resolve(dirname(p), await readlink(p)));\n      if (t === '..' || t.startsWith(`..${sep}`) || isAbsolute(t)) throw new Error(`escaping symlink: ${p}`);\n    }\n  }\n}","typeGuard":"null","tryCatchPattern":"try {\n  await buildExperimentWorker({ outputDir });\n} catch (e) {\n  if (String(e?.message).includes('escaping symlinks')) {\n    console.error('Retarget the listed symlink inside the artifact or copy the files in');\n  } else throw e;\n}","preventionTips":["Never symlink from the output/source tree to paths outside the project","Copy shared assets into the tree instead of linking to siblings","Check monorepo workspace links that resolve outside the package","Audit node_modules for virtual-store symlinks before packaging"],"tags":["build","symlink","security"],"backgroundTag":"escaping-symlink","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}