From: Michael Lohmann <git@lohmann.sh>
To: git@vger.kernel.org
Cc: Michael Lohmann <git@lohmann.sh>
Subject: [PATCH 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`
Date: Mon, 13 Oct 2025 11:41:46 +0200 [thread overview]
Message-ID: <20251013094152.23597-6-git@lohmann.sh> (raw)
In-Reply-To: <20251013094152.23597-1-git@lohmann.sh>
Git considers all repositories as safe, if they are either
- explicitly set in "safe.directory" config, or
- the user owns the repo
Since a user could unzip a folder they downloaded from the internet and
unknown to them, it is a repository with malicious hooks/config, an
attacker could easily get code execution. Even a command line prompt
would automatically trigger this if executing `git status` after
entering the malicious directory.
Allow not to automatically treat all repos owned by the user as safe.
This can either be done by "--assume-unsafe", the environment variable
"GIT_ASSUME_UNSAFE" or by setting the configuration "safe.assumeUnsafe"
in a safe context (so not the repo config, as it should not be able to
allow list itself).
Signed-off-by: Michael Lohmann <git@lohmann.sh>
---
Question in setup.c: is setting the environment variable inside of
safe_directory_cb the best way to "communicate" this result?
Alternatively one could add a new member to the struct, but I thought
this was not the best either...
Documentation/config/safe.adoc | 9 +++++++
Documentation/git.adoc | 14 ++++++++++-
environment.h | 1 +
git.c | 6 ++++-
setup.c | 9 +++++++
t/t0036-allow-unsafe-directory.sh | 42 +++++++++++++++++++++++++++++++
6 files changed, 79 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc
index 2d45c98b12..2ac5d94762 100644
--- a/Documentation/config/safe.adoc
+++ b/Documentation/config/safe.adoc
@@ -60,3 +60,12 @@ which id the original user has.
If that is not what you would prefer and want git to only trust
repositories that are owned by root instead, then you can remove
the `SUDO_UID` variable from root's environment before invoking git.
+
+safe.assumeUnsafe::
+ Boolean to indicate that the ownership of a repository should not
+ be taken into account when checking if the repository is safe. It
+ will prevent against accidental arbitrariy code execution
++
+To temporarily allow git execution in case of an assumed unsafe repository,
+run the command with `--allow-unsafe`. To permanently trust this path, add
+it to the `safe.directory` config.
diff --git a/Documentation/git.adoc b/Documentation/git.adoc
index 7df51c38f9..162350f3db 100644
--- a/Documentation/git.adoc
+++ b/Documentation/git.adoc
@@ -14,7 +14,7 @@ SYNOPSIS
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]
[--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]
[--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]
- [--allow-unsafe]
+ [--allow-unsafe] [--assume-unsafe]
<command> [<args>]
DESCRIPTION
@@ -238,6 +238,13 @@ If you just want to run git as if it was started in `<path>` then use
execution by hooks or configuration settings. Equivalent to setting
the environment variable `GIT_ALLOW_UNSAFE=1`.
+--assume-unsafe::
+ Prevent arbitrary code execution by hooks or configuration if not
+ executed in a "safe.directory". With setting this, filesystem ownership
+ of the repository in question no longer satisfies to mark it as safe.
+ Equivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overwritten if
+ `--allow-unsafe` is passed as well.
+
GIT COMMANDS
------------
@@ -506,6 +513,11 @@ Git so take care if using a foreign front-end.
owns the repository before potentially executing arbitrary code
from hooks or config.
+`GIT_ASSUME_UNSAFE`::
+ This Boolean environment variable can be set to true enforce
+ explicit "safe.directory" configuration for the repository. This
+ can be overwritten by setting `GIT_ALLOW_UNSAFE`.
+
`GIT_INDEX_FILE`::
This environment variable specifies an alternate
index file. If not specified, the default of `$GIT_DIR/index`
diff --git a/environment.h b/environment.h
index ee9e1b9514..89036a9460 100644
--- a/environment.h
+++ b/environment.h
@@ -43,6 +43,7 @@
#define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR"
#define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
#define GIT_ALLOW_UNSAFE "GIT_ALLOW_UNSAFE"
+#define GIT_ASSUME_UNSAFE "GIT_ASSUME_UNSAFE"
/*
* Environment variable used to propagate the --no-advice global option to the
diff --git a/git.c b/git.c
index a7581a6805..40ef89558d 100644
--- a/git.c
+++ b/git.c
@@ -42,7 +42,7 @@ const char git_usage_string[] =
" [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\n"
" [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\n"
" [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\n"
- " [--allow-unsafe]\n"
+ " [--allow-unsafe] [--assume-unsafe]\n"
" <command> [<args>]");
const char git_more_info_string[] =
@@ -359,6 +359,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
setenv(GIT_ALLOW_UNSAFE, "1", 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--assume-unsafe")) {
+ setenv(GIT_ASSUME_UNSAFE, "1", 1);
+ if (envchanged)
+ *envchanged = 1;
} else {
fprintf(stderr, _("unknown option: %s\n"), cmd);
usage(git_usage_string);
diff --git a/setup.c b/setup.c
index 10975fd9a3..0d6cddfcb9 100644
--- a/setup.c
+++ b/setup.c
@@ -1238,6 +1238,12 @@ static int safe_directory_cb(const char *key, const char *value,
{
struct safe_directory_data *data = d;
+ if (!strcmp(key, "safe.assumeunsafe")) {
+ if (git_config_bool(key, value))
+ setenv(GIT_ASSUME_UNSAFE, value, 0);
+ return 0;
+ }
+
if (strcmp(key, "safe.directory"))
return 0;
@@ -1330,6 +1336,9 @@ static int ensure_safe_repository(const char *gitfile,
if (data.is_safe)
return 1;
+ if (git_env_bool("GIT_ASSUME_UNSAFE", 0))
+ return 0;
+
if (!git_env_bool("GIT_TEST_ASSUME_DIFFERENT_OWNER", 0) &&
(!gitfile || is_path_owned_by_current_user(gitfile, report)) &&
(!worktree || is_path_owned_by_current_user(worktree, report)) &&
diff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh
index 4b98e815ff..7e08c261bc 100755
--- a/t/t0036-allow-unsafe-directory.sh
+++ b/t/t0036-allow-unsafe-directory.sh
@@ -25,4 +25,46 @@ test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '
git status
'
+test_expect_success '--assume-unsafe prevents execution if not in safe.directory' '
+ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
+ git status &&
+ test_must_fail git --assume-unsafe status 2>err &&
+ grep "dubious ownership" err
+'
+test_expect_success 'GIT_ASSUME_UNSAFE prevents execution if not in safe.directory' '
+ test_must_fail env GIT_ASSUME_UNSAFE=1 \
+ git status 2>err &&
+ grep "dubious ownership" err
+'
+
+test_expect_success 'safe.assumeUnsafe on the command line' '
+ test_must_fail git -c safe.assumeUnsafe="true" status 2>err &&
+ grep "dubious ownership" err
+'
+
+test_expect_success 'safe.assumeUnsafe in the environment' '
+ test_must_fail env GIT_CONFIG_COUNT=1 \
+ GIT_CONFIG_KEY_0="safe.assumeUnsafe" \
+ GIT_CONFIG_VALUE_0="true" \
+ git status 2>err &&
+ grep "dubious ownership" err
+'
+
+test_expect_success 'safe.assumeUnsafe in GIT_CONFIG_PARAMETERS' '
+ test_must_fail env GIT_CONFIG_PARAMETERS="${SQ}safe.assumeUnsafe${SQ}=${SQ}true${SQ}" \
+ git status 2>err &&
+ grep "dubious ownership" err
+'
+
+test_expect_success 'ignoring safe.assumeUnsafe in repo config' '
+ git config safe.assumeUnsafe "false" &&
+ git config --global safe.assumeUnsafe "true" &&
+ test_must_fail git status 2>err &&
+ grep "dubious ownership" err
+'
+
+test_expect_success 'allow-unsafe must overwrite assume-unsafe' '
+ env GIT_ASSUME_UNSAFE=1 git --allow-unsafe status
+'
+
test_done
--
2.50.1 (Apple Git-155)
next prev parent reply other threads:[~2025-10-13 9:42 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-13 9:41 [PATCH 0/5] Allow enforcing safe.directory Michael Lohmann
2025-10-13 9:41 ` [PATCH 1/5] setup: rename `ensure_safe_repository()` for clarity Michael Lohmann
2025-10-13 9:41 ` [PATCH 2/5] setup: rename `die_upon_assumed_unsafe_repo()` to align with check Michael Lohmann
2025-10-14 20:16 ` Junio C Hamano
2025-10-13 9:41 ` [PATCH 3/5] setup: refactor `ensure_safe_repository()` testing priorities Michael Lohmann
2025-10-14 20:32 ` Junio C Hamano
2025-10-13 9:41 ` [PATCH 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks Michael Lohmann
2025-10-13 9:41 ` Michael Lohmann [this message]
2025-10-13 11:59 ` [PATCH 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()` D. Ben Knoble
2025-10-13 21:46 ` [PATCH v2 0/5] Apply comments of D. Ben Knoble Michael Lohmann
2025-10-13 21:46 ` [PATCH v2 1/5] setup: rename `ensure_safe_repository()` for clarity Michael Lohmann
2025-10-13 21:46 ` [PATCH v2 2/5] setup: rename `die_upon_assumed_unsafe_repo()` to align with check Michael Lohmann
2025-10-13 21:46 ` [PATCH v2 3/5] setup: refactor `ensure_safe_repository()` testing priorities Michael Lohmann
2025-10-13 21:46 ` [PATCH v2 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks Michael Lohmann
2025-10-13 21:46 ` [PATCH v2 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()` Michael Lohmann
2025-10-16 5:33 ` [PATCH v3 0/5] Allow skipping ownership of repo in safety consideration Michael Lohmann
2025-10-16 5:33 ` [PATCH v3 1/5] setup: rename `ensure_safe_repository()` for clarity Michael Lohmann
2025-10-16 5:33 ` [PATCH v3 2/5] setup: rename `die_upon_unsafe_repo()` to align with check Michael Lohmann
2025-10-16 5:33 ` [PATCH v3 3/5] setup: refactor `ensure_safe_repository()` testing priorities Michael Lohmann
2025-10-16 5:33 ` [PATCH v3 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks Michael Lohmann
2025-10-16 19:26 ` Junio C Hamano
2025-10-16 5:33 ` [PATCH v3 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()` Michael Lohmann
2025-10-16 19:33 ` Junio C Hamano
2025-10-16 19:58 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251013094152.23597-6-git@lohmann.sh \
--to=git@lohmann.sh \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).