{"record":{"id":"514111c078d74060","repo":"jdx/mise","slug":"brew-cask-invalid-app-source","errorCode":null,"errorMessage":"brew-cask: invalid app source '{}'","messagePattern":"brew-cask: invalid app source '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/cask/mod.rs","lineNumber":899,"sourceCode":"        remove_stale_versions(&caskroom_token, &cask.version)?;\n        remove_cask_journals(&cask.token)?;\n        file::remove_all(stage)?;\n        Ok(cask.version)\n    }\n}\n\nimpl AppArtifact {\n    fn target_name(&self) -> Result<&str> {\n        if let Some(target) = &self.target {\n            return Ok(target);\n        }\n        // A nested archive source still installs as its bundle basename. Check\n        // the source before taking that basename so traversal cannot be hidden.\n        if self.source.contains(['\\0', '\\\\'])\n            || self.source.ends_with('/')\n            || relative_artifact_path(Path::new(\"\"), Path::new(&self.source)).is_none()\n        {\n            bail!(\"brew-cask: invalid app source '{}'\", self.source);\n        }\n        file_name_str(Path::new(&self.source), \"app source\")\n    }\n}\n\nimpl BinaryArtifact {\n    fn target_name(&self) -> Result<String> {\n        match &self.target {\n            Some(target) => Ok(target.clone()),\n            None => Ok(file_name_str(Path::new(&self.source), \"binary source\")?.to_string()),\n        }\n    }\n\n    fn target_path(&self, appdir: &Path) -> Result<PathBuf> {\n        binary_target_path(&self.target_name()?, appdir)\n    }\n}\n","sourceCodeStart":881,"sourceCodeEnd":917,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/mod.rs#L881-L917","documentation":"AppArtifact source validation rejects sources that contain NUL bytes or backslashes, end with '/', or cannot be expressed as a relative artifact path (relative_artifact_path returns None). This runs before taking the basename so that traversal tricks (embedded separators, directory escapes) cannot hide inside a nested archive source.","triggerScenarios":"Resolving an app artifact whose `source` string contains '\\0' or '\\\\', ends with '/', or is absolute/outside the staged archive (relative_artifact_path(Path::new(\"\"), source) fails).","commonSituations":"Hand-edited cask JSON with Windows-style separators; a source accidentally written as a directory ('foo/') instead of a file; malicious or corrupted cask definitions attempting '../' traversal; conversion scripts emitting absolute paths.","solutions":["Fix the cask's source to be a clean relative path inside the archive (no backslashes, NULs, or trailing slash).","Replace backslash separators with '/' if the definition was written on Windows.","Point the source at the actual file/bundle rather than a directory.","Verify the cask against its upstream tap if you did not author it."],"exampleFix":"// before\n\"source\": \".\\\\Applications\\\\MyApp.app/\"\n// after\n\"source\": \"MyApp.app\"","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":["rust","brew","cask","path-validation","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}