{"record":{"id":"8dfef6eb96a78d51","repo":"jdx/mise","slug":"invalid-brew-formula-name-use-brew-formula-or-brew-owner-tap","errorCode":null,"errorMessage":"invalid brew formula {name:?}; use brew:<formula> or brew:<owner>/<tap>/<formula> with normal, nonempty path components","messagePattern":"invalid brew formula (.+?); use brew:<formula> or brew:<owner>/<tap>/<formula> with normal, nonempty path components","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/mod.rs","lineNumber":65,"sourceCode":"pub(crate) use cask::{\n    BrewCaskManager, apply_cask_prune_plan, cask_formula_dependencies, cask_prune_plan,\n};\npub(crate) use maintenance::{apply_prune_plan, default_tap_url, linked_formulae, prune_plan};\n\n/// Resolve a canonical formula name or owner/tap/name to its installed opt path using local records.\n/// Qualified names identify the rack by their final component; aliases and tap provenance are not resolved.\npub(crate) fn package_root(name: &str) -> Result<PathBuf> {\n    let parts = name.split('/').collect::<Vec<_>>();\n    if !matches!(parts.len(), 1 | 3)\n        || parts.iter().any(|part| {\n            part.is_empty()\n                || part\n                    .chars()\n                    .any(|c| c.is_whitespace() || c.is_control() || matches!(c, ':' | '\\\\'))\n                || !is_normal_formula_component(part)\n        })\n    {\n        bail!(\n            \"invalid brew formula {name:?}; use brew:<formula> or brew:<owner>/<tap>/<formula> with normal, nonempty path components\"\n        );\n    }\n    if parts.len() == 3 && parts[0] == \"homebrew\" && parts[1] == \"cask\" {\n        bail!(\"brew:{name}: the Homebrew cask namespace is unsupported for formula lookup\");\n    }\n    let formula = request_formula_name(name);\n    assert!(\n        is_normal_formula_component(formula),\n        \"validated formula must normalize to exactly one normal path component\"\n    );\n    pour::strict_package_root(formula)\n        .wrap_err_with(|| format!(\"failed to locate installed brew:{name}\"))\n}\n\n/// Require one normal path component so a formula name stays within its expected rack.\nfn is_normal_formula_component(name: &str) -> bool {\n    let mut components = Path::new(name).components();","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/mod.rs#L47-L83","documentation":"package_root validates brew formula names before deriving their Cellar path. Names must decompose into `brew:<formula>` or `brew:<owner>/<tap>/<formula>` with normal (non-empty, no whitespace/control chars, no ':' or backslash) path components; anything else is rejected to prevent malformed lookups and path traversal.","triggerScenarios":"Called from assert_query_target and package_root tests; triggered by names containing whitespace, control characters, ':', backslash, empty segments, or segments failing is_normal_formula_component (e.g. leading '.' or '-'), or more/fewer than 1 or 3 path segments.","commonSituations":"Copy-pasting formula specs with stray spaces or Windows-style backslashes; passing a full bottle URL or versioned spec as the name; empty segments from typos like \"brew://wget\"; trying to sneak path separators into a formula name.","solutions":["Use a plain formula name (brew:wget) or the full three-part form (brew:owner/tap/formula).","Remove whitespace, control characters, ':', and backslashes from the name.","Check for empty path segments (double slashes) in the spec.","For nonstandard taps, register the tap with an explicit URL rather than encoding it into the formula string."],"exampleFix":"// before\nlet tool = \"brew:homebrew-core/wget\";   // 2 segments: invalid\nlet tool2 = \"brew:Cask/ thing\";          // whitespace: invalid\n// after\nlet tool = \"brew:homebrew/core/wget\";   // 3 normal segments\nlet tool2 = \"brew:wget\";                 // simple form","handlingStrategy":"validation","validationCode":"fn valid_formula_spec(name: &str) -> bool {\n    let parts: Vec<&str> = name.split('/').collect();\n    matches!(parts.len(), 1 | 3)\n        && parts.iter().all(|p| {\n            !p.is_empty()\n                && !p.chars().any(|c| c.is_whitespace() || c.is_control() || matches!(c, ':' | '\\\\'))\n                && !p.starts_with('.')\n        })\n}","typeGuard":"fn as_formula_spec(name: &str) -> Option<Vec<&str>> {\n    let parts: Vec<&str> = name.split('/').collect();\n    if matches!(parts.len(), 1 | 3)\n        && parts.iter().all(|p| !p.is_empty() && !p.chars().any(|c| c.is_whitespace() || c.is_control() || matches!(c, ':' | '\\\\')))\n    { Some(parts) } else { None }\n}","tryCatchPattern":null,"preventionTips":["Validate tool specs when parsing config, before passing to the backend.","Reject names containing ':', backslashes, or whitespace at the config layer.","Use simple formula names or full owner/tap/formula — never mixed forms.","Strip shell quoting artifacts before constructing brew specs."],"tags":["validation","brew","path-traversal"],"backgroundTag":"invalid-identifier-format","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"}