{"record":{"id":"e5e02e8a6c92c85b","repo":"Hmbown/CodeWhale","slug":"the-installation-destination-must-not-be-a-symlink","errorCode":null,"errorMessage":"The installation destination must not be a symlink.","messagePattern":"The installation destination must not be a symlink\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/app/install-macos.mjs","lineNumber":11,"sourceCode":"import fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { spawnSync } from \"node:child_process\";\n\n/** Publish a verified bundle in one rename, retaining the previous install. */\nexport function replaceMacBundle(source, destination, { prepare = () => {}, verify = verifySignature } = {}) {\n  const parent = path.dirname(destination);\n  fs.mkdirSync(parent, { recursive: true });\n  if (fs.existsSync(destination) && !fs.existsSync(path.join(destination, \"Contents\", \"Resources\", \"plugin\", \"app\", \"daemon.mjs\"))) throw new Error(\"The installation destination contains a different application.\");\n  if (fs.existsSync(destination) && fs.lstatSync(destination).isSymbolicLink()) throw new Error(\"The installation destination must not be a symlink.\");\n  const staging = fs.mkdtempSync(path.join(parent, \".codewhale-cu-update-\"));\n  const next = path.join(staging, path.basename(destination));\n  let backup = null;\n  try {\n    fs.cpSync(source, next, { recursive: true });\n    prepare(next);\n    verify(next);\n    if (fs.existsSync(destination)) {\n      const backups = path.join(parent, \".codewhale-cu-backups\");\n      fs.mkdirSync(backups, { recursive: true, mode: 0o700 });\n      backup = path.join(backups, `${Date.now()}-${crypto.randomUUID()}.app`);\n      fs.renameSync(destination, backup);\n    }\n    try { fs.renameSync(next, destination); }\n    catch (error) { if (backup) fs.renameSync(backup, destination); throw error; }\n    return { backup };\n  } finally { fs.rmSync(staging, { recursive: true, force: true }); }\n}","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/app/install-macos.mjs#L1-L29","documentation":"replaceMacBundle rejects a destination that is a symbolic link. Atomic publication relies on rename replacing a real directory; renaming over a symlink would silently write through to whatever the link targets, so a defensive lstat check throws before any staging work.","triggerScenarios":"Calling replaceMacBundle(source, destination) where the existing destination path is a symlink (fs.lstatSync(destination).isSymbolicLink() is true). Note the symlink check runs only after the different-application check, so a symlink pointing at a foreign app reports error 91 instead.","commonSituations":"User or an earlier tool replaced the .app with a symlink (e.g. linking to a versioned directory); automounter or cloud-sync directories exposed as symlinks; malicious symlink planted to redirect an install.","solutions":["Delete the symlink at destination and install into a real directory path","Point destination at the real target directory instead of the link","Investigate what created the symlink (version manager, sync tool) and reconfigure it before reinstalling"],"exampleFix":"// before\ncode.replaceMacBundle(src, \"/Applications/ComputerUse.app\"); // path is a symlink\n// after\ncode.fs.unlinkSync(\"/Applications/ComputerUse.app\"); // remove symlink first\ncode.replaceMacBundle(src, \"/Applications/ComputerUse.app\");","handlingStrategy":"validation","validationCode":"if (fs.existsSync(destination) && fs.lstatSync(destination).isSymbolicLink()) {\n  throw new Error(\"destination is a symlink\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  replaceMacBundle(src, dest);\n} catch (e) {\n  if (e.message.includes(\"must not be a symlink\")) {\n    fs.unlinkSync(dest); // or resolve and install at the real target\n    replaceMacBundle(src, dest);\n  } else throw e;\n}","preventionTips":["Never symlink the .app path; keep it a real directory","Audit install scripts and version managers for symlinked app paths","Exclude the install directory from tools that create links (cloud sync, automount)","Resolve destination with fs.realpath before installing if links are expected upstream"],"tags":["install","macos","symlink","safety-guard"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}