{"record":{"id":"4c390c2b5c8b2248","repo":"jdx/mise","slug":"brew-cask-app-target-contains-nul-4c390c","errorCode":null,"errorMessage":"brew-cask: app target contains NUL","messagePattern":"brew-cask: app target contains NUL","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/paths.rs","lineNumber":115,"sourceCode":"        };\n        let matches = match (a, b) {\n            (Component::Normal(a), Component::Normal(b)) => match (a.to_str(), b.to_str()) {\n                (Some(a), Some(b)) => a.eq_ignore_ascii_case(b),\n                _ => a == b,\n            },\n            _ => a == b,\n        };\n        if !matches {\n            return false;\n        }\n    }\n    true\n}\n\npub(super) fn app_target_path(target_name: &str) -> Result<PathBuf> {\n    let app_dir = target_app_dir()?;\n    if target_name.contains('\\0') {\n        bail!(\"brew-cask: app target contains NUL\");\n    }\n    if target_name.contains('/') {\n        let target = target_name.replace(\"$HOMEBREW_PREFIX\", &prefix::prefix().to_string_lossy());\n        let path = PathBuf::from(target);\n        if path\n            .components()\n            .any(|component| matches!(component, Component::ParentDir))\n        {\n            bail!(\"brew-cask: app target '{target_name}' must not contain '..'\");\n        }\n        if path.is_absolute() {\n            let prefix_app_dir = prefix::prefix().join(\"Applications\");\n            if path.starts_with(&app_dir) || path.starts_with(&prefix_app_dir) {\n                return Ok(path);\n            }\n            // Casks routinely hardcode an absolute `/Applications/Foo.app`\n            // target. When an override appdir is configured, relocate such a\n            // target into it (preserving any subdirectories) rather than","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/cask/paths.rs#L97-L133","documentation":"app_target_path rejects app target names containing NUL bytes because Rust PathBuf and the filesystem APIs cannot represent embedded NULs; such a target is necessarily invalid. The check runs before any path construction or install work.","triggerScenarios":"Calling app_target_path (directly or via installed_skip_reason, app_target_paths, install_app, etc.) with a target_name string containing '\\0', typically from untrusted cask metadata or malformed JSON input.","commonSituations":"Corrupt or hostile cask metadata whose target field embeds a NUL, a truncation bug upstream producing a padded string, or binary data accidentally parsed into the target field.","solutions":["Strip or reject NUL characters from the target name before installing the app","Re-fetch the cask metadata from the trusted upstream source","Sanitize inputs at the deserialization boundary so embedded NULs never reach path construction"],"exampleFix":"// before\nlet target_name = raw_target; // may contain '\\0'\n// after\nlet target_name = raw_target.trim_end_matches('\\0');","handlingStrategy":"validation","validationCode":"if target_name.contains('\\0') {\n    return Err(\"app target contains NUL byte\");\n}","typeGuard":"fn is_safe_target(s: &str) -> bool {\n    !s.is_empty() && !s.contains('\\0') && !s.contains('\\u{0}')\n}","tryCatchPattern":"match install_result {\n    Err(e) if e.to_string().contains(\"NUL\") => {\n        eprintln!(\"cask metadata is corrupt; refetching\");\n        refetch_cask_metadata();\n    }\n    _ => {}\n}","preventionTips":["Sanitize strings at the JSON/deserialization boundary for embedded NULs","Treat cask metadata from untrusted sources as hostile and validate all path fields"],"tags":["brew-cask","path-validation","input-sanitization"],"backgroundTag":"invalid-argument-format","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}