{"record":{"id":"fad693e01ad4a63f","repo":"jdx/mise","slug":"brew-cask-invalid-structured-flight-path","errorCode":null,"errorMessage":"brew-cask: invalid structured flight path '{}'","messagePattern":"brew-cask: invalid structured flight path '(.+?)'","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask.rs","lineNumber":4038,"sourceCode":"        .replace(\"{{staged_path}}\", &staged_path.to_string_lossy())\n        .replace(\"{{appdir}}\", &appdir.to_string_lossy());\n    if let Some(version) = version {\n        value = value.replace(\"{{version}}\", version);\n    }\n    if let Some(rest) = value.strip_prefix(\"~/\") {\n        value = crate::dirs::HOME.join(rest).to_string_lossy().to_string();\n    }\n    value\n}\n\nfn validate_flight_relative_path(path: &str) -> Result<()> {\n    let path = Path::new(path);\n    if path.is_absolute()\n        || path\n            .components()\n            .any(|component| matches!(component, Component::ParentDir))\n    {\n        bail!(\n            \"brew-cask: invalid structured flight path '{}'\",\n            path.display()\n        );\n    }\n    Ok(())\n}\n\nfn expand_braces(pattern: &str) -> Vec<String> {\n    let Some(start) = pattern.find('{') else {\n        return vec![pattern.to_string()];\n    };\n    let Some(end_offset) = pattern[start + 1..].find('}') else {\n        return vec![pattern.to_string()];\n    };\n    let end = start + 1 + end_offset;\n    let prefix = &pattern[..start];\n    let suffix = &pattern[end + 1..];\n    let mut expanded = Vec::new();","sourceCodeStart":4020,"sourceCodeEnd":4056,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/system/packages/brew/cask.rs#L4020-L4056","documentation":"`validate_flight_relative_path` rejects flight paths that are absolute or contain a `..` (ParentDir) component. It is the traversal guard applied to every path joined onto the staged root, so structured steps cannot reach files outside the staging sandbox.","triggerScenarios":"A cask stanza path like `../shared/lib.x` or `/Applications/Foo.app` where a staged-relative path is required; a template expanding to a value that begins with `/`; concatenation bugs leaving `..` segments in the string.","commonSituations":"Porting ruby stanzas that use absolute paths; a user-edited cask trying to share payloads across casks; version strings injecting a leading separator.","solutions":["Rewrite the path relative to the staged root with no `..` components","Use an artifact form and base that legitimately supports the location instead of smuggling absolute paths","Sanitize template output before it lands in a staged-relative field"],"exampleFix":"# before\nmove(source: \"../../shared/lib.dylib\", target: \"lib/lib.dylib\")\n# after\nmove(source: \"shared/lib.dylib\", target: \"lib/lib.dylib\")","handlingStrategy":"validation","validationCode":"// Reject absolute or parent-escaping relative paths before they reach the resolver\nfn is_safe_relative_path(p: &str) -> bool {\n    let path = std::path::Path::new(p);\n    !path.is_absolute()\n        && !path\n            .components()\n            .any(|c| matches!(c, std::path::Component::ParentDir))\n}","typeGuard":null,"tryCatchPattern":"if err.to_string().contains(\"invalid structured flight path\") {\n    // rewrite the offending path staged-relative; do not attempt to whitelist `..` segments\n}","preventionTips":["Lint cask stanzas for absolute paths and `..` segments","Sanitize template output before it becomes a flight path","Treat this guard as non-negotiable: it is the staging sandbox boundary"],"tags":["brew-cask","path-validation","path-traversal","install-artifact"],"backgroundTag":"path-traversal-detected","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}