← all lessons

example-infra · 2026-08-09 · crondeploymentgitsecurityunix-permissions

git safe.directory

The idea

Since version 2.35.2, git refuses to run in a repository whose .git directory is owned by a different user than the one running the command. It stops with "detected dubious ownership". The reason: a repository can run code. Config keys like core.fsmonitor and hooks run as you, so a repo you don't own is one somebody else could use to run commands as you. Rather than guess whether that's fine, git makes you say so by listing the path in the safe.directory config.

The check looks at the owner's UID only. Being in the owning group, or having full read and write access, makes no difference — permissions and ownership are separate questions, and git is asking about ownership.

How it shows up

Any time one account works on files owned by another: a deploy script your user runs against a web root owned by the server user, a container mounting a host volume, or sudo git in someone else's home directory.

$ git fetch
fatal: detected dubious ownership in repository at '/var/www/example.com'

$ git config --global --add safe.directory /var/www/example.com
$ git fetch          # works

The failure is loud in a terminal and invisible from cron. A deploy script with set -e and git fetch near the top exits non-zero having done nothing, so the site quietly stops updating while every check still says the cron ran.

Read more

Exercises

  1. Reproduce the refusal — make a repo owned by another user (sudo mkdir /tmp/other && sudo git init /tmp/other), then run git -C /tmp/other status as yourself. Done when: you see the "dubious ownership" error naming that path.
  2. Prove groups don't helpsudo chgrp $(id -gn) /tmp/other/.git and sudo chmod -R g+rwX /tmp/other, then run git -C /tmp/other status again. Done when: you can write into the repo but git still refuses, showing the check is about the owner UID, not access.
  3. Trust it, then inspect the record — add the path with git config --global --add safe.directory /tmp/other, confirm the command works, then run git config --global --get-all safe.directory. Done when: the command succeeds and you can see the entry listed, so you know where the trust decision is written down and how to revoke it.

My notes