From 3cdb6076d6de8696896ec9e962bab19425c8fd39 Mon Sep 17 00:00:00 2001
From: lucascouts <lucascs@protonmail.com>
Date: Wed, 14 Oct 2026 14:54:53 -0300
Subject: [PATCH] git: refuse to create a worktree when the repository's config
 can run programs

`git worktree add` checks out a whole tree, and a checkout runs whatever
programs the repository's own config names for it. RealGitRepository::
create_worktree ran a plain `git worktree add`, so a repository whose
.git/config named a filter driver had that program run the moment the
agent panel, the worktree picker or a restored archived thread created a
worktree in it. Repository-local config is not versioned: whoever wrote it
-- a tool, a script, a checkout the user does not trust -- chose the
program, not the user.

The Claude Code VS Code extension refuses this since 2.1.289-2.1.296
(PARITY-REPORT item 105). Its rule, read from the bundled extension.js of
2.1.296 (functions jD1/Py4/DD1 and the refusal messages in Bj1), is:

  - read `git config --local --includes -z --list`, and `--worktree` too;
  - refuse when that cannot be read (non-zero exit other than a missing
    file, more than 1 MiB of output, or output that is not UTF-8), and
    when .git cannot be vetted;
  - refuse `includeIf.*.path`, whose conditional loads cannot be checked;
  - refuse `lfs.customtransfer.*.path`, `lfs.[<url>.]standalonetransferagent`
    and anything under `lfs.extension.`, also when smuggled into a value
    after a newline (Git LFS reads `git config --list` line by line);
  - filter drivers are NOT refused there: it passes
    `-c filter.<name>.clean= -c filter.<name>.smudge=
    -c filter.<name>.process= -c filter.<name>.required=false` to
    `git worktree add`, and refuses only a filter name holding `=` or a
    newline.

This patch mirrors that rule in crates/git/src/worktree_config.rs, run in
create_worktree before anything is written -- not even the parent
directory -- with one deliberate difference: a filter driver with a
non-empty clean/smudge/process command is refused rather than switched
off. A checkout with its filters off writes the raw blobs (an LFS pointer
instead of the file) into a worktree an agent then edits as if it were the
real content; refusing says so instead. An empty command, which is how a
driver is switched off, is accepted.

Not screened, on purpose: `core.fsmonitor`, `core.hooksPath`,
`core.sshCommand`, `credential.helper` and `diff.external`.
GitBinary::build_command already overrides the first on every command and
the rest on every untrusted repository, `git worktree add` included, and
refusing `core.hooksPath` would refuse every husky or lefthook repository
for a setting that is already off where it matters. The extension does not
refuse them either. Its .git vetting (a .git link or gitdir file that does
not point back, a folder borrowing another repository's data) is reduced
here to "the git directory and the common config file must be readable",
checked on the filesystem before git runs, because git's discovery can walk
past an unreadable .git and answer for an enclosing repository.

The error is a typed UnsafeWorktreeConfig whose message names the key
(control characters escaped, long keys shortened) and says what to do:
move the setting to the global config if it is yours, remove it if not.
It reaches the user through the paths that already exist --
worktree_service's "git worktree create failed" toast and its View Log, and
over RPC for a remote project, whose host runs the screening.

Tests: unit tests for each refused class, the multiline smuggling case, a
benign config (a typical clone, include.path, husky hooksPath, builtin
fsmonitor, sshCommand, a switched-off filter, ordinary lfs.* keys), the
listing parser and its limits, and an unreadable .git and config; against
real git, a filter driver in .git/config and one in config.worktree,
includeIf, and an unreadable config are each refused with no directory,
branch or worktree created and the filter never run, and the settings
Zed already overrides still create a worktree.
---

diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs
index 6749bbf..c8fc62c 100644
--- a/crates/git/src/git.rs
+++ b/crates/git/src/git.rs
@@ -5,6 +5,7 @@ mod remote;
 pub mod repository;
 pub mod stash;
 pub mod status;
+pub mod worktree_config;
 
 pub use crate::hosting_provider::*;
 pub use crate::remote::*;
diff --git a/crates/git/src/repository.rs b/crates/git/src/repository.rs
index e08c001..cdc5610 100644
--- a/crates/git/src/repository.rs
+++ b/crates/git/src/repository.rs
@@ -2308,8 +2308,14 @@ impl GitRepository for RealGitRepository {
             }
         }
 
+        let git_dir = self.git_dir.clone();
+        let common_dir = self.common_dir.clone();
         self.executor
             .spawn(async move {
+                // Before anything is written: a checkout runs the programs the
+                // repository's own config names, so refuse when it names any.
+                crate::worktree_config::screen_repository_config(&git, &git_dir, &common_dir)
+                    .await?;
                 std::fs::create_dir_all(path.parent().unwrap_or(&path))?;
                 let output = git.build_command(&args).output().await?;
                 if output.status.success() {
@@ -6343,6 +6349,201 @@ mod tests {
         assert_eq!(missing, None);
     }
 
+    /// A repository with one commit and a `.gitattributes` that routes every
+    /// file through the `evil` filter, opened as a `RealGitRepository`.
+    fn repository_with_one_commit(
+        temp_dir: &Path,
+        cx: &mut TestAppContext,
+    ) -> (PathBuf, RealGitRepository) {
+        let repo_dir = temp_dir.join("repo");
+        git_init_repo(&repo_dir);
+        fs::write(repo_dir.join("file.txt"), "content").unwrap();
+        fs::write(repo_dir.join(".gitattributes"), "* filter=evil\n").unwrap();
+        git_command(&repo_dir, ["add", "file.txt", ".gitattributes"]);
+        git_command(&repo_dir, ["commit", "-m", "initial"]);
+        let repo = RealGitRepository::new(
+            &repo_dir.join(".git"),
+            None,
+            Some("git".into()),
+            cx.executor(),
+        )
+        .unwrap();
+        (repo_dir, repo)
+    }
+
+    fn new_branch_target(branch_name: &str) -> CreateWorktreeTarget {
+        CreateWorktreeTarget::NewBranch {
+            branch_name: branch_name.to_string(),
+            base_sha: Some("HEAD".to_string()),
+        }
+    }
+
+    #[gpui::test]
+    async fn test_create_worktree_refuses_a_filter_driver_and_creates_nothing(
+        cx: &mut TestAppContext,
+    ) {
+        disable_git_global_config();
+        cx.executor().allow_parking();
+
+        let temp_dir = tempfile::tempdir().unwrap();
+        let (repo_dir, repo) = repository_with_one_commit(temp_dir.path(), cx);
+        let marker = temp_dir.path().join("smudge-ran");
+        git_command(
+            &repo_dir,
+            [
+                OsString::from("config"),
+                OsString::from("filter.evil.smudge"),
+                OsString::from(format!("touch '{}'; cat", marker.display())),
+            ],
+        );
+
+        let worktrees_dir = temp_dir.path().join("worktrees");
+        let worktree_path = worktrees_dir.join("refused");
+        let error = repo
+            .create_worktree(new_branch_target("refused"), worktree_path.clone())
+            .await
+            .expect_err("a filter driver in the repository's config must refuse the worktree");
+
+        assert_eq!(
+            error.downcast_ref::<crate::worktree_config::UnsafeWorktreeConfig>(),
+            Some(
+                &crate::worktree_config::UnsafeWorktreeConfig::FilterDriver {
+                    key: "filter.evil.smudge".to_string()
+                }
+            ),
+            "{error:#}"
+        );
+        assert!(
+            error.to_string().contains("`filter.evil.smudge`"),
+            "the message names the key: {error:#}"
+        );
+        assert!(!marker.exists(), "the filter must not have run");
+        assert!(!worktrees_dir.exists(), "no directory is created");
+        assert_eq!(repo.worktrees().await.unwrap().len(), 1);
+        assert_eq!(
+            git_command_output(&repo_dir, ["branch", "--list", "refused"]),
+            "",
+            "no branch is created"
+        );
+    }
+
+    #[gpui::test]
+    async fn test_create_worktree_refuses_a_filter_in_the_per_worktree_config(
+        cx: &mut TestAppContext,
+    ) {
+        disable_git_global_config();
+        cx.executor().allow_parking();
+
+        let temp_dir = tempfile::tempdir().unwrap();
+        let (repo_dir, repo) = repository_with_one_commit(temp_dir.path(), cx);
+        git_command(&repo_dir, ["config", "extensions.worktreeConfig", "true"]);
+        git_command(
+            &repo_dir,
+            ["config", "--worktree", "filter.evil.process", "run-me"],
+        );
+
+        let worktree_path = temp_dir.path().join("worktrees").join("refused");
+        let error = repo
+            .create_worktree(new_branch_target("refused"), worktree_path.clone())
+            .await
+            .expect_err("config.worktree is screened too");
+        assert!(
+            matches!(
+                error.downcast_ref::<crate::worktree_config::UnsafeWorktreeConfig>(),
+                Some(crate::worktree_config::UnsafeWorktreeConfig::FilterDriver { .. })
+            ),
+            "{error:#}"
+        );
+        assert!(!worktree_path.exists());
+    }
+
+    #[gpui::test]
+    async fn test_create_worktree_refuses_a_conditional_include(cx: &mut TestAppContext) {
+        disable_git_global_config();
+        cx.executor().allow_parking();
+
+        let temp_dir = tempfile::tempdir().unwrap();
+        let (repo_dir, repo) = repository_with_one_commit(temp_dir.path(), cx);
+        git_command(
+            &repo_dir,
+            [
+                "config",
+                "includeIf.onbranch:main.path",
+                "elsewhere.gitconfig",
+            ],
+        );
+
+        let worktree_path = temp_dir.path().join("worktrees").join("refused");
+        let error = repo
+            .create_worktree(new_branch_target("refused"), worktree_path.clone())
+            .await
+            .expect_err("includeIf must refuse the worktree");
+        assert!(
+            matches!(
+                error.downcast_ref::<crate::worktree_config::UnsafeWorktreeConfig>(),
+                Some(crate::worktree_config::UnsafeWorktreeConfig::ConditionalInclude { .. })
+            ),
+            "{error:#}"
+        );
+        assert!(!worktree_path.exists());
+    }
+
+    #[cfg(unix)]
+    #[gpui::test]
+    async fn test_create_worktree_refuses_an_unreadable_git_config(cx: &mut TestAppContext) {
+        use std::os::unix::fs::PermissionsExt as _;
+
+        disable_git_global_config();
+        cx.executor().allow_parking();
+
+        let temp_dir = tempfile::tempdir().unwrap();
+        let (repo_dir, repo) = repository_with_one_commit(temp_dir.path(), cx);
+        let config = repo_dir.join(".git").join("config");
+        fs::set_permissions(&config, fs::Permissions::from_mode(0o000)).unwrap();
+
+        let worktree_path = temp_dir.path().join("worktrees").join("refused");
+        let result = repo
+            .create_worktree(new_branch_target("refused"), worktree_path.clone())
+            .await;
+        fs::set_permissions(&config, fs::Permissions::from_mode(0o644)).unwrap();
+
+        let error = result.expect_err("an unreadable config must refuse the worktree");
+        assert!(
+            matches!(
+                error.downcast_ref::<crate::worktree_config::UnsafeWorktreeConfig>(),
+                Some(crate::worktree_config::UnsafeWorktreeConfig::Unreadable { .. })
+            ),
+            "{error:#}"
+        );
+        assert!(!worktree_path.exists());
+    }
+
+    #[gpui::test]
+    async fn test_create_worktree_accepts_settings_zed_already_overrides(cx: &mut TestAppContext) {
+        disable_git_global_config();
+        cx.executor().allow_parking();
+
+        let temp_dir = tempfile::tempdir().unwrap();
+        let (repo_dir, repo) = repository_with_one_commit(temp_dir.path(), cx);
+        // A husky-style hooks path, the builtin fsmonitor, a per-repository SSH
+        // key and a filter switched off: none of them makes the checkout run a
+        // program the repository chose.
+        git_command(&repo_dir, ["config", "core.hooksPath", ".husky/_"]);
+        git_command(&repo_dir, ["config", "core.fsmonitor", "true"]);
+        git_command(&repo_dir, ["config", "core.sshCommand", "ssh -i /dev/null"]);
+        git_command(&repo_dir, ["config", "filter.evil.smudge", ""]);
+
+        let worktree_path = temp_dir.path().join("worktrees").join("accepted");
+        repo.create_worktree(new_branch_target("accepted"), worktree_path.clone())
+            .await
+            .unwrap();
+        assert_eq!(
+            fs::read_to_string(worktree_path.join("file.txt")).unwrap(),
+            "content"
+        );
+        assert_eq!(repo.worktrees().await.unwrap().len(), 2);
+    }
+
     #[gpui::test]
     async fn test_remove_worktree(cx: &mut TestAppContext) {
         disable_git_global_config();
diff --git a/crates/git/src/worktree_config.rs b/crates/git/src/worktree_config.rs
new file mode 100644
index 0000000..fef826b
--- /dev/null
+++ b/crates/git/src/worktree_config.rs
@@ -0,0 +1,452 @@
+//! Screens a repository's own git config before `git worktree add` runs.
+//!
+//! `git worktree add` checks out a whole tree, and a checkout runs whatever
+//! programs the repository's config names for it: filter drivers
+//! (`filter.<name>.clean`/`smudge`/`process`), and, through the `lfs` filter,
+//! Git LFS's custom transfer agents and extensions. Repository-local config is
+//! not versioned, so whoever wrote it — a tool, a script, a checkout the user
+//! does not trust — chose those programs, not the user.
+//!
+//! The rule mirrors the Claude Code VS Code extension (2.1.289–2.1.296), which
+//! refuses the same keys, plus filter drivers, which it neutralises with
+//! `-c filter.<name>.clean=` overrides rather than refusing. Refusing is the
+//! stricter choice and the one taken here: a checkout with its filters switched
+//! off writes the unfiltered blobs (an LFS pointer instead of the file) into a
+//! worktree an agent then edits as if it were the real content.
+//!
+//! Not screened, because `GitBinary::build_command` already overrides them on
+//! every command it builds: `core.fsmonitor` (always), and `core.hooksPath`,
+//! `core.sshCommand`, `credential.helper`, `diff.external` (untrusted
+//! repositories).
+
+use crate::repository::GitBinary;
+use anyhow::Result;
+use std::fmt;
+use std::path::Path;
+
+/// A git config listing larger than this is refused rather than parsed.
+const MAX_CONFIG_LISTING_BYTES: usize = 1024 * 1024;
+
+const ADVICE: &str = "If that setting is yours, move it to your global git config \
+    (`git config --global`); if you don't recognize it, remove it from the repository's \
+    config, since a tool or checkout you don't trust may have written it.";
+
+/// Why a worktree was not created. Its `Display` is the message shown to the
+/// user, and names the offending key so it can be found and removed.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum UnsafeWorktreeConfig {
+    /// The repository's git directory or config could not be read, so it
+    /// could not be checked.
+    Unreadable { detail: String },
+    /// `includeIf.<condition>.path` loads further config only under a
+    /// condition, so what it loads at checkout time cannot be checked now.
+    ConditionalInclude { key: String },
+    /// `filter.<name>.clean`, `.smudge` or `.process` names a program the
+    /// checkout would run.
+    FilterDriver { key: String },
+    /// A Git LFS setting that names a program LFS would run during checkout.
+    LfsProgram { key: String },
+}
+
+impl fmt::Display for UnsafeWorktreeConfig {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Self::Unreadable { detail } => write!(
+                f,
+                "Refused to create the worktree: this repository's git directory or config \
+                 could not be read, so it could not be checked for programs git would run \
+                 while creating the worktree ({detail}). Make the repository's .git readable \
+                 and try again."
+            ),
+            Self::ConditionalInclude { key } => write!(
+                f,
+                "Refused to create the worktree: this repository's git config sets `{}`, an \
+                 includeIf setting that can load other settings which cannot be checked for \
+                 programs git would run while creating the worktree. {ADVICE}",
+                display_key(key)
+            ),
+            Self::FilterDriver { key } => write!(
+                f,
+                "Refused to create the worktree: this repository's git config sets `{}`, a \
+                 filter driver git would run while checking out the worktree. {ADVICE}",
+                display_key(key)
+            ),
+            Self::LfsProgram { key } => write!(
+                f,
+                "Refused to create the worktree: this repository's git config sets `{}`, a \
+                 program Git LFS would run while checking out the worktree. {ADVICE}",
+                display_key(key)
+            ),
+        }
+    }
+}
+
+impl std::error::Error for UnsafeWorktreeConfig {}
+
+/// A key as read from the config, made safe to print: control characters
+/// escaped, and a very long key cut in the middle.
+fn display_key(key: &str) -> String {
+    let escaped: String = key.escape_debug().collect();
+    let chars: Vec<char> = escaped.chars().collect();
+    if chars.len() <= 100 {
+        return escaped;
+    }
+    let head: String = chars[..60].iter().collect();
+    let tail: String = chars[chars.len() - 40..].iter().collect();
+    format!("{head}…{tail}")
+}
+
+/// One `git config -z --list` entry. `value` is `None` for a key written
+/// without `=`, which git reads as boolean true.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ConfigEntry {
+    pub key: String,
+    pub value: Option<String>,
+}
+
+/// Parses the output of `git config -z --list`: entries end in NUL, and a key
+/// is separated from its value by the first newline.
+pub fn parse_config_listing(stdout: &[u8]) -> Result<Vec<ConfigEntry>, UnsafeWorktreeConfig> {
+    if stdout.len() >= MAX_CONFIG_LISTING_BYTES {
+        return Err(UnsafeWorktreeConfig::Unreadable {
+            detail: format!(
+                "the config listing is {} bytes or larger",
+                MAX_CONFIG_LISTING_BYTES
+            ),
+        });
+    }
+    let text = std::str::from_utf8(stdout).map_err(|_| UnsafeWorktreeConfig::Unreadable {
+        detail: "the config is not valid UTF-8".to_string(),
+    })?;
+    Ok(text
+        .split('\0')
+        .filter(|entry| !entry.is_empty())
+        .map(|entry| match entry.split_once('\n') {
+            Some((key, value)) => ConfigEntry {
+                key: key.to_string(),
+                value: Some(value.to_string()),
+            },
+            None => ConfigEntry {
+                key: entry.to_string(),
+                value: None,
+            },
+        })
+        .collect())
+}
+
+/// Refuses the first entry that names a program a checkout would run.
+pub fn screen_config_entries(entries: &[ConfigEntry]) -> Result<(), UnsafeWorktreeConfig> {
+    for entry in entries {
+        let key = entry.key.to_lowercase();
+
+        if key.starts_with("includeif.") && key.ends_with(".path") {
+            return Err(UnsafeWorktreeConfig::ConditionalInclude {
+                key: entry.key.clone(),
+            });
+        }
+
+        if is_lfs_program_key(&key) {
+            return Err(UnsafeWorktreeConfig::LfsProgram {
+                key: entry.key.clone(),
+            });
+        }
+
+        // Git LFS reads `git config --list` line by line, so a value holding a
+        // newline can smuggle a second `key=value` line past git's own parser.
+        if let Some(value) = &entry.value {
+            for line in value.lines().skip(1) {
+                if let Some((smuggled, _)) = line.split_once('=')
+                    && is_lfs_program_key(&smuggled.trim().to_lowercase())
+                {
+                    return Err(UnsafeWorktreeConfig::LfsProgram {
+                        key: entry.key.clone(),
+                    });
+                }
+            }
+        }
+
+        if key.starts_with("filter.")
+            && (key.ends_with(".clean") || key.ends_with(".smudge") || key.ends_with(".process"))
+            // An empty value is how a driver is switched off.
+            && entry.value.as_deref().is_some_and(|value| !value.trim().is_empty())
+        {
+            return Err(UnsafeWorktreeConfig::FilterDriver {
+                key: entry.key.clone(),
+            });
+        }
+    }
+    Ok(())
+}
+
+/// The VS Code extension's rule, as a lowercase key match:
+/// `lfs.customtransfer.<name>.path`, `lfs.[<url>.]standalonetransferagent`,
+/// and anything under `lfs.extension.`.
+fn is_lfs_program_key(key: &str) -> bool {
+    (key.starts_with("lfs.customtransfer.") && key.ends_with(".path"))
+        || (key.starts_with("lfs.")
+            && (key == "lfs.standalonetransferagent" || key.ends_with(".standalonetransferagent")))
+        || key.starts_with("lfs.extension.")
+}
+
+/// Reads the repository's local config (and its per-worktree config, when
+/// `extensions.worktreeConfig` is on), following `include.path`, and refuses
+/// anything `screen_config_entries` refuses.
+pub(crate) async fn screen_repository_config(
+    git: &GitBinary,
+    git_dir: &Path,
+    common_dir: &Path,
+) -> Result<()> {
+    check_readable(git_dir, common_dir)?;
+
+    let mut entries = read_config_scope(git, "--local").await?;
+    let worktree_config = entries.iter().any(|entry| {
+        entry.key.eq_ignore_ascii_case("extensions.worktreeconfig")
+            && entry.value.as_deref().is_none_or(|value| {
+                matches!(
+                    value.trim().to_lowercase().as_str(),
+                    "true" | "yes" | "on" | "1"
+                )
+            })
+    });
+    if worktree_config {
+        entries.extend(read_config_scope(git, "--worktree").await?);
+    }
+
+    screen_config_entries(&entries)?;
+    Ok(())
+}
+
+/// Checked on the filesystem before asking git: when `.git` cannot be read,
+/// git's discovery may walk past it to an enclosing repository and answer for
+/// that one instead.
+fn check_readable(git_dir: &Path, common_dir: &Path) -> Result<(), UnsafeWorktreeConfig> {
+    std::fs::read_dir(git_dir).map_err(|error| UnsafeWorktreeConfig::Unreadable {
+        detail: format!("{}: {error}", git_dir.display()),
+    })?;
+    let config = common_dir.join("config");
+    match std::fs::File::open(&config) {
+        Ok(_) => Ok(()),
+        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
+        Err(error) => Err(UnsafeWorktreeConfig::Unreadable {
+            detail: format!("{}: {error}", config.display()),
+        }),
+    }
+}
+
+async fn read_config_scope(git: &GitBinary, scope: &str) -> Result<Vec<ConfigEntry>> {
+    let output = git
+        .build_command(&["config", scope, "--includes", "-z", "--list"])
+        .env("LC_ALL", "C")
+        .output()
+        .await
+        .map_err(|error| UnsafeWorktreeConfig::Unreadable {
+            detail: format!("could not run git config {scope}: {error}"),
+        })?;
+    if !output.status.success() {
+        let stderr = String::from_utf8_lossy(&output.stderr);
+        // A repository without a config file has nothing to screen.
+        if stderr.contains("unable to read config file") && stderr.contains("No such file") {
+            return Ok(Vec::new());
+        }
+        return Err(UnsafeWorktreeConfig::Unreadable {
+            detail: format!("git config {scope} failed: {}", stderr.trim()),
+        }
+        .into());
+    }
+    Ok(parse_config_listing(&output.stdout)?)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    fn entry(key: &str, value: Option<&str>) -> ConfigEntry {
+        ConfigEntry {
+            key: key.to_string(),
+            value: value.map(str::to_string),
+        }
+    }
+
+    fn screen(entries: &[(&str, Option<&str>)]) -> Result<(), UnsafeWorktreeConfig> {
+        let entries: Vec<_> = entries.iter().map(|(k, v)| entry(k, *v)).collect();
+        screen_config_entries(&entries)
+    }
+
+    #[test]
+    fn test_parse_config_listing_splits_keys_values_and_valueless_keys() {
+        let listing = b"core.bare\nfalse\0user.note\nline one\nline two\0core.flag\0";
+        assert_eq!(
+            parse_config_listing(listing).unwrap(),
+            vec![
+                entry("core.bare", Some("false")),
+                entry("user.note", Some("line one\nline two")),
+                entry("core.flag", None),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_parse_config_listing_refuses_invalid_utf8_and_oversized_output() {
+        assert!(matches!(
+            parse_config_listing(b"core.bare\n\xff\0"),
+            Err(UnsafeWorktreeConfig::Unreadable { .. })
+        ));
+        let huge = vec![b'a'; MAX_CONFIG_LISTING_BYTES];
+        assert!(matches!(
+            parse_config_listing(&huge),
+            Err(UnsafeWorktreeConfig::Unreadable { .. })
+        ));
+    }
+
+    #[test]
+    fn test_benign_config_is_accepted() {
+        // A typical clone, plus the program settings `build_command` already
+        // overrides on every command it builds, plus a filter switched off.
+        assert_eq!(
+            screen(&[
+                ("core.repositoryformatversion", Some("0")),
+                ("core.filemode", Some("true")),
+                ("core.bare", Some("false")),
+                ("core.logallrefupdates", Some("true")),
+                (
+                    "remote.origin.url",
+                    Some("git@github.com:zed-industries/zed.git")
+                ),
+                (
+                    "remote.origin.fetch",
+                    Some("+refs/heads/*:refs/remotes/origin/*")
+                ),
+                ("branch.main.remote", Some("origin")),
+                ("include.path", Some("../shared.gitconfig")),
+                ("core.hookspath", Some(".husky/_")),
+                ("core.fsmonitor", Some("true")),
+                ("core.sshcommand", Some("ssh -i ~/.ssh/work")),
+                ("filter.lfs.required", Some("true")),
+                ("filter.lfs.smudge", Some("")),
+                ("lfs.url", Some("https://example.com/lfs")),
+                ("lfs.concurrenttransfers", Some("8")),
+            ]),
+            Ok(())
+        );
+    }
+
+    #[test]
+    fn test_conditional_include_is_refused() {
+        assert_eq!(
+            screen(&[("includeIf.gitdir:~/work/.path", Some("~/work.gitconfig"))]),
+            Err(UnsafeWorktreeConfig::ConditionalInclude {
+                key: "includeIf.gitdir:~/work/.path".to_string()
+            })
+        );
+        // git lowercases the section name in its listing.
+        assert!(matches!(
+            screen(&[("includeif.onbranch:main.path", Some("x"))]),
+            Err(UnsafeWorktreeConfig::ConditionalInclude { .. })
+        ));
+    }
+
+    #[test]
+    fn test_filter_drivers_are_refused() {
+        for key in [
+            "filter.lfs.smudge",
+            "filter.crypt.clean",
+            "filter.My.Driver.process",
+        ] {
+            assert_eq!(
+                screen(&[(key, Some("run-me %f"))]),
+                Err(UnsafeWorktreeConfig::FilterDriver {
+                    key: key.to_string()
+                }),
+                "{key}"
+            );
+        }
+    }
+
+    #[test]
+    fn test_lfs_programs_are_refused() {
+        for key in [
+            "lfs.customtransfer.agent.path",
+            "lfs.standalonetransferagent",
+            "lfs.https://example.com/.standalonetransferagent",
+            "lfs.extension.foo.clean",
+            "lfs.extension.foo.priority",
+        ] {
+            assert_eq!(
+                screen(&[(key, Some("x"))]),
+                Err(UnsafeWorktreeConfig::LfsProgram {
+                    key: key.to_string()
+                }),
+                "{key}"
+            );
+        }
+    }
+
+    #[test]
+    fn test_lfs_program_smuggled_in_a_multiline_value_is_refused() {
+        assert_eq!(
+            screen(&[(
+                "user.note",
+                Some("harmless\nlfs.customtransfer.evil.path=/tmp/evil")
+            )]),
+            Err(UnsafeWorktreeConfig::LfsProgram {
+                key: "user.note".to_string()
+            })
+        );
+    }
+
+    #[test]
+    fn test_message_names_the_key_and_says_what_to_do() {
+        let message = UnsafeWorktreeConfig::FilterDriver {
+            key: "filter.lfs.smudge".to_string(),
+        }
+        .to_string();
+        assert!(message.contains("`filter.lfs.smudge`"), "{message}");
+        assert!(message.contains("global git config"), "{message}");
+
+        let message = UnsafeWorktreeConfig::LfsProgram {
+            key: format!("lfs.extension.{}\u{1b}[31m.path", "x".repeat(200)),
+        }
+        .to_string();
+        assert!(
+            !message.contains('\u{1b}'),
+            "control characters are escaped"
+        );
+        assert!(message.contains('…'), "a long key is shortened");
+    }
+
+    #[cfg(unix)]
+    #[test]
+    fn test_unreadable_git_directory_is_refused() {
+        use std::os::unix::fs::PermissionsExt as _;
+
+        let dir = tempfile::tempdir().unwrap();
+        let git_dir = dir.path().join(".git");
+        std::fs::create_dir(&git_dir).unwrap();
+        std::fs::write(git_dir.join("config"), "[core]\n\tbare = false\n").unwrap();
+        assert_eq!(check_readable(&git_dir, &git_dir), Ok(()));
+
+        std::fs::set_permissions(
+            git_dir.join("config"),
+            std::fs::Permissions::from_mode(0o000),
+        )
+        .unwrap();
+        let refused = check_readable(&git_dir, &git_dir);
+        std::fs::set_permissions(
+            git_dir.join("config"),
+            std::fs::Permissions::from_mode(0o644),
+        )
+        .unwrap();
+        assert!(
+            matches!(refused, Err(UnsafeWorktreeConfig::Unreadable { .. })),
+            "{refused:?}"
+        );
+
+        std::fs::set_permissions(&git_dir, std::fs::Permissions::from_mode(0o000)).unwrap();
+        let refused = check_readable(&git_dir, &git_dir);
+        std::fs::set_permissions(&git_dir, std::fs::Permissions::from_mode(0o755)).unwrap();
+        assert!(
+            matches!(refused, Err(UnsafeWorktreeConfig::Unreadable { .. })),
+            "{refused:?}"
+        );
+    }
+}
