gitoxide icon indicating copy to clipboard operation
gitoxide copied to clipboard

Interaction between 'safe.directory' and trust level and remotes.

Open TyberiusPrime opened this issue 8 months ago • 3 comments

Current behavior 😯

(version 0.70.0)

When a .git is not owned by the current user, but is listed in safe.directories, open(repo).remote_names() returns an empty list.

bail_if_untrusted(true) does not seem to make a difference.

See https://github.com/jj-vcs/jj/issues/6155

Expected behavior 🤔

If the directory is in safe.directories, I'd expect it to be trusted. Or, if gitoxide doesn't read safe.directories, I'd expect it to fail if bail_if_untrusted is set.

Git behavior

Git lists the remotes in both cases.

Steps to reproduce 🕹

  1. git init owned; cd owned; git remote add origin https://github.com/GitoxideLabs/gitoxide; cd ..
  2. git init non_owned; cd non_owned; git remote add origin https://github.com/GitoxideLabs/gitoxide; cd ..; sudo chown -R root non_owned
fn main() {
    println!("using owned repo");
    {
        let repo_owned = gix::open("owned").expect("could not opon owned repo");
        let rn = repo_owned.remote_names();
        println!("found {} remotes", rn.len());
        for remote in rn {
            println!("remote: {}", remote);
        }
    }

    println!("\n\n");
    {
        println!("using non-owned repo, which is in .gitconfig/safe.directories");
        let repo_non_owned = gix::open("non_owned").expect("could not opon non-owned repo");
        let rn = repo_non_owned.remote_names();
        println!("found {} remotes", rn.len());
        for remote in rn {
            println!("remote: {}", remote);
        }
    }

    println!("\n\n");
    {
        println!(
            "using non-owned repo, which is in .gitconfig/safe.directories, + bail_if_untrusted"
        );
        let repo_non_owned =
            gix::open_opts("non_owned", gix::open::Options::default().bail_if_untrusted(true))
                .expect("could not opon non-owned repo wit bail-if-untrused");
        let rn = repo_non_owned.remote_names();
        println!("found {} remotes", rn.len());
        for remote in rn {
            println!("remote: {}", remote);
        }
    }
}

TyberiusPrime avatar Mar 28 '25 07:03 TyberiusPrime

Thanks a lot for bringing this to my attention.

Interestingly just a couple of days ago I thought about this for no apparent reason and I wondered the same - does safeDirectories affect the current implementation in terms of trust?

And even though something is implemented in that regard (I'd have to check for details), I don't think that configuration files are affected enough, or it's a bug.

I put this on my list, but it's long and any help is appreciated.

Byron avatar Mar 29 '25 00:03 Byron

Even though this is fixed, I'd really like some help with figuring out a way to test this, on any platform. Thus far I made the fix with manual tests only, so a regression is absolutely possible right now.

Byron avatar Apr 21 '25 14:04 Byron

On another note, and something that might require a follow-up: configuration is only marked as safe if either one uses prefix-based directory specifications , like /this/is/safe/*, or if one provides the path to the configuration file in addition to the Git directory, as in [safe] directory = /path/to/.git and [safe] directory = /path/to/.git/config.

The latter is probably unintuitive if one wants to be euphemistic, or a bug if one wants to be realistic. And now that I have written this it's hard to not fix it immediately. Now that I see this code I also realize that the implementation is currently incorrect compared to what Git does.

Byron avatar Apr 21 '25 16:04 Byron