Skip to content

safe.bareRepository: default to "explicit" with WITH_BREAKING_CHANGES#2072

Open
dscho wants to merge 46 commits intogitgitgadget:masterfrom
dscho:make-safe.bareRepositories-the-default-in-git-3.0
Open

safe.bareRepository: default to "explicit" with WITH_BREAKING_CHANGES#2072
dscho wants to merge 46 commits intogitgitgadget:masterfrom
dscho:make-safe.bareRepositories-the-default-in-git-3.0

Conversation

@dscho
Copy link
Copy Markdown
Member

@dscho dscho commented Mar 24, 2026

In one of my projects, I work exclusively on bare repositories. During one of the tests, I noticed that safe.bareRepository is not yet enabled by default. This strikes me as undesirable, and I deem Git v3.0 the most logical opportunity to change the default.

Cc: Patrick Steinhardt ps@pks.im

@dscho dscho self-assigned this Mar 24, 2026
@dscho dscho force-pushed the make-safe.bareRepositories-the-default-in-git-3.0 branch from 54b6c08 to a30a801 Compare March 27, 2026 17:31
@gitgitgadget
Copy link
Copy Markdown

gitgitgadget bot commented Mar 27, 2026

There are issues in commit 8365979:
infra: test-lib-functions and test-tool --git-dir

  • Commit checks stopped - the message is too short
  • Commit not signed off

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget bot commented Mar 27, 2026

There are issues in commit 9d4fafe:
category-H

  • Commit checks stopped - the message is too short
  • Commit not signed off

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget bot commented Mar 27, 2026

There are issues in commit 9d973bb:
category-L

  • Commit checks stopped - the message is too short
  • Commit not signed off

dscho added 25 commits March 31, 2026 11:21
8d1a744 (setup.c: create `safe.bareRepository`, 2022-07-14)
introduced a setting to restrict implicit bare repository discovery,
mitigating a social-engineering attack where an embedded bare repo's
hooks get executed unknowingly. To allow for that default to change
at some stage in the future, the tests need to be prepared.

This test runs `git aliasedinit` from inside a bare repo to verify
that aliased commands work there. The test is about alias resolution,
not bare repo discovery, so add `test_config_global
safe.bareRepository all` to opt in explicitly.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The bare repo tests in t0003-attributes.sh currently `cd` into the
bare repository inside subshells, relying on implicit discovery.
Restructure these tests to pass `--git-dir=bare.git` to the
`attr_check` and `attr_check_source` helpers instead. This makes the
bare repo access explicit, compatible with
`safe.bareRepository=explicit`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `git -C c/a.git` invocations in t0056-git-C.sh enter what is
technically the `.git` directory of a repository (not a standalone bare
repo) to test `-C` combined with `--work-tree`. With
`safe.bareRepository=explicit`, Git sees a bare-looking directory during
discovery and rejects it.

Add `--git-dir=.` to these commands so Git treats the directory as an
explicitly-specified git dir rather than attempting implicit discovery.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Replace `(cd foo.git && git show -s HEAD)` with
`git --git-dir=foo.git show -s HEAD`. The subshell pattern relies on
implicit bare repo discovery, which fails with
`safe.bareRepository=explicit`. Using `--git-dir` is actually simpler.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `test_repo_info` helper hard-codes `-C` to access the repository
under test. Add an optional 6th parameter `repo_flag` that defaults to
`-C`. The bare repository test now passes `--git-dir` as that
parameter, making the access explicit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `worktree repair` commands need to run from inside the bare
repository directory. Use `(cd bare.git && GIT_DIR=. && export GIT_DIR
&& ...)` subshells to preserve the working directory while making bare
repo access explicit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
`test_commit_bulk -C` does not work with bare repositories because it
assumes a working tree. Use `(GIT_DIR="$REPO" && export GIT_DIR &&
test_commit_bulk ...)` to sidestep this and make bare repo access
explicit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
A loop iterated over both a bare (`cloned`) and a non-bare
(`unbundled`) repository with the same `-C` flag. Split into
`for opt in "--git-dir cloned" "-C unbundled"` so each gets the right
flag.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `--work-tree=.` test must run from inside the bare repository
because it uses a relative work-tree path. Wrap in a subshell that
exports `GIT_DIR=.` to make the bare repo access explicit.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This test expects `scalar register` to discover a bare repo and
reject it. Since `scalar` does not support `--git-dir`, pass
`-c safe.bareRepository=all` to opt in, so the test keeps
working once the default changes to `explicit`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `check_obj` call uses a relative alternate path resolved from
`$GIT_DIR`, so it must run from inside the bare repo. Wrap in
`(cd bare.git && GIT_DIR=. && export GIT_DIR && ...)`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `test_hook` helper installs hook scripts into a repository. It
currently only supports `-C <dir>` to locate the target, which triggers
implicit bare repository discovery and would fail under
`safe.bareRepository=explicit`.

Add a `--git-dir <dir>` option so that the helper can locate bare
repositories explicitly. When given, the internal `git rev-parse
--absolute-git-dir` call that resolves the hooks directory uses
`--git-dir=<dir>` instead of `-C <dir>`.

See 8d1a744 (setup.c: create `safe.bareRepository`, 2022-07-14)
for the background on implicit bare repository discovery and why it
may become the default to reject it.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
8d1a744 (setup.c: create `safe.bareRepository`, 2022-07-14)
introduced the `safe.bareRepository` config setting that can reject
implicit bare repository discovery. It defaults to `all` for now, but
the commit message already describes the social engineering attack that
motivates changing that default to `explicit` at some point.

When that day comes, any `test-tool -C <bare-repo>` invocation will
trigger implicit discovery and be refused. Add a `--git-dir=<path>`
option that works the same way `git --git-dir=<path>` does: it calls
`setenv(GIT_DIR_ENVIRONMENT, ...)` before dispatching to the
subcommand, bypassing repository discovery entirely.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
When `safe.bareRepository` defaults to `explicit`, bare repositories
can no longer be discovered implicitly via directory walking. The
commit-graph helpers `graph_git_behavior` and `graph_read_expect` in
lib-commit-graph.sh currently use `-C $DIR` to enter the bare
repository, which triggers implicit discovery.

Teach both helpers to accept a `--bare` flag. When set,
`graph_git_behavior` passes `--git-dir=$DIR` instead of `-C $DIR`, and
`graph_read_expect` sets `GIT_DIR=. && export GIT_DIR` inside the
subshell that runs `test-tool read-graph`.

See 8d1a744 (setup.c: create `safe.bareRepository`, 2022-07-14)
for the background on why implicit bare repo discovery may be rejected.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
In the spirit of 8d1a744 (setup.c: create `safe.bareRepository`,
2022-07-14), Git's test suite should be prepared for a potential default
change of that setting from `all` to `explicit`. The `test_config`
and `test_unconfig` helpers use `git -C <dir> config` under the hood,
and that triggers implicit bare repository discovery when <dir> is a
bare repository.

Add a `--git-dir` option alongside the existing `-C` option. When
specified, the helpers pass `--git-dir=<dir>` instead of `-C <dir>` to
the underlying `git config` call, and the `test_when_finished` cleanup
registered by `test_config` uses `test_unconfig --git-dir` accordingly.
This ensures that both the set and the unset paths bypass discovery.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `post_checkout_hook` helper hard-codes `test_hook -C "$1"` to
install the hook. Change it to `test_hook "$@"` so callers can pass
`--git-dir <dir>` for bare repos.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add `--git-dir` option parsing to `test_cmp_refs` in
t5411/common-functions.sh, parallel to the existing `-C` handling. When
given, the helper passes `--git-dir="$gitdir"` to `git show-ref`
instead of `-C "$indir"`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `print_all_reflog_entries` and `test_migration` helpers in
t1460-refs-migrate.sh previously hard-coded `-C` to enter the
repository. With `safe.bareRepository=explicit`, bare repos accessed via
`-C` are rejected.

Teach both helpers to accept `--git-dir` as the first argument, and
pass the chosen flag through to both `git` and `test-tool` invocations.
Callers testing bare repos now pass `--git-dir`, while non-bare callers
continue to use `-C`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
To prepare for `safe.bareRepository` defaulting to `explicit`,
pass `--bare` at all bare-repo call sites in t5318 so the helpers
use `--git-dir` instead of `-C`.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Replace `test_config -C` with `test_config --git-dir` (24 matches, 7 files).

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: --git-dir -> -C
        gsub(/test_config --git-dir /, "test_config -C ", new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Replace `test_hook ... -C` with `test_hook ... --git-dir` (78 matches, 32 files).

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: --git-dir -> -C
        gsub(/ --git-dir /, " -C ", new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Replace `test_cmp_refs -C` with `test_cmp_refs --git-dir` (66 matches, 30 files).

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: --git-dir -> -C
        gsub(/test_cmp_refs --git-dir /, "test_cmp_refs -C ", new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
dscho added 21 commits March 31, 2026 12:15
To prepare for `safe.bareRepository` defaulting to `explicit`,
replace `git -C ..` with `git --git-dir=../.git` to access
the parent repository explicitly.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Switch bare-repo caller to `post_checkout_hook --git-dir bare`.

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: --git-dir -> (nothing)
        gsub(/post_checkout_hook --git-dir /, "post_checkout_hook ", new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
An earlier commit replaced `-C bare.git` with
`--git-dir=bare.git`. Since `--git-dir` does not change the
working directory, the `../` prefix on a `--filter-to` path
is no longer needed.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
To prepare for `safe.bareRepository` defaulting to `explicit`,
set and export `GIT_DIR` after entering the bare repo so
subsequent commands do not rely on implicit discovery.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
To prepare for `safe.bareRepository` defaulting to `explicit` (see
8d1a744), prefix bare-repo `git worktree add` invocations
with an inline `GIT_DIR=.` so Git does not rely on implicit bare
repository discovery.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
To prepare for `safe.bareRepository` defaulting to `explicit`
(see 8d1a744), export `GIT_DIR=.` right after
`git init --bare &&` so subsequent commands access the bare
repo explicitly.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
To prepare for `safe.bareRepository` defaulting to `explicit`
(see 8d1a744), replace `cd <dir> && git config` with
`git --git-dir=<dir> config` so the helper does not rely on
implicit bare repository discovery.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Earlier commits replaced `-C <dir>` with `--git-dir=<dir>` for
bare repository access. Since `--git-dir` does not change the
working directory the way `-C` does, paths that duplicated the
directory name as a prefix are now redundant.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
After switching from `-C pushee` to `--git-dir=pushee`, `ext::`
URLs that used `.` (resolved relative to the `-C` target) must
spell out the directory name explicitly.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Earlier commits replaced `-C <dir>` with `--git-dir=<dir>`.
Since `--git-dir` does not change the working directory, a `.`
that formerly referred to the `-C` target must become `..` to
point at the same location.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Earlier commits export `GIT_DIR` when entering bare repositories.
When the test leaves that context via `cd`, the stale `GIT_DIR`
must be cleaned up to avoid confusing subsequent commands.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Export `GIT_CONFIG_PARAMETERS` with `safe.bareRepository=all` (12 matches).

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: strip GIT_CONFIG_PARAMETERS export
        gsub(/ export GIT_CONFIG_PARAMETERS=.* &&/, "", new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Adjust paths after switching from `-C` to `--git-dir` (20 matches, 10 files).

Mechanical verification (the awk script applies the
transformation on the old lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ {
        line = substr($0,2)
        # apply: -C X -> --git-dir=X and strip ../
        if (match(line, /-C ([^ ]*)/, a)) gsub(/-C [^ ]*/, "--git-dir=" a[1], line); gsub(/\.\.\//, "", line)
        old[m++] = line
      }
      /^\+/ { new = substr($0,2); if (old[n++] != new) print old[n-1] " != " new }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Add `GIT_DIR=. && export GIT_DIR &&` after `cd <bare> &&` (105 matches, 45 files).

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: strip GIT_DIR export
        gsub(/ GIT_DIR=[.] && export GIT_DIR &&/, "", new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Replace `git -C <bare>` with `git --git-dir=<bare>` (367 matches, 73 files).

Mechanical verification (the awk script undoes the
transformation on the new lines; empty output is success):

    git diff HEAD^! | awk '
      /^diff/,/^\+\+\+/ { next }
      /^-/ { old[m++] = substr($0,2) }
      /^\+/ {
        new = substr($0,2)
        # undo: --git-dir=X -> -C X
        if (match(new, /--git-dir=([^ ]*)/, a)) gsub(/--git-dir=[^ ]*/, "-C " a[1], new)
        if (old[n++] != new) print old[n-1] " != " new
      }'

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
When an attacker can convince a user to clone a crafted repository that
contains an embedded bare repository with malicious hooks, any Git
command the user runs after entering that subdirectory will discover the
bare repository and execute the hooks. The user does not even need to run
a Git command explicitly: many shell prompts run `git status` in the
background to display branch and dirty state information, and `git
status` in turn may invoke the fsmonitor hook if so configured, making
the user vulnerable the moment they `cd` into the directory. The
safe.bareRepository configuration variable (introduced in 8959555
(setup_git_directory(): add an owner check for the top-level directory,
2022-03-02)) already provides protection against this attack vector by
allowing users to set it to "explicit", but the default remained "all"
for backwards compatibility.

Since Git 3.0 is the natural point to change defaults to safer values,
flip the default from "all" to "explicit" when built with
WITH_BREAKING_CHANGES. This means Git will refuse to work with bare
repositories that are discovered implicitly by walking up the directory
tree. Bare repositories specified via --git-dir or GIT_DIR continue to
work, and directories that look like .git, worktrees, or submodule
directories are unaffected (the existing is_implicit_bare_repo()
whitelist handles those cases).

Users who rely on implicit bare repository discovery can restore the
previous behavior by setting safe.bareRepository=all in their global or
system configuration.

The test for the "safe.bareRepository in the repository" scenario needed
a more involved fix: it writes a safe.bareRepository=all entry into the
bare repository's own config to verify that repo-local config does not
override the protected (global) setting. Previously, test_config -C was
used to write that entry, but its cleanup runs git -C <bare-repo> config
--unset, which itself fails when the default is "explicit" and the
global config has already been cleaned up. Switching to direct git config
--file access avoids going through repository discovery entirely.

Assisted-by: Claude Opus 4.6
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho dscho force-pushed the make-safe.bareRepositories-the-default-in-git-3.0 branch from a30a801 to 56cd924 Compare March 31, 2026 15:36
@gitgitgadget
Copy link
Copy Markdown

gitgitgadget bot commented Mar 31, 2026

The pull request has 46 commits. The max allowed is 30. Please split the patch series into multiple pull requests. Also consider squashing related commits.

@gitgitgadget
Copy link
Copy Markdown

gitgitgadget bot commented Mar 31, 2026

There are merge commits in this Pull Request:

ba5445cb20655869b2b744bd1b89aeaa6af3b8e3
e956f0d154c8d3b77a28d993dbd7562ed5bfe490
f14f6f9b60d620526b608ab2a0c79a0a61153ceb
865f2382894d58d8e242cd31d635d5e0cdd2bfb3
cc4055b1baa340f53c478c495ee25cb10bcec571
abc24c6ba7ba294bfcf38ace54e37fe4b3d96ea7
2ea73fd8150c6456032d96231bb7933dd9461ea5

Please rebase the branch and force-push.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant