{"record":{"id":"c31ec00467d54b42","repo":"jdx/mise","slug":"brew-cask-binary-target-contains-nul","errorCode":null,"errorMessage":"brew-cask: binary target contains NUL","messagePattern":"brew-cask: binary target contains NUL","errorType":"validation","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":6131,"sourceCode":"    }\n    Ok(roots)\n}\n\nfn is_appdir_binary_target(target_name: &str) -> bool {\n    target_name.starts_with(\"$APPDIR/\")\n}\n\nfn allowed_binary_target_roots_display(roots: &[PathBuf]) -> String {\n    roots\n        .iter()\n        .map(|root| root.display().to_string())\n        .collect::<Vec<_>>()\n        .join(\" or \")\n}\n\nfn binary_target_path(target_name: &str, appdir: &Path) -> Result<PathBuf> {\n    if target_name.contains('\\0') {\n        bail!(\"brew-cask: binary target contains NUL\");\n    }\n    if let Some(relative) = target_name.strip_prefix(\"$APPDIR/\") {\n        let relative = Path::new(relative);\n        if relative.components().next().is_none()\n            || relative\n                .components()\n                .any(|component| !matches!(component, Component::Normal(_)))\n        {\n            bail!(\"brew-cask: binary $APPDIR target '{target_name}' must stay below Applications\");\n        }\n        if !allowed_appdir_roots()?.iter().any(|root| root == appdir) {\n            bail!(\"brew-cask: invalid appdir '{}'\", appdir.display());\n        }\n        return Ok(appdir.join(relative));\n    }\n    if target_name.contains(\"$APPDIR\") {\n        bail!(\"brew-cask: $APPDIR must prefix a binary target\");\n    }","sourceCodeStart":6113,"sourceCodeEnd":6149,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L6113-L6149","documentation":"binary_target_path converts a cask 'binary' artifact target into the destination PathBuf (under the brew prefix, /usr/local, or $APPDIR). Before any filesystem work it rejects targets containing a NUL byte, since NUL can never occur in a valid filename and would truncate the path at the OS boundary. In practice this indicates corrupted cask data or deliberate path injection.","triggerScenarios":"A cask 'binary' artifact target string containing '\\0' — from tap JSON, cached cask data, or a stored receipt — reaching binary_target_path during install/link.","commonSituations":"Corrupted cache or receipt JSON; a malicious tap attempting path injection into bin targets; truncated downloads of tap metadata.","solutions":["Inspect the cask's binary stanza (`brew info --json=v2 --cask <token>`, tap source) for garbage characters","Remove the offending tap (`brew untap <tap>`) if it is third-party","Clear mise's cached cask data (`mise cache clear`) and retry","Update mise for the latest hardening and parser fixes"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn is_clean_binary_target(name: &str) -> bool {\n    !name.contains('\\0')\n}","tryCatchPattern":"match binary_target_path(name, &appdir) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"contains NUL\") => {\n        warn!(\"dropping corrupted binary target {name:?}\");\n        continue;\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Treat NUL bytes in artifact targets as corruption — investigate the tap or cache instead of retrying","Validate tap JSON before installing casks from untrusted sources"],"tags":["brew-cask","path-validation","hardening","corruption"],"backgroundTag":"invalid-path-characters","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}