{"record":{"id":"22586603ae5e32f0","repo":"jdx/mise","slug":"brew-cask-invalid-generic-artifact-parent","errorCode":null,"errorMessage":"brew-cask: invalid generic artifact parent","messagePattern":"brew-cask: invalid generic artifact parent","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":2225,"sourceCode":"            || current_groups.contains(&stat.st_gid);\n        let writable_by_untrusted = stat.st_mode & 0o002 != 0\n            || (stat.st_mode & 0o020 != 0 && (!allow_current_user || !trusted_group));\n        if !SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFDIR)\n            || !trusted_owner\n            || writable_by_untrusted\n        {\n            bail!(\n                \"brew-cask: refusing operation through untrusted directory {}\",\n                directory.display()\n            );\n        }\n        Ok(())\n    };\n    let mut directory = resolved_root.to_path_buf();\n    verify(&fd, &directory)?;\n    for component in relative.components() {\n        let Component::Normal(name) = component else {\n            bail!(\"brew-cask: invalid generic artifact parent\");\n        };\n        directory.push(name);\n        fd = match openat(&fd, name, flags, Mode::empty()) {\n            Ok(fd) => fd,\n            Err(nix::errno::Errno::ENOENT) if create_missing => {\n                match nix::sys::stat::mkdirat(\n                    &fd,\n                    name,\n                    Mode::S_IRWXU | Mode::S_IRGRP | Mode::S_IXGRP | Mode::S_IROTH | Mode::S_IXOTH,\n                ) {\n                    Ok(()) | Err(nix::errno::Errno::EEXIST) => {}\n                    Err(err) => {\n                        return Err(err).wrap_err_with(|| {\n                            format!(\n                                \"brew-cask: cannot create operation directory {}\",\n                                directory.display()\n                            )\n                        });","sourceCodeStart":2207,"sourceCodeEnd":2243,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/packages/brew/cask.rs#L2207-L2243","documentation":"While opening the generic artifact's parent path component by component, only Component::Normal names are accepted. Any other component (ParentDir '..', CurDir '.', RootDir '/', or a disk prefix) makes the path invalid for a trusted parent, because such paths cannot be safely walked with openat relative to the trusted prefix root.","triggerScenarios":"Passing a parent path containing '..', '.', or a leading '/' into the generic artifact parent resolution; parent strings built by string concatenation with unvalidated user input instead of Path::join.","commonSituations":"Cask stanzas or config with relative parent paths like \"../shared/lib\"; code that builds paths with format!() and never normalizes; templates leaving placeholder dot components behind.","solutions":["Normalize the parent path before use: resolve '.'/'..' with a path-cleaning step or canonicalize the existing prefix portion","Fix the cask/config so the parent is a plain sequence of names relative to the Homebrew prefix","Validate user-supplied parent strings with a components() check and reject non-normal components early"],"exampleFix":"// before\nlet parent = format!(\"{}/../lib\", prefix);\n\n// after -- build with Path and assert no traversal\nlet parent = prefix.join(\"lib\");\nassert!(!parent.components().any(|c| matches!(c, std::path::Component::ParentDir)));","handlingStrategy":"validation","validationCode":"use std::path::{Path, Component};\n\nfn has_only_normal_components(path: &Path) -> bool {\n    !path.is_absolute()\n        && path.components().next().is_some()\n        && path.components().all(|c| matches!(c, Component::Normal(_)))\n}","typeGuard":"fn is_normal_relative_path(p: &std::path::Path) -> bool {\n    p.components().all(|c| matches!(c, std::path::Component::Normal(_)))\n}","tryCatchPattern":null,"preventionTips":["Never build parent paths with '..' via string concatenation; use Path::join","Canonicalize or path-clean user-supplied parents before passing them to artifact handlers","Reject ParentDir/CurDir/RootDir components early in validation"],"tags":["brew-cask","path-validation","parent-dir"],"backgroundTag":"path-traversal-guard","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}