{"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":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":2961,"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":2943,"sourceCodeEnd":2979,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/mod.rs#L2943-L2979","documentation":"After rejecting `..` components, mise enforces that the resolved completion target still starts with the expected prefix (the shell's default completion directory). This is a second containment check: even without traversal components, a target that resolves outside the managed tree (e.g. via absolute construction or prefix mismatch) is rejected to keep cask completions inside mise's completion directories.","triggerScenarios":"`!target.starts_with(&prefix)` where `prefix` is the expected completion directory for the shell — the computed target (e.g. `default_completion_dir(shell).join(...)`) resolved to a path not under that prefix.","commonSituations":"A cask overrides the completion directory to somewhere outside mise's managed tree; an environment/`HOME` mismatch makes the resolved target diverge from the expected prefix; a custom completion target on a different filesystem or volume-style path.","solutions":["Make the cask's completion target relative so it lands under the shell's default completion directory","Check your HOME/completion-dir environment so the resolved prefix matches expectations","If you need completions elsewhere, link them yourself manually after install rather than via the cask stanza"],"exampleFix":"// before (cask stanza)\ncompletion \"/usr/local/share/fish/vendor_completions.d/x.fish\"\n// after\ncompletion \"x.fish\"  # goes under the managed fish completions dir","handlingStrategy":"validation","validationCode":"let prefix = std::path::Path::new(\"<expected-completion-dir>\");\nassert!(target_path.starts_with(prefix), \"completion target must stay under the shell's completion dir\");","typeGuard":"fn stays_under(prefix: &std::path::Path, target: &std::path::Path) -> bool {\n  target.starts_with(prefix)\n}","tryCatchPattern":"if let Err(e) = result {\n  if e.contains(\"must be under\") {\n    eprintln!(\"completion target escaped the managed tree; make it relative so it lands under the default completion dir\");\n  }\n}","preventionTips":["Keep completion targets relative to the shell's default directory","Avoid overriding the completion directory to outside locations","Check HOME-dependent path resolution when installs run in different environments"],"tags":["path-traversal","security","brew-cask","completions"],"backgroundTag":"path-traversal-blocked","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}