{"record":{"id":"a768c3cf2c2c3fc0","repo":"jdx/mise","slug":"brew-cask-completion-target-must-be-under","errorCode":null,"errorMessage":"brew-cask: completion target '{}' must be under {}","messagePattern":"brew-cask: completion target '(.+?)' must be under (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":3178,"sourceCode":"    let path = PathBuf::from(&target_name);\n    let target = if path.is_absolute() {\n        path\n    } else if target_name.contains('/') {\n        prefix.join(path)\n    } else {\n        default_completion_dir(shell).join(completion_filename(shell, &target_name)?)\n    };\n    if target\n        .components()\n        .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\n            \"brew-cask: completion target '{}' must not contain '..'\",\n            target.display()\n        );\n    }\n    if !target.starts_with(&prefix) {\n        bail!(\n            \"brew-cask: completion target '{}' must be under {}\",\n            target.display(),\n            prefix.display()\n        );\n    }\n    Ok(target)\n}\n\nfn generated_completion_target_path(shell: CompletionShell, base_name: &str) -> Result<PathBuf> {\n    match shell {\n        CompletionShell::Pwsh => {\n            let name = format!(\"_{}.ps1\", base_name);\n            completion_target_path(shell, &name)\n        }\n        _ => completion_target_path(shell, base_name),\n    }\n}\n","sourceCodeStart":3160,"sourceCodeEnd":3196,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/mod.rs#L3160-L3196","documentation":"This error enforces the containment part of the same path-safety check: after resolving the completion target (absolute, prefix-joined, or default-dir joined), the path must remain under the Homebrew prefix. If it starts outside the prefix (e.g. /etc/..., /Users/..., or an absolute path into another tree), the install is rejected to prevent casks from writing outside the managed prefix.","triggerScenarios":"A cask completion target declared as an absolute path outside the Homebrew prefix (e.g. /etc/bash_completion.d/foo or /usr/local/share/... when the prefix is /opt/homebrew), or an absolute target that does not begin with the current prefix after $HOMEBREW_PREFIX substitution.","commonSituations":"A cask written for an Intel Homebrew layout (/usr/local) being installed on Apple Silicon (/opt/homebrew); hard-coded absolute paths in a hand-rolled cask; a $HOMEBREW_PREFIX substitution producing a non-prefix root; shared cask recipes copied between systems with different prefixes.","solutions":["Change the target to use the $HOMEBREW_PREFIX placeholder instead of a hard-coded absolute path.","Verify the Homebrew prefix on this machine (brew --prefix) and make the target relative or prefix-based so it resolves under it.","If the file must live outside the prefix, install it out-of-band rather than through the cask completion mechanism.","Update a stale cask recipe that assumes the /usr/local prefix to the prefix-independent form."],"exampleFix":"// before (Intel-style absolute path on Apple Silicon)\ncompletions = [{ target = \"/usr/local/etc/bash_completion.d/mytool\" }]\n// after\ncompletions = [{ target = \"$HOMEBREW_PREFIX/etc/bash_completion.d/mytool\" }]","handlingStrategy":"validation","validationCode":"let target = resolve_target(target_name)?; // apply $HOMEBREW_PREFIX substitution first\nif !target.starts_with(&prefix) {\n    return Err(format!(\"target {} is outside prefix {}\", target.display(), prefix.display()));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always express custom completion locations via $HOMEBREW_PREFIX instead of hard-coded absolute paths.","Check `brew --prefix` per machine; do not assume /usr/local vs /opt/homebrew.","Test cask recipes on both Intel and Apple Silicon prefix layouts before distributing."],"tags":["path-traversal","security","brew-cask","completions","homebrew-prefix"],"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"}