{"record":{"id":"a712a9df36caa261","repo":"jdx/mise","slug":"app-info-plist-must-be-a-regular-file","errorCode":null,"errorMessage":"app Info.plist must be a regular file","messagePattern":"app Info\\.plist must be a regular file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/system/packages/brew/cask/app_version.rs","lineNumber":60,"sourceCode":"\n/// Reads optional short and build strings from an absolute app bundle path.\n///\n/// Accepts XML and binary plists. Returns an error for unsafe paths, nonregular\n/// files, malformed dictionaries, or version fields whose values are not strings.\npub(super) fn read_app_version(app: &Path) -> Result<AppVersion> {\n    use nix::fcntl::{OFlag, openat};\n    use nix::sys::stat::{Mode, SFlag, fstat};\n\n    let contents = app.join(\"Contents\");\n    let parent = open_trusted_directory(Path::new(\"/\"), contents.strip_prefix(\"/\")?, true, false)?;\n    let fd = openat(\n        &parent.fd,\n        \"Info.plist\",\n        OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_NONBLOCK,\n        Mode::empty(),\n    )?;\n    if SFlag::from_bits_truncate(fstat(&fd)?.st_mode) & SFlag::S_IFMT != SFlag::S_IFREG {\n        bail!(\"app Info.plist must be a regular file\");\n    }\n    let plist = plist::Value::from_reader(std::fs::File::from(fd))?;\n    let dict = plist\n        .as_dictionary()\n        .ok_or_else(|| eyre!(\"app Info.plist must be a dictionary\"))?;\n    let field = |key| -> Result<Option<String>> {\n        dict.get(key)\n            .map(|value| {\n                value\n                    .as_string()\n                    .map(str::to_owned)\n                    .ok_or_else(|| eyre!(\"app {key} must be a string\"))\n            })\n            .transpose()\n    };\n    Ok(AppVersion {\n        short: field(\"CFBundleShortVersionString\")?,\n        build: field(\"CFBundleVersion\")?,","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/packages/brew/cask/app_version.rs#L42-L78","documentation":"When determining an installed cask app's version, mise opens the app bundle's Info.plist with O_NOFOLLOW (symlinks rejected) and then verifies via fstat that it is a regular file. Anything else (directory, symlink, fifo, device) triggers this bail to avoid unsafe plist reads.","triggerScenarios":"The Info.plist inside an installed .app bundle is a symlink, directory, or special file rather than a regular file; the open succeeds (or the stat shows the wrong type) and the S_IFREG check fails.","commonSituations":"Tampered or corrupted app bundles; sync tools (Dropbox/iCloud) replacing files with placeholders/symlinks; malicious bundles where plist is a symlink to elsewhere — a security guard.","solutions":["Inspect the app: ls -la '/Applications/MyApp.app/Contents/Info.plist'","Restore the app bundle — reinstall the cask (mise should surface an installed_skip_reason otherwise)","Replace the symlink with a real plist file if intentional","Delete broken bundles that mise should no longer treat as installed"],"exampleFix":"// before\n$ ls -l MyApp.app/Contents/Info.plist\nInfo.plist -> /etc/passwd   # symlink\n// after\n$ rm MyApp.app/Contents/Info.plist && reinstall the app","handlingStrategy":"try-catch","validationCode":"const st = fs.lstatSync(appPath + '/Contents/Info.plist');\nif (!st.isFile()) console.warn('Info.plist is not a regular file; reinstall the cask');","typeGuard":"function isRegularPlist(appPath: string): boolean {\n  try { return fs.lstatSync(appPath + '/Contents/Info.plist').isFile(); }\n  catch { return false; }\n}","tryCatchPattern":"match read_app_version(app) {\n    Err(e) if e.to_string().contains(\"must be a regular file\") => reinstall_cask(app),\n    other => other?,\n}","preventionTips":["Keep app bundles out of cloud-sync placeholder directories","Reinstall casks whose bundles were modified by other tools","Treat non-regular Info.plist as a tampering signal and investigate"],"tags":["brew","cask","security","filesystem","plist"],"backgroundTag":"unexpected-response-shape","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"}