{"record":{"id":"5811a7f25399922e","repo":"jdx/mise","slug":"brew-cask-invalid-kind-value","errorCode":null,"errorMessage":"brew-cask: invalid {kind} '{value}'","messagePattern":"brew-cask: invalid (.+?) '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"src/system/packages/brew/cask.rs","lineNumber":968,"sourceCode":"    if cask.token != requested_token && !trusted_alias {\n        bail!(\n            \"brew-cask: requested token '{requested_token}' does not match API token '{}'\",\n            cask.token\n        );\n    }\n    Ok(())\n}\n\nfn validate_cask_path_component(kind: &str, value: &str) -> Result<()> {\n    let mut components = Path::new(value).components();\n    let valid = !value.is_empty()\n        && !value.contains('\\0')\n        && matches!(components.next(), Some(Component::Normal(_)))\n        && components.next().is_none()\n        && value != \".metadata\"\n        && !value.starts_with(\".mise-\");\n    if !valid {\n        bail!(\"brew-cask: invalid {kind} '{value}'\");\n    }\n    Ok(())\n}\n\nasync fn fetch_and_stage(cask: &Cask, pr: Option<&dyn SingleReport>) -> Result<PathBuf> {\n    if cask.url.ends_with(\".git\") {\n        return fetch_git_clone_and_stage(cask, pr).await;\n    }\n    let archive = fetch_archive(cask, pr).await?;\n    extract_archive(cask, &archive, pr)\n}\n\nasync fn fetch_git_clone_and_stage(cask: &Cask, pr: Option<&dyn SingleReport>) -> Result<PathBuf> {\n    let extract_dir = crate::dirs::CACHE\n        .join(\"system-brew\")\n        .join(\"cask-extract\")\n        .join(format!(\"{}-{}\", cask.token, cask.version));\n    file::remove_all(&extract_dir)?;","sourceCodeStart":950,"sourceCodeEnd":986,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L950-L986","documentation":"validate_cask_path_component (src/system/packages/brew/cask.rs:968) hardens every string that becomes a filesystem path — the requested token, the API token, and the cask version. A value is valid only if it is a single normal path component (no '/', no '..' resolution, no NUL bytes), is not empty, is not '.metadata', and does not start with '.mise-'. Values failing this are rejected before any path join; this is a path-traversal and metadata-collision guard against hostile tap metadata.","triggerScenarios":"A cask JSON (typically from a third-party tap) whose token or version contains '/', '..', or a NUL, is empty, resolves as an absolute/rooted path, equals '.metadata', or begins with '.mise-'; also a user-supplied name like ../evil passed as the requested token.","commonSituations":"Malicious or malformed taps crafting metadata that escapes the caskroom; hand-edited cask JSON; versions like 1.0/beta or tokens copied with a trailing '/'; reserved-name collisions with mise's own metadata directories.","solutions":["Remove path separators, '..', and leading '.mise-' from the requested token or version in your config","If the offending value comes from the tap's metadata, the tap is broken or hostile — stop using it and report it upstream","Never bypass this check; the token/version strings are joined directly into privileged filesystem paths, which is exactly what the guard prevents"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// run on every user-supplied token/version before it reaches install APIs\nassert!(is_valid_cask_component(requested_token));","typeGuard":"fn is_valid_cask_component(value: &str) -> bool {\n    let mut c = std::path::Path::new(value).components();\n    !value.is_empty()\n        && !value.contains('\\0')\n        && matches!(c.next(), Some(std::path::Component::Normal(_)))\n        && c.next().is_none()\n        && value != \".metadata\"\n        && !value.starts_with(\".mise-\")\n}","tryCatchPattern":null,"preventionTips":["Reject tokens containing '/', '..', NUL, or the reserved .metadata / .mise-* prefixes at the config boundary","Never join untrusted cask metadata into filesystem paths without a single-component check","Lint tap-provided cask JSON (token and version fields) before trusting it in CI"],"tags":["brew-cask","path-traversal","security","validation"],"backgroundTag":"path-traversal-blocked","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}