{"record":{"id":"fe0072ba0324e60e","repo":"jdx/mise","slug":"brew-cask-generic-artifact-target-must-stay-fe0072","errorCode":null,"errorMessage":"brew-cask: generic artifact target '{}' must stay below {}","messagePattern":"brew-cask: generic artifact target '(.+?)' must stay below (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":2440,"sourceCode":"        run_installer_artifact(stage, installer, targets.copied_files())?;\n        completed(index)?;\n    }\n    durabilize_staged_symlink_targets(stage, temporary_caskroom, targets)\n}\n\nfn generic_artifact_target_path(target: &str) -> Result<PathBuf> {\n    let prefix = prefix::prefix();\n    let expanded = target.replace(\"$HOMEBREW_PREFIX\", &prefix.to_string_lossy());\n    let target = PathBuf::from(expanded);\n    if !target.is_absolute()\n        || !target.starts_with(&prefix)\n        || target.strip_prefix(&prefix)?.components().next().is_none()\n        || target\n            .components()\n            .any(|component| matches!(component, Component::ParentDir))\n        || !path_starts_with_resolved_root(&target, &prefix)\n    {\n        bail!(\n            \"brew-cask: generic artifact target '{}' must stay below {}\",\n            target.display(),\n            prefix.display()\n        );\n    }\n    Ok(target)\n}\n\n/// The receipt of the currently installed version, if there is one.\nfn previous_receipt(cask: &Cask) -> Result<Option<CaskReceipt>> {\n    let Some(version) = installed_version(&cask.token) else {\n        return Ok(None);\n    };\n    read_receipt(&caskroom_version_dir(&cask.token, &version))\n}\n\nfn previous_generic_targets(cask: &Cask) -> Result<Vec<CaskTargetRecord>> {\n    let Some(receipt) = previous_receipt(cask)? else {","sourceCodeStart":2422,"sourceCodeEnd":2458,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/mod.rs#L2422-L2458","documentation":"mise's brew-cask backend validates that a generic (non-app, non-font) artifact's install target stays under the mise/brew prefix. The target must not traverse upward with '..' components, must have a component below the prefix, and must resolve inside the prefix root after path resolution. Otherwise installation is refused, preventing cask artifact declarations from writing files into arbitrary system locations.","triggerScenarios":"A cask declares a generic artifact whose target path contains '..' components; the target is an absolute path outside the prefix; path_starts_with_resolved_root fails because the target resolves (via symlinks) outside the prefix; a malformed artifact target with no components under the prefix.","commonSituations":"Hand-written or third-party casks with targets like '/Library/Fonts' or '~/bin' outside the managed prefix; prefix relocations (custom HOMEBREW_PREFIX) making previously valid relative targets resolve outside; users editing cask stanza targets to redirect artifacts.","solutions":["Change the artifact target to a path relative to and below the configured prefix (e.g. 'share/fonts/...' not '/Library/Fonts').","Remove any '..' components from the target path.","Verify your brew prefix configuration; if you use a custom prefix, ensure artifact targets are defined relative to it.","Use a dedicated cask or mise feature (e.g. font artifacts) for locations outside the prefix instead of generic artifacts.","Update the cask to a newer revision where the upstream fixed the target path."],"exampleFix":"// before\nartifact target: \"/Library/Scripts/mytool\"\n// after\nartifact target: \"share/scripts/mytool\"","handlingStrategy":"validation","validationCode":"let target = std::path::PathBuf::from(declared_target);\nif target.components().any(|c| c == std::path::Component::ParentDir)\n    || target.is_absolute()\n{\n    eprintln!(\"artifact target must be relative and below the prefix\");\n    std::process::exit(1);\n}","typeGuard":"fn stays_below_prefix(t: &std::path::Path, prefix: &std::path::Path) -> bool {\n    !t.components().any(|c| c == std::path::Component::ParentDir)\n        && t.strip_prefix(prefix).is_ok()\n}","tryCatchPattern":null,"preventionTips":["Declare generic artifact targets as prefix-relative paths.","Never use '..' in artifact targets.","Match your prefix configuration with the cask's expected layout.","Use font/app artifact kinds for locations outside the prefix instead of forcing generic artifacts."],"tags":["brew-cask","path-safety","prefix","path-traversal"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}