{"record":{"id":"81fcf9afb859dbd7","repo":"gitbutlerapp/gitbutler","slug":"refusing-to-read-git-metadata-path","errorCode":null,"errorMessage":"Refusing to read Git metadata path '{}'","messagePattern":"Refusing to read Git metadata path '(.+?)'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-repo/src/commands.rs","lineNumber":393,"sourceCode":"                canonical_workdir.display()\n            );\n        }\n    };\n\n    // Refuse `.git` in every spelling an OS can map onto it (`.GIT`,\n    // `GIT~1`, `.git.`). Runs before the stat: `.git` can be a file\n    // (linked worktrees) and must be refused all the same.\n    if relative_path.components().any(|component| {\n        matches!(\n            gix::validate::path::component(\n                component.as_os_str().as_encoded_bytes().as_bstr(),\n                None,\n                Default::default(),\n            ),\n            Err(gix::validate::path::component::Error::DotGitDir)\n        )\n    }) {\n        bail!(\n            \"Refusing to read Git metadata path '{}'\",\n            relative_path.display()\n        );\n    }\n\n    let out = match path.symlink_metadata() {\n        Ok(md) => {\n            // Directories are exempt: their `FileInfo` placeholder carries no\n            // content, and callers rely on getting it rather than an error.\n            if !md.is_dir() {\n                ensure_not_ignored(repo, &relative_path)?;\n            }\n\n            if md.is_file() {\n                let content = std::fs::read(&path)?;\n                FileInfo::from_content(&relative_path, &content)\n            } else if md.is_symlink() {\n                let content = std::fs::read_link(&path)?;","sourceCodeStart":375,"sourceCodeEnd":411,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/gitbutler-repo/src/commands.rs#L375-L411","documentation":"read_worktree_file validates path components with gix and refuses any path that resolves into the .git directory (gix::validate::path::component::Error::DotGitDir). Reading Git metadata files through this API is not allowed.","triggerScenarios":"Calling read_worktree_file for a path inside .git (e.g. \".git/config\", \".git/HEAD\", \".git/objects/...\") via get_workspace_file_from_source or read_file_from_workspace.","commonSituations":"A file watcher or UI listing the worktree includes .git contents and something tries to display them; tooling asking to read .git/COMMIT_EDITMSG through the workspace-file API.","solutions":["Exclude .git (and nested git dirs) from the paths you request.","Read Git metadata through dedicated git APIs (repo config, refs) instead of file reads.","Filter directory listings before requesting file contents."],"exampleFix":"// before\nread_file_from_workspace(project, \".git/config\")\n// after\nif path.components().any(|c| c.as_os_str() == \".git\") { return Err(...); }\nread_file_from_workspace(project, path)","handlingStrategy":"validation","validationCode":"fn touches_git_dir(p: &Path) -> bool {\n    p.components().any(|c| c.as_os_str() == \".git\")\n}\nif touches_git_dir(path) { return Err(\"git metadata paths are not readable here\"); }","typeGuard":"fn is_git_metadata(p: &Path) -> bool {\n    p.components().any(|c| c.as_os_str() == \".git\")\n}","tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"Refusing to read Git metadata path\") => {\n        // skip this path in listings\n    }\n    other => other,\n}","preventionTips":["Filter .git entries out of any directory listing fed to file reads.","Use dedicated git APIs for metadata (config, refs, objects).","Apply this check to nested worktrees' .git files too."],"tags":["git","security","path-validation","rust"],"backgroundTag":"path-traversal-blocked","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}