{"record":{"id":"69fd493e898b1bf4","repo":"jdx/mise","slug":"brew-cask-refusing-generic-artifact-copy-outside","errorCode":null,"errorMessage":"brew-cask: refusing generic artifact copy outside Homebrew prefix: {}","messagePattern":"brew-cask: refusing generic artifact copy outside Homebrew prefix: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":2189,"sourceCode":"    )?;\n    if bound.st_dev != linked.st_dev || bound.st_ino != linked.st_ino {\n        bail!(\"brew-cask: temporary artifact directory was replaced\");\n    }\n    nix::unistd::unlinkat(\n        &parent.fd,\n        staging_name,\n        nix::unistd::UnlinkatFlags::RemoveDir,\n    )?;\n    Ok(())\n}\n\nfn validate_generic_copy_target(target: &Path) -> Result<()> {\n    let prefix = prefix::prefix();\n    if !target.starts_with(&prefix)\n        || target.strip_prefix(&prefix)?.components().next().is_none()\n        || !path_starts_with_resolved_root(target, &prefix)\n    {\n        bail!(\n            \"brew-cask: refusing generic artifact copy outside Homebrew prefix: {}\",\n            target.display()\n        );\n    }\n    Ok(())\n}\n\n#[cfg(unix)]\nstruct TrustedOperationParent {\n    fd: std::os::fd::OwnedFd,\n}\n\n#[cfg(unix)]\nimpl TrustedOperationParent {\n    fn path(&self) -> Result<PathBuf> {\n        #[cfg(target_os = \"linux\")]\n        return Ok(\n            Path::new(\"/proc/self/fd\").join(std::os::fd::AsRawFd::as_raw_fd(&self.fd).to_string())","sourceCodeStart":2171,"sourceCodeEnd":2207,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L2171-L2207","documentation":"validate_generic_copy_target gates the destination of a cask generic artifact copy. The target must start with the Homebrew prefix path, have at least one component strictly below it (the prefix itself is not a valid destination), and pass path_starts_with_resolved_root, which resolves symlinks so a target that only appears to sit under the prefix via a symlink is rejected. Anything else aborts the copy before data moves.","triggerScenarios":"A cask artifact target such as /Library/LaunchDaemons/x.plist, /etc/man.conf, ~/Library/x, a target equal to the prefix itself, or a target under the prefix whose symlinked ancestors resolve outside the prefix.","commonSituations":"Casks written for classic Homebrew ruby behavior that install launchd plists or system configs to absolute paths; prefix mismatch after moving between /usr/local and /opt/homebrew; assuming arbitrary absolute targets are supported.","solutions":["Change the artifact target to a path strictly under the Homebrew prefix, e.g. $HOMEBREW_PREFIX/etc/foo.conf","For system locations (/Library, /etc), use the cask's preflight/postflight steps or a pkg artifact instead of a generic artifact","If the file must exist elsewhere, install under the prefix and symlink from a postflight step","Confirm the prefix mise uses (HOMEBREW_PREFIX / mise settings) matches what the cask assumed"],"exampleFix":"# before (cask stanza)\nartifact \"config.plist\", target: \"/Library/Preferences/com.example.plist\"\n\n# after\nartifact \"config.plist\", target: \"#{HOMEBREW_PREFIX}/etc/com.example.plist\"","handlingStrategy":"validation","validationCode":"// Check a generic artifact target before installing.\nfn target_under_prefix(target: &std::path::Path, prefix: &std::path::Path) -> bool {\n    target.is_absolute()\n        && target.starts_with(prefix)\n        && target.strip_prefix(prefix).map(|p| p.components().next().is_some()).unwrap_or(false)\n        && std::fs::canonicalize(target)\n            .or_else(|_| std::fs::canonicalize(target.parent().unwrap_or(prefix)))\n            .map(|r| r.starts_with(prefix))\n            .unwrap_or(false)\n}","typeGuard":"fn is_safe_generic_target(target: &str, prefix: &Path) -> bool {\n    let expanded = target.replace(\"$HOMEBREW_PREFIX\", &prefix.to_string_lossy());\n    target_under_prefix(Path::new(&expanded), prefix)\n}","tryCatchPattern":null,"preventionTips":["Always express artifact targets as $HOMEBREW_PREFIX-relative paths","Lint cask definitions for absolute targets outside the prefix before shipping them","Remember the prefix itself is not a valid target — at least one component must follow it"],"tags":["brew-cask","generic-artifact","path-validation","homebrew-prefix","security"],"backgroundTag":"path-outside-allowed-root","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}