{"thread":{"id":"64302","subject":"[PATCH 0/5] Allow enforcing safe.directory","startedAt":"2025-10-13T09:42:31Z","lastAt":"2025-10-16T19:58:42Z","messageCount":24,"participants":["Michael Lohmann","D. Ben Knoble","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":5},"messages":[{"id":"528605","messageId":"20251013094152.23597-1-git@lohmann.sh","threadId":"64302","inReplyTo":null,"subject":"[PATCH 0/5] Allow enforcing safe.directory","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T09:41:41Z","receivedAt":"2025-10-13T09:42:31Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"Hey everyone!\n\nAs a first step to allow making git more resistant against accidental\narbitrary code execution, Jeff King suggested in\n\n https://lore.kernel.org/git/20251009224317.77565-1-git@lohmann.sh/T/#m6cce96f9ae58a4341ae3fbbc02110e20547c58bc\n\nto make the \"safe.directory\" config enforcable.\nIf a user has a command line status like:\n\n```bash\n# Let's assume the simplest command status prompt that shows \"(+)\" if\n# there are uncommitted changes:\nexport PS1='$(if [ -n \"$(git status --short 2>/dev/null)\" ]; then; echo \"(+)\"; fi)> '\n\n# You download a random zip folder from the internet, not knowing it is\n# actually a repo:\ncurl --silent https://www.lohmann.sh/nuggits/002-dangerous-git/malicious.zip --output malicious.zip\n# unzipping means the folder is owned by the user, so by default git\n# assumes it is safe to execute hooks/config\nunzip malicious.zip >/dev/null\n\necho 'Just a \"README\" no \"xxx\" file, see:'\nls malicious\n\n# This `cd` now triggers arbitrary code execution due to `git status`:\ncd malicious\n# now there is an \"xxx\" file\n```\n\nWith this feature, the prompt could either perform\n`git --assume-unsafe status` or to make all git invocations by any\nprograms safe against accidental arbitrary code invocations a user could\nset \"safe.assumeUnsafe\" to true.\n\nAlso allow to temporarily bypass this check with a new `--allow-unsafe`\nflag.\n\n--Michael\n\nMichael Lohmann (5):\n  setup: rename `ensure_safe_repository()` for clarity\n  setup: rename `die_upon_assumed_unsafe_repo()` to align with check\n  setup: refactor `ensure_safe_repository()` testing priorities\n  setup: allow temporary bypass of `ensure_safe_repository()` checks\n  setup: allow not marking self owned repos as safe in\n    `ensure_safe_repository()`\n\n Documentation/config/safe.adoc    |  9 ++++\n Documentation/git.adoc            | 25 +++++++++++\n builtin/clone.c                   |  2 +-\n environment.h                     |  2 +\n git.c                             |  9 ++++\n path.c                            |  4 +-\n setup.c                           | 45 ++++++++++++++------\n setup.h                           |  2 +-\n t/meson.build                     |  1 +\n t/t0036-allow-unsafe-directory.sh | 70 +++++++++++++++++++++++++++++++\n 10 files changed, 153 insertions(+), 16 deletions(-)\n create mode 100755 t/t0036-allow-unsafe-directory.sh\n\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528606","messageId":"20251013094152.23597-2-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013094152.23597-1-git@lohmann.sh","subject":"[PATCH 1/5] setup: rename `ensure_safe_repository()` for clarity","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T09:41:42Z","receivedAt":"2025-10-13T09:42:39Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"In addition to ownership it checks for \"safe.directory\" config, making\nthe name `ensure_valid_ownership()` not expressive. This function\nensures that a repository is considered to be safe.\nWhen additional options to check if a repository is considered to be\nsafe are added, this name is more indicative of the content.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n setup.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 7086741e6c..2c41874774 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1301,7 +1301,7 @@ static int safe_directory_cb(const char *key, const char *value,\n  * config settings; for non-bare repositories, their worktree needs to be\n  * added, for bare ones their git directory.\n  */\n-static int ensure_valid_ownership(const char *gitfile,\n+static int ensure_safe_repository(const char *gitfile,\n \t\t\t\t  const char *worktree, const char *gitdir,\n \t\t\t\t  struct strbuf *report)\n {\n@@ -1339,7 +1339,7 @@ void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\n \tconst char *path;\n \n-\tif (ensure_valid_ownership(gitfile, worktree, gitdir, &report))\n+\tif (ensure_safe_repository(gitfile, worktree, gitdir, &report))\n \t\treturn;\n \n \tstrbuf_complete(&report, '\\n');\n@@ -1526,7 +1526,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\t\tconst char *gitdir_candidate =\n \t\t\t\tgitdir_path ? gitdir_path : gitdirenv;\n \n-\t\t\tif (ensure_valid_ownership(gitfile, dir->buf,\n+\t\t\tif (ensure_safe_repository(gitfile, dir->buf,\n \t\t\t\t\t\t   gitdir_candidate, report)) {\n \t\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n \t\t\t\tret = GIT_DIR_DISCOVERED;\n@@ -1554,7 +1554,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\t\tif (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&\n \t\t\t    !is_implicit_bare_repo(dir->buf))\n \t\t\t\treturn GIT_DIR_DISALLOWED_BARE;\n-\t\t\tif (!ensure_valid_ownership(NULL, NULL, dir->buf, report))\n+\t\t\tif (!ensure_safe_repository(NULL, NULL, dir->buf, report))\n \t\t\t\treturn GIT_DIR_INVALID_OWNERSHIP;\n \t\t\tstrbuf_addstr(gitdir, \".\");\n \t\t\treturn GIT_DIR_BARE;\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528607","messageId":"20251013094152.23597-3-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013094152.23597-1-git@lohmann.sh","subject":"[PATCH 2/5] setup: rename `die_upon_assumed_unsafe_repo()` to align with check","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T09:41:43Z","receivedAt":"2025-10-13T09:42:39Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"This function dies if the repo in question is deemed to be unsafe and\nthe ownership is only part of the verification. In addition it already\nchecks for \"safe.directory\" config, making the name\n`ensure_valid_ownership()` not expressive.\nWhen additional options to check if a repository is considered to be\nsafe are added, this name is more indicative of the content.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n builtin/clone.c | 2 +-\n path.c          | 4 ++--\n setup.c         | 2 +-\n setup.h         | 2 +-\n 4 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex c990f398ef..6faf67dc68 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -263,7 +263,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t * operation as the hardlinked files can be rewritten at will by the\n \t * potentially-untrusted user. We thus refuse to do so by default.\n \t */\n-\tdie_upon_dubious_ownership(NULL, NULL, src_repo);\n+\tdie_upon_assumed_unsafe_repo(NULL, NULL, src_repo);\n \n \tmkdir_if_missing(dest->buf, 0777);\n \ndiff --git a/path.c b/path.c\nindex 7f56eaf993..254ba6c02f 100644\n--- a/path.c\n+++ b/path.c\n@@ -810,7 +810,7 @@ const char *enter_repo(const char *path, unsigned flags)\n \t\t\treturn NULL;\n \t\tgitfile = read_gitfile(used_path.buf);\n \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, used_path.buf);\n+\t\t\tdie_upon_assumed_unsafe_repo(gitfile, NULL, used_path.buf);\n \t\tif (gitfile) {\n \t\t\tstrbuf_reset(&used_path);\n \t\t\tstrbuf_addstr(&used_path, gitfile);\n@@ -822,7 +822,7 @@ const char *enter_repo(const char *path, unsigned flags)\n \telse {\n \t\tconst char *gitfile = read_gitfile(path);\n \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, path);\n+\t\t\tdie_upon_assumed_unsafe_repo(gitfile, NULL, path);\n \t\tif (gitfile)\n \t\t\tpath = gitfile;\n \t\tif (chdir(path))\ndiff --git a/setup.c b/setup.c\nindex 2c41874774..69f6d1b36c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1333,7 +1333,7 @@ static int ensure_safe_repository(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n+void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n \t\t\t\tconst char *gitdir)\n {\n \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\ndiff --git a/setup.h b/setup.h\nindex 8522fa8575..25bd5f1e70 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -51,7 +51,7 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);\n  * config settings; for non-bare repositories, their worktree needs to be\n  * added, for bare ones their git directory.\n  */\n-void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n+void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n \t\t\t\tconst char *gitdir);\n \n void setup_work_tree(void);\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528608","messageId":"20251013094152.23597-4-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013094152.23597-1-git@lohmann.sh","subject":"[PATCH 3/5] setup: refactor `ensure_safe_repository()` testing priorities","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T09:41:44Z","receivedAt":"2025-10-13T09:42:39Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"The implicit ownership test takes precedence over the explicit\nallow-listing of a path by \"safe.directory\" config. Sort by \"priority\"\n(explicitness). This also allows to more easily integrate additional\nchecks.\n\nMake the explicit safe.directory check take precedence over owner check.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n setup.c | 17 ++++++++++-------\n 1 file changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 69f6d1b36c..41a12a85ab 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1307,12 +1307,6 @@ static int ensure_safe_repository(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n-\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n-\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n-\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n-\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n-\t\treturn 1;\n-\n \t/*\n \t * normalize the data.path for comparison with normalized paths\n \t * that come from the configuration file.  The path is unsafe\n@@ -1330,7 +1324,16 @@ static int ensure_safe_repository(const char *gitfile,\n \tgit_protected_config(safe_directory_cb, &data);\n \n \tfree(data.path);\n-\treturn data.is_safe;\n+\tif (data.is_safe)\n+\t\treturn 1;\n+\n+\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n+\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n+\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n+\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n+\t\treturn 1;\n+\n+\treturn 0;\n }\n \n void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528609","messageId":"20251013094152.23597-5-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013094152.23597-1-git@lohmann.sh","subject":"[PATCH 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T09:41:45Z","receivedAt":"2025-10-13T09:42:39Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"So far, the only option to allow executing git in what it considers to\nbe an \"unsafe context\" is to set this repository as \"safe.directory\". If\na user only wants to temporarily execute one command, they would need to\nset the path as safe, execute the command and then remove the path\nagain. Forgetting to do the latter would make the user vulnerable if\nthis repo was changed afterwards in a malicious way.\n\nAllow temporarily bypassing `ensure_safe_repository()` checks with a new\nflag \"--allow-unsafe\" or environment variable \"GIT_ALLOW_UNSAFE\".\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\nThis appends to the error message of the \"dubious ownership\". A first\ndraft had that reworded, but as Johannes Schindelin pointed out, this\ncould be \"breaking the API\" for external programs. Is appending fine?\n\nWas adding the new test file to t/meson.build the only thing needed to\ndo? I didn't see anything documented on how to add a new test file, but\n`make` complained without manually adding it.\n\n\n Documentation/git.adoc            | 13 +++++++++++++\n environment.h                     |  1 +\n git.c                             |  5 +++++\n setup.c                           | 13 +++++++++++--\n t/meson.build                     |  1 +\n t/t0036-allow-unsafe-directory.sh | 28 ++++++++++++++++++++++++++++\n 6 files changed, 59 insertions(+), 2 deletions(-)\n create mode 100755 t/t0036-allow-unsafe-directory.sh\n\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex ce099e78b8..7df51c38f9 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -14,6 +14,7 @@ SYNOPSIS\n     [-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     <command> [<args>]\n \n DESCRIPTION\n@@ -231,6 +232,12 @@ If you just want to run git as if it was started in `<path>` then use\n \tlinkgit:gitattributes[5]. This is equivalent to setting the\n \t`GIT_ATTR_SOURCE` environment variable.\n \n+--allow-unsafe::\n+\tTemporarily trust the repository regardless of \"safe.directory\"\n+\tconfiguration or ownership, potentially resulting in arbitrary code\n+\texecution by hooks or configuration settings. Equivalent to setting\n+\tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n+\n GIT COMMANDS\n ------------\n \n@@ -493,6 +500,12 @@ These environment variables apply to 'all' core Git commands. Nb: it\n is worth noting that they may be used/overridden by SCMS sitting above\n Git so take care if using a foreign front-end.\n \n+`GIT_ALLOW_UNSAFE`::\n+\tThis Boolean environment variable can be set to true to skip the\n+\tsafety checks of \"safe.directory\" configuration and if the user\n+\towns the repository before potentially executing arbitrary code\n+\tfrom hooks or config.\n+\n `GIT_INDEX_FILE`::\n \tThis environment variable specifies an alternate\n \tindex file. If not specified, the default of `$GIT_DIR/index`\ndiff --git a/environment.h b/environment.h\nindex 51898c99cd..ee9e1b9514 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -42,6 +42,7 @@\n #define GIT_OPTIONAL_LOCKS_ENVIRONMENT \"GIT_OPTIONAL_LOCKS\"\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n+#define GIT_ALLOW_UNSAFE \"GIT_ALLOW_UNSAFE\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/git.c b/git.c\nindex c5fad56813..a7581a6805 100644\n--- a/git.c\n+++ b/git.c\n@@ -42,6 +42,7 @@ const char git_usage_string[] =\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\\n\"\n \t   \"           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\\n\"\n \t   \"           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\\n\"\n+\t   \"           [--allow-unsafe]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -354,6 +355,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_ADVICE_ENVIRONMENT, \"0\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"--allow-unsafe\")) {\n+\t\t\tsetenv(GIT_ALLOW_UNSAFE, \"1\", 1);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n \t\t} else {\n \t\t\tfprintf(stderr, _(\"unknown option: %s\\n\"), cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/setup.c b/setup.c\nindex 41a12a85ab..10975fd9a3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1307,6 +1307,9 @@ static int ensure_safe_repository(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n+\tif (git_env_bool(\"GIT_ALLOW_UNSAFE\", 0))\n+\t\treturn 1;\n+\n \t/*\n \t * normalize the data.path for comparison with normalized paths\n \t * that come from the configuration file.  The path is unsafe\n@@ -1353,7 +1356,10 @@ void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n \t      \"%s\"\n \t      \"To add an exception for this directory, call:\\n\"\n \t      \"\\n\"\n-\t      \"\\tgit config --global --add safe.directory %s\"),\n+\t      \"\\tgit config --global --add safe.directory %s\\n\"\n+\t      \"\\n\"\n+\t      \"To temporarily bypass safety-checks, run 'git --allow-unsafe <command>'\\n\"\n+\t      \"or set the environment variable 'GIT_ALLOW_UNSAFE=true'.\"),\n \t    path, report.buf, quoted.buf);\n }\n \n@@ -1797,7 +1803,10 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\t\t      \"%s\"\n \t\t\t      \"To add an exception for this directory, call:\\n\"\n \t\t\t      \"\\n\"\n-\t\t\t      \"\\tgit config --global --add safe.directory %s\"),\n+\t\t\t      \"\\tgit config --global --add safe.directory %s\\n\"\n+\t\t\t      \"\\n\"\n+\t\t\t      \"To temporarily bypass safety-checks, run 'git --allow-unsafe <command>'\\n\"\n+\t\t\t      \"or set the environment variable 'GIT_ALLOW_UNSAFE=true'.\"),\n \t\t\t    dir.buf, report.buf, quoted.buf);\n \t\t}\n \t\t*nongit_ok = 1;\ndiff --git a/t/meson.build b/t/meson.build\nindex 11376b9e25..c55fb55784 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -100,6 +100,7 @@ integration_tests = [\n   't0033-safe-directory.sh',\n   't0034-root-safe-directory.sh',\n   't0035-safe-bare-repository.sh',\n+  't0036-allow-unsafe-directory.sh',\n   't0040-parse-options.sh',\n   't0041-usage.sh',\n   't0050-filesystem.sh',\ndiff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh\nnew file mode 100755\nindex 0000000000..4b98e815ff\n--- /dev/null\n+++ b/t/t0036-allow-unsafe-directory.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+test_description='verify safe.directory checks'\n+\n+. ./test-lib.sh\n+\n+GIT_TEST_ASSUME_DIFFERENT_OWNER=1\n+export GIT_TEST_ASSUME_DIFFERENT_OWNER\n+\n+expect_rejected_dir () {\n+\ttest_must_fail git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+}\n+\n+test_expect_success 'safe.directory is not set' '\n+\texpect_rejected_dir\n+'\n+\n+test_expect_success '--allow-unsafe allows execution in unsafe directory' '\n+\tgit --allow-unsafe status\n+'\n+\n+test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '\n+\tenv GIT_ALLOW_UNSAFE=true \\\n+\t    git status\n+'\n+\n+test_done\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528610","messageId":"20251013094152.23597-6-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013094152.23597-1-git@lohmann.sh","subject":"[PATCH 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T09:41:46Z","receivedAt":"2025-10-13T09:42:41Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"Git considers all repositories as safe, if they are either\n - explicitly set in \"safe.directory\" config, or\n - the user owns the repo\n\nSince a user could unzip a folder they downloaded from the internet and\nunknown to them, it is a repository with malicious hooks/config, an\nattacker could easily get code execution. Even a command line prompt\nwould automatically trigger this if executing `git status` after\nentering the malicious directory.\n\nAllow not to automatically treat all repos owned by the user as safe.\nThis can either be done by \"--assume-unsafe\", the environment variable\n\"GIT_ASSUME_UNSAFE\" or by setting the configuration \"safe.assumeUnsafe\"\nin a safe context (so not the repo config, as it should not be able to\nallow list itself).\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\nQuestion in setup.c: is setting the environment variable inside of\nsafe_directory_cb the best way to \"communicate\" this result?\nAlternatively one could add a new member to the struct, but I thought\nthis was not the best either...\n\n\n Documentation/config/safe.adoc    |  9 +++++++\n Documentation/git.adoc            | 14 ++++++++++-\n environment.h                     |  1 +\n git.c                             |  6 ++++-\n setup.c                           |  9 +++++++\n t/t0036-allow-unsafe-directory.sh | 42 +++++++++++++++++++++++++++++++\n 6 files changed, 79 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc\nindex 2d45c98b12..2ac5d94762 100644\n--- a/Documentation/config/safe.adoc\n+++ b/Documentation/config/safe.adoc\n@@ -60,3 +60,12 @@ which id the original user has.\n If that is not what you would prefer and want git to only trust\n repositories that are owned by root instead, then you can remove\n the `SUDO_UID` variable from root's environment before invoking git.\n+\n+safe.assumeUnsafe::\n+\tBoolean to indicate that the ownership of a repository should not\n+\tbe taken into account when checking if the repository is safe. It\n+\twill prevent against accidental arbitrariy code execution\n++\n+To temporarily allow git execution in case of an assumed unsafe repository,\n+run the command with `--allow-unsafe`. To permanently trust this path, add\n+it to the `safe.directory` config.\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex 7df51c38f9..162350f3db 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -14,7 +14,7 @@ SYNOPSIS\n     [-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>]\n \n DESCRIPTION\n@@ -238,6 +238,13 @@ If you just want to run git as if it was started in `<path>` then use\n \texecution by hooks or configuration settings. Equivalent to setting\n \tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n \n+--assume-unsafe::\n+\tPrevent arbitrary code execution by hooks or configuration if not\n+\texecuted in a \"safe.directory\". With setting this, filesystem ownership\n+\tof the repository in question no longer satisfies to mark it as safe.\n+\tEquivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overwritten if\n+\t`--allow-unsafe` is passed as well.\n+\n GIT COMMANDS\n ------------\n \n@@ -506,6 +513,11 @@ Git so take care if using a foreign front-end.\n \towns the repository before potentially executing arbitrary code\n \tfrom hooks or config.\n \n+`GIT_ASSUME_UNSAFE`::\n+\tThis Boolean environment variable can be set to true enforce\n+\texplicit \"safe.directory\" configuration for the repository. This\n+\tcan be overwritten by setting `GIT_ALLOW_UNSAFE`.\n+\n `GIT_INDEX_FILE`::\n \tThis environment variable specifies an alternate\n \tindex file. If not specified, the default of `$GIT_DIR/index`\ndiff --git a/environment.h b/environment.h\nindex ee9e1b9514..89036a9460 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -43,6 +43,7 @@\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n #define GIT_ALLOW_UNSAFE \"GIT_ALLOW_UNSAFE\"\n+#define GIT_ASSUME_UNSAFE \"GIT_ASSUME_UNSAFE\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/git.c b/git.c\nindex a7581a6805..40ef89558d 100644\n--- a/git.c\n+++ b/git.c\n@@ -42,7 +42,7 @@ const char git_usage_string[] =\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\\n\"\n \t   \"           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\\n\"\n \t   \"           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\\n\"\n-\t   \"           [--allow-unsafe]\\n\"\n+\t   \"           [--allow-unsafe] [--assume-unsafe]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -359,6 +359,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_ALLOW_UNSAFE, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"--assume-unsafe\")) {\n+\t\t\tsetenv(GIT_ASSUME_UNSAFE, \"1\", 1);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n \t\t} else {\n \t\t\tfprintf(stderr, _(\"unknown option: %s\\n\"), cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/setup.c b/setup.c\nindex 10975fd9a3..0d6cddfcb9 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1238,6 +1238,12 @@ static int safe_directory_cb(const char *key, const char *value,\n {\n \tstruct safe_directory_data *data = d;\n \n+\tif (!strcmp(key, \"safe.assumeunsafe\")) {\n+\t\tif (git_config_bool(key, value))\n+\t\t\tsetenv(GIT_ASSUME_UNSAFE, value, 0);\n+\t\treturn 0;\n+\t}\n+\n \tif (strcmp(key, \"safe.directory\"))\n \t\treturn 0;\n \n@@ -1330,6 +1336,9 @@ static int ensure_safe_repository(const char *gitfile,\n \tif (data.is_safe)\n \t\treturn 1;\n \n+\tif (git_env_bool(\"GIT_ASSUME_UNSAFE\", 0))\n+\t\treturn 0;\n+\n \tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n \t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n \t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\ndiff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh\nindex 4b98e815ff..7e08c261bc 100755\n--- a/t/t0036-allow-unsafe-directory.sh\n+++ b/t/t0036-allow-unsafe-directory.sh\n@@ -25,4 +25,46 @@ test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '\n \t    git status\n '\n \n+test_expect_success '--assume-unsafe prevents execution if not in safe.directory' '\n+\tsane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&\n+\tgit status &&\n+\ttest_must_fail git --assume-unsafe status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+test_expect_success 'GIT_ASSUME_UNSAFE prevents execution if not in safe.directory' '\n+\ttest_must_fail env GIT_ASSUME_UNSAFE=1 \\\n+\t\t\t   git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe on the command line' '\n+\ttest_must_fail git -c safe.assumeUnsafe=\"true\" status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe in the environment' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t    GIT_CONFIG_KEY_0=\"safe.assumeUnsafe\" \\\n+\t    GIT_CONFIG_VALUE_0=\"true\" \\\n+\t    git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe in GIT_CONFIG_PARAMETERS' '\n+\ttest_must_fail env GIT_CONFIG_PARAMETERS=\"${SQ}safe.assumeUnsafe${SQ}=${SQ}true${SQ}\" \\\n+\t    git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'ignoring safe.assumeUnsafe in repo config' '\n+\tgit config safe.assumeUnsafe \"false\" &&\n+\tgit config --global safe.assumeUnsafe \"true\" &&\n+\ttest_must_fail git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'allow-unsafe must overwrite assume-unsafe' '\n+\tenv GIT_ASSUME_UNSAFE=1 git --allow-unsafe status\n+'\n+\n test_done\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528617","messageId":"CALnO6CBLr2iL0r+ywM4Vjw0=J2DNFv9Nhhq_PHuxt4eK=Z95ww@mail.gmail.com","threadId":"64302","inReplyTo":"20251013094152.23597-6-git@lohmann.sh","subject":"Re: [PATCH 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2025-10-13T11:59:02Z","receivedAt":"2025-10-13T11:59:17Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Mon, Oct 13, 2025 at 5:43 AM Michael Lohmann <git@lohmann.sh> wrote:\n>\n> Git considers all repositories as safe, if they are either\n>  - explicitly set in \"safe.directory\" config, or\n>  - the user owns the repo\n>\n> Since a user could unzip a folder they downloaded from the internet and\n> unknown to them, it is a repository with malicious hooks/config, an\n> attacker could easily get code execution. Even a command line prompt\n> would automatically trigger this if executing `git status` after\n> entering the malicious directory.\n>\n> Allow not to automatically treat all repos owned by the user as safe.\n> This can either be done by \"--assume-unsafe\", the environment variable\n> \"GIT_ASSUME_UNSAFE\" or by setting the configuration \"safe.assumeUnsafe\"\n> in a safe context (so not the repo config, as it should not be able to\n> allow list itself).\n>\n> Signed-off-by: Michael Lohmann <git@lohmann.sh>\n> ---\n> Question in setup.c: is setting the environment variable inside of\n> safe_directory_cb the best way to \"communicate\" this result?\n> Alternatively one could add a new member to the struct, but I thought\n> this was not the best either...\n>\n>\n>  Documentation/config/safe.adoc    |  9 +++++++\n>  Documentation/git.adoc            | 14 ++++++++++-\n>  environment.h                     |  1 +\n>  git.c                             |  6 ++++-\n>  setup.c                           |  9 +++++++\n>  t/t0036-allow-unsafe-directory.sh | 42 +++++++++++++++++++++++++++++++\n>  6 files changed, 79 insertions(+), 2 deletions(-)\n>\n> diff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc\n> index 2d45c98b12..2ac5d94762 100644\n> --- a/Documentation/config/safe.adoc\n> +++ b/Documentation/config/safe.adoc\n> @@ -60,3 +60,12 @@ which id the original user has.\n>  If that is not what you would prefer and want git to only trust\n>  repositories that are owned by root instead, then you can remove\n>  the `SUDO_UID` variable from root's environment before invoking git.\n> +\n> +safe.assumeUnsafe::\n> +       Boolean to indicate that the ownership of a repository should not\n> +       be taken into account when checking if the repository is safe. It\n> +       will prevent against accidental arbitrariy code execution\n\ns/arbitrariy/arbitrary. (fix typo + add period)\n\n> ++\n> +To temporarily allow git execution in case of an assumed unsafe repository,\n> +run the command with `--allow-unsafe`. To permanently trust this path, add\n> +it to the `safe.directory` config.\n> diff --git a/Documentation/git.adoc b/Documentation/git.adoc\n> index 7df51c38f9..162350f3db 100644\n> --- a/Documentation/git.adoc\n> +++ b/Documentation/git.adoc\n> @@ -14,7 +14,7 @@ SYNOPSIS\n>      [-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>]\n>\n>  DESCRIPTION\n> @@ -238,6 +238,13 @@ If you just want to run git as if it was started in `<path>` then use\n>         execution by hooks or configuration settings. Equivalent to setting\n>         the environment variable `GIT_ALLOW_UNSAFE=1`.\n>\n> +--assume-unsafe::\n> +       Prevent arbitrary code execution by hooks or configuration if not\n> +       executed in a \"safe.directory\". With setting this, filesystem ownership\n> +       of the repository in question no longer satisfies to mark it as safe.\n> +       Equivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overwritten if\n> +       `--allow-unsafe` is passed as well.\n\nHere and later, I think you mean \"overridden\" not \"overwritten\"\n\n-- \nD. Ben Knoble\n"},{"id":"528664","messageId":"20251013214608.33581-2-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013214608.33581-1-git@lohmann.sh","subject":"[PATCH v2 1/5] setup: rename `ensure_safe_repository()` for clarity","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T21:46:04Z","receivedAt":"2025-10-13T21:46:31Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"In addition to ownership it checks for \"safe.directory\" config, making\nthe name `ensure_valid_ownership()` not expressive. This function\nensures that a repository is considered to be safe.\nWhen additional options to check if a repository is considered to be\nsafe are added, this name is more indicative of the content.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n setup.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 7086741e6c..2c41874774 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1301,7 +1301,7 @@ static int safe_directory_cb(const char *key, const char *value,\n  * config settings; for non-bare repositories, their worktree needs to be\n  * added, for bare ones their git directory.\n  */\n-static int ensure_valid_ownership(const char *gitfile,\n+static int ensure_safe_repository(const char *gitfile,\n \t\t\t\t  const char *worktree, const char *gitdir,\n \t\t\t\t  struct strbuf *report)\n {\n@@ -1339,7 +1339,7 @@ void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\n \tconst char *path;\n \n-\tif (ensure_valid_ownership(gitfile, worktree, gitdir, &report))\n+\tif (ensure_safe_repository(gitfile, worktree, gitdir, &report))\n \t\treturn;\n \n \tstrbuf_complete(&report, '\\n');\n@@ -1526,7 +1526,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\t\tconst char *gitdir_candidate =\n \t\t\t\tgitdir_path ? gitdir_path : gitdirenv;\n \n-\t\t\tif (ensure_valid_ownership(gitfile, dir->buf,\n+\t\t\tif (ensure_safe_repository(gitfile, dir->buf,\n \t\t\t\t\t\t   gitdir_candidate, report)) {\n \t\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n \t\t\t\tret = GIT_DIR_DISCOVERED;\n@@ -1554,7 +1554,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\t\tif (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&\n \t\t\t    !is_implicit_bare_repo(dir->buf))\n \t\t\t\treturn GIT_DIR_DISALLOWED_BARE;\n-\t\t\tif (!ensure_valid_ownership(NULL, NULL, dir->buf, report))\n+\t\t\tif (!ensure_safe_repository(NULL, NULL, dir->buf, report))\n \t\t\t\treturn GIT_DIR_INVALID_OWNERSHIP;\n \t\t\tstrbuf_addstr(gitdir, \".\");\n \t\t\treturn GIT_DIR_BARE;\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528665","messageId":"20251013214608.33581-3-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013214608.33581-1-git@lohmann.sh","subject":"[PATCH v2 2/5] setup: rename `die_upon_assumed_unsafe_repo()` to align with check","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T21:46:05Z","receivedAt":"2025-10-13T21:46:31Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"This function dies if the repo in question is deemed to be unsafe and\nthe ownership is only part of the verification. In addition it already\nchecks for \"safe.directory\" config, making the name\n`ensure_valid_ownership()` not expressive.\nWhen additional options to check if a repository is considered to be\nsafe are added, this name is more indicative of the content.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n builtin/clone.c | 2 +-\n path.c          | 4 ++--\n setup.c         | 2 +-\n setup.h         | 2 +-\n 4 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex c990f398ef..6faf67dc68 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -263,7 +263,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t * operation as the hardlinked files can be rewritten at will by the\n \t * potentially-untrusted user. We thus refuse to do so by default.\n \t */\n-\tdie_upon_dubious_ownership(NULL, NULL, src_repo);\n+\tdie_upon_assumed_unsafe_repo(NULL, NULL, src_repo);\n \n \tmkdir_if_missing(dest->buf, 0777);\n \ndiff --git a/path.c b/path.c\nindex 7f56eaf993..254ba6c02f 100644\n--- a/path.c\n+++ b/path.c\n@@ -810,7 +810,7 @@ const char *enter_repo(const char *path, unsigned flags)\n \t\t\treturn NULL;\n \t\tgitfile = read_gitfile(used_path.buf);\n \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, used_path.buf);\n+\t\t\tdie_upon_assumed_unsafe_repo(gitfile, NULL, used_path.buf);\n \t\tif (gitfile) {\n \t\t\tstrbuf_reset(&used_path);\n \t\t\tstrbuf_addstr(&used_path, gitfile);\n@@ -822,7 +822,7 @@ const char *enter_repo(const char *path, unsigned flags)\n \telse {\n \t\tconst char *gitfile = read_gitfile(path);\n \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, path);\n+\t\t\tdie_upon_assumed_unsafe_repo(gitfile, NULL, path);\n \t\tif (gitfile)\n \t\t\tpath = gitfile;\n \t\tif (chdir(path))\ndiff --git a/setup.c b/setup.c\nindex 2c41874774..69f6d1b36c 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1333,7 +1333,7 @@ static int ensure_safe_repository(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n+void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n \t\t\t\tconst char *gitdir)\n {\n \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\ndiff --git a/setup.h b/setup.h\nindex 8522fa8575..25bd5f1e70 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -51,7 +51,7 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);\n  * config settings; for non-bare repositories, their worktree needs to be\n  * added, for bare ones their git directory.\n  */\n-void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n+void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n \t\t\t\tconst char *gitdir);\n \n void setup_work_tree(void);\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528666","messageId":"20251013214608.33581-4-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013214608.33581-1-git@lohmann.sh","subject":"[PATCH v2 3/5] setup: refactor `ensure_safe_repository()` testing priorities","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T21:46:06Z","receivedAt":"2025-10-13T21:46:31Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"The implicit ownership test takes precedence over the explicit\nallow-listing of a path by \"safe.directory\" config. Sort by \"priority\"\n(explicitness). This also allows to more easily integrate additional\nchecks.\n\nMake the explicit safe.directory check take precedence over owner check.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n setup.c | 17 ++++++++++-------\n 1 file changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 69f6d1b36c..41a12a85ab 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1307,12 +1307,6 @@ static int ensure_safe_repository(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n-\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n-\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n-\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n-\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n-\t\treturn 1;\n-\n \t/*\n \t * normalize the data.path for comparison with normalized paths\n \t * that come from the configuration file.  The path is unsafe\n@@ -1330,7 +1324,16 @@ static int ensure_safe_repository(const char *gitfile,\n \tgit_protected_config(safe_directory_cb, &data);\n \n \tfree(data.path);\n-\treturn data.is_safe;\n+\tif (data.is_safe)\n+\t\treturn 1;\n+\n+\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n+\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n+\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n+\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n+\t\treturn 1;\n+\n+\treturn 0;\n }\n \n void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528667","messageId":"20251013214608.33581-5-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013214608.33581-1-git@lohmann.sh","subject":"[PATCH v2 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T21:46:07Z","receivedAt":"2025-10-13T21:46:31Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"So far, the only option to allow executing git in what it considers to\nbe an \"unsafe context\" is to set this repository as \"safe.directory\". If\na user only wants to temporarily execute one command, they would need to\nset the path as safe, execute the command and then remove the path\nagain. Forgetting to do the latter would make the user vulnerable if\nthis repo was changed afterwards in a malicious way.\n\nAllow temporarily bypassing `ensure_safe_repository()` checks with a new\nflag \"--allow-unsafe\" or environment variable \"GIT_ALLOW_UNSAFE\".\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n Documentation/git.adoc            | 13 +++++++++++++\n environment.h                     |  1 +\n git.c                             |  5 +++++\n setup.c                           | 13 +++++++++++--\n t/meson.build                     |  1 +\n t/t0036-allow-unsafe-directory.sh | 28 ++++++++++++++++++++++++++++\n 6 files changed, 59 insertions(+), 2 deletions(-)\n create mode 100755 t/t0036-allow-unsafe-directory.sh\n\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex ce099e78b8..7df51c38f9 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -14,6 +14,7 @@ SYNOPSIS\n     [-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     <command> [<args>]\n \n DESCRIPTION\n@@ -231,6 +232,12 @@ If you just want to run git as if it was started in `<path>` then use\n \tlinkgit:gitattributes[5]. This is equivalent to setting the\n \t`GIT_ATTR_SOURCE` environment variable.\n \n+--allow-unsafe::\n+\tTemporarily trust the repository regardless of \"safe.directory\"\n+\tconfiguration or ownership, potentially resulting in arbitrary code\n+\texecution by hooks or configuration settings. Equivalent to setting\n+\tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n+\n GIT COMMANDS\n ------------\n \n@@ -493,6 +500,12 @@ These environment variables apply to 'all' core Git commands. Nb: it\n is worth noting that they may be used/overridden by SCMS sitting above\n Git so take care if using a foreign front-end.\n \n+`GIT_ALLOW_UNSAFE`::\n+\tThis Boolean environment variable can be set to true to skip the\n+\tsafety checks of \"safe.directory\" configuration and if the user\n+\towns the repository before potentially executing arbitrary code\n+\tfrom hooks or config.\n+\n `GIT_INDEX_FILE`::\n \tThis environment variable specifies an alternate\n \tindex file. If not specified, the default of `$GIT_DIR/index`\ndiff --git a/environment.h b/environment.h\nindex 51898c99cd..ee9e1b9514 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -42,6 +42,7 @@\n #define GIT_OPTIONAL_LOCKS_ENVIRONMENT \"GIT_OPTIONAL_LOCKS\"\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n+#define GIT_ALLOW_UNSAFE \"GIT_ALLOW_UNSAFE\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/git.c b/git.c\nindex c5fad56813..a7581a6805 100644\n--- a/git.c\n+++ b/git.c\n@@ -42,6 +42,7 @@ const char git_usage_string[] =\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\\n\"\n \t   \"           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\\n\"\n \t   \"           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\\n\"\n+\t   \"           [--allow-unsafe]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -354,6 +355,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_ADVICE_ENVIRONMENT, \"0\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"--allow-unsafe\")) {\n+\t\t\tsetenv(GIT_ALLOW_UNSAFE, \"1\", 1);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n \t\t} else {\n \t\t\tfprintf(stderr, _(\"unknown option: %s\\n\"), cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/setup.c b/setup.c\nindex 41a12a85ab..10975fd9a3 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1307,6 +1307,9 @@ static int ensure_safe_repository(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n+\tif (git_env_bool(\"GIT_ALLOW_UNSAFE\", 0))\n+\t\treturn 1;\n+\n \t/*\n \t * normalize the data.path for comparison with normalized paths\n \t * that come from the configuration file.  The path is unsafe\n@@ -1353,7 +1356,10 @@ void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n \t      \"%s\"\n \t      \"To add an exception for this directory, call:\\n\"\n \t      \"\\n\"\n-\t      \"\\tgit config --global --add safe.directory %s\"),\n+\t      \"\\tgit config --global --add safe.directory %s\\n\"\n+\t      \"\\n\"\n+\t      \"To temporarily bypass safety-checks, run 'git --allow-unsafe <command>'\\n\"\n+\t      \"or set the environment variable 'GIT_ALLOW_UNSAFE=true'.\"),\n \t    path, report.buf, quoted.buf);\n }\n \n@@ -1797,7 +1803,10 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\t\t      \"%s\"\n \t\t\t      \"To add an exception for this directory, call:\\n\"\n \t\t\t      \"\\n\"\n-\t\t\t      \"\\tgit config --global --add safe.directory %s\"),\n+\t\t\t      \"\\tgit config --global --add safe.directory %s\\n\"\n+\t\t\t      \"\\n\"\n+\t\t\t      \"To temporarily bypass safety-checks, run 'git --allow-unsafe <command>'\\n\"\n+\t\t\t      \"or set the environment variable 'GIT_ALLOW_UNSAFE=true'.\"),\n \t\t\t    dir.buf, report.buf, quoted.buf);\n \t\t}\n \t\t*nongit_ok = 1;\ndiff --git a/t/meson.build b/t/meson.build\nindex 11376b9e25..c55fb55784 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -100,6 +100,7 @@ integration_tests = [\n   't0033-safe-directory.sh',\n   't0034-root-safe-directory.sh',\n   't0035-safe-bare-repository.sh',\n+  't0036-allow-unsafe-directory.sh',\n   't0040-parse-options.sh',\n   't0041-usage.sh',\n   't0050-filesystem.sh',\ndiff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh\nnew file mode 100755\nindex 0000000000..4b98e815ff\n--- /dev/null\n+++ b/t/t0036-allow-unsafe-directory.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+test_description='verify safe.directory checks'\n+\n+. ./test-lib.sh\n+\n+GIT_TEST_ASSUME_DIFFERENT_OWNER=1\n+export GIT_TEST_ASSUME_DIFFERENT_OWNER\n+\n+expect_rejected_dir () {\n+\ttest_must_fail git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+}\n+\n+test_expect_success 'safe.directory is not set' '\n+\texpect_rejected_dir\n+'\n+\n+test_expect_success '--allow-unsafe allows execution in unsafe directory' '\n+\tgit --allow-unsafe status\n+'\n+\n+test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '\n+\tenv GIT_ALLOW_UNSAFE=true \\\n+\t    git status\n+'\n+\n+test_done\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528668","messageId":"20251013214608.33581-1-git@lohmann.sh","threadId":"64302","inReplyTo":"CALnO6CBLr2iL0r+ywM4Vjw0=J2DNFv9Nhhq_PHuxt4eK=Z95ww@mail.gmail.com","subject":"[PATCH v2 0/5] Apply comments of D. Ben Knoble","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T21:46:03Z","receivedAt":"2025-10-13T21:46:31Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"Thanks!\n\nMichael Lohmann (5):\n  setup: rename `ensure_safe_repository()` for clarity\n  setup: rename `die_upon_assumed_unsafe_repo()` to align with check\n  setup: refactor `ensure_safe_repository()` testing priorities\n  setup: allow temporary bypass of `ensure_safe_repository()` checks\n  setup: allow not marking self owned repos as safe in\n    `ensure_safe_repository()`\n\n Documentation/config/safe.adoc    |  9 ++++\n Documentation/git.adoc            | 25 +++++++++++\n builtin/clone.c                   |  2 +-\n environment.h                     |  2 +\n git.c                             |  9 ++++\n path.c                            |  4 +-\n setup.c                           | 45 ++++++++++++++------\n setup.h                           |  2 +-\n t/meson.build                     |  1 +\n t/t0036-allow-unsafe-directory.sh | 70 +++++++++++++++++++++++++++++++\n 10 files changed, 153 insertions(+), 16 deletions(-)\n create mode 100755 t/t0036-allow-unsafe-directory.sh\n\nRange-diff against v1:\n1:  3f8805eb96 = 1:  3f8805eb96 setup: rename `ensure_safe_repository()` for clarity\n2:  aa09159dec = 2:  aa09159dec setup: rename `die_upon_assumed_unsafe_repo()` to align with check\n3:  ad4f64fdb8 = 3:  ad4f64fdb8 setup: refactor `ensure_safe_repository()` testing priorities\n4:  db31fdef4e = 4:  db31fdef4e setup: allow temporary bypass of `ensure_safe_repository()` checks\n5:  f65fd1c4fa ! 5:  6f710af1da setup: allow not marking self owned repos as safe in `ensure_safe_repository()`\n    @@ Documentation/config/safe.adoc: which id the original user has.\n     +safe.assumeUnsafe::\n     +\tBoolean to indicate that the ownership of a repository should not\n     +\tbe taken into account when checking if the repository is safe. It\n    -+\twill prevent against accidental arbitrariy code execution\n    ++\twill prevent against accidental arbitrary code execution.\n     ++\n     +To temporarily allow git execution in case of an assumed unsafe repository,\n     +run the command with `--allow-unsafe`. To permanently trust this path, add\n    @@ Documentation/git.adoc: If you just want to run git as if it was started in `<pa\n     +\tPrevent arbitrary code execution by hooks or configuration if not\n     +\texecuted in a \"safe.directory\". With setting this, filesystem ownership\n     +\tof the repository in question no longer satisfies to mark it as safe.\n    -+\tEquivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overwritten if\n    ++\tEquivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overridden if\n     +\t`--allow-unsafe` is passed as well.\n     +\n      GIT COMMANDS\n    @@ Documentation/git.adoc: Git so take care if using a foreign front-end.\n     +`GIT_ASSUME_UNSAFE`::\n     +\tThis Boolean environment variable can be set to true enforce\n     +\texplicit \"safe.directory\" configuration for the repository. This\n    -+\tcan be overwritten by setting `GIT_ALLOW_UNSAFE`.\n    ++\tcan be overridden by setting `GIT_ALLOW_UNSAFE`.\n     +\n      `GIT_INDEX_FILE`::\n      \tThis environment variable specifies an alternate\n    @@ t/t0036-allow-unsafe-directory.sh: test_expect_success 'GIT_ALLOW_UNSAFE bool al\n     +\tgrep \"dubious ownership\" err\n     +'\n     +\n    -+test_expect_success 'allow-unsafe must overwrite assume-unsafe' '\n    ++test_expect_success 'allow-unsafe must override assume-unsafe' '\n     +\tenv GIT_ASSUME_UNSAFE=1 git --allow-unsafe status\n     +'\n     +\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528669","messageId":"20251013214608.33581-6-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013214608.33581-1-git@lohmann.sh","subject":"[PATCH v2 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-13T21:46:08Z","receivedAt":"2025-10-13T21:46:37Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"Git considers all repositories as safe, if they are either\n - explicitly set in \"safe.directory\" config, or\n - the user owns the repo\n\nSince a user could unzip a folder they downloaded from the internet and\nunknown to them, it is a repository with malicious hooks/config, an\nattacker could easily get code execution. Even a command line prompt\nwould automatically trigger this if executing `git status` after\nentering the malicious directory.\n\nAllow not to automatically treat all repos owned by the user as safe.\nThis can either be done by \"--assume-unsafe\", the environment variable\n\"GIT_ASSUME_UNSAFE\" or by setting the configuration \"safe.assumeUnsafe\"\nin a safe context (so not the repo config, as it should not be able to\nallow list itself).\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n Documentation/config/safe.adoc    |  9 +++++++\n Documentation/git.adoc            | 14 ++++++++++-\n environment.h                     |  1 +\n git.c                             |  6 ++++-\n setup.c                           |  9 +++++++\n t/t0036-allow-unsafe-directory.sh | 42 +++++++++++++++++++++++++++++++\n 6 files changed, 79 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc\nindex 2d45c98b12..d93881d6c0 100644\n--- a/Documentation/config/safe.adoc\n+++ b/Documentation/config/safe.adoc\n@@ -60,3 +60,12 @@ which id the original user has.\n If that is not what you would prefer and want git to only trust\n repositories that are owned by root instead, then you can remove\n the `SUDO_UID` variable from root's environment before invoking git.\n+\n+safe.assumeUnsafe::\n+\tBoolean to indicate that the ownership of a repository should not\n+\tbe taken into account when checking if the repository is safe. It\n+\twill prevent against accidental arbitrary code execution.\n++\n+To temporarily allow git execution in case of an assumed unsafe repository,\n+run the command with `--allow-unsafe`. To permanently trust this path, add\n+it to the `safe.directory` config.\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex 7df51c38f9..e24dafc2a9 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -14,7 +14,7 @@ SYNOPSIS\n     [-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>]\n \n DESCRIPTION\n@@ -238,6 +238,13 @@ If you just want to run git as if it was started in `<path>` then use\n \texecution by hooks or configuration settings. Equivalent to setting\n \tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n \n+--assume-unsafe::\n+\tPrevent arbitrary code execution by hooks or configuration if not\n+\texecuted in a \"safe.directory\". With setting this, filesystem ownership\n+\tof the repository in question no longer satisfies to mark it as safe.\n+\tEquivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overridden if\n+\t`--allow-unsafe` is passed as well.\n+\n GIT COMMANDS\n ------------\n \n@@ -506,6 +513,11 @@ Git so take care if using a foreign front-end.\n \towns the repository before potentially executing arbitrary code\n \tfrom hooks or config.\n \n+`GIT_ASSUME_UNSAFE`::\n+\tThis Boolean environment variable can be set to true enforce\n+\texplicit \"safe.directory\" configuration for the repository. This\n+\tcan be overridden by setting `GIT_ALLOW_UNSAFE`.\n+\n `GIT_INDEX_FILE`::\n \tThis environment variable specifies an alternate\n \tindex file. If not specified, the default of `$GIT_DIR/index`\ndiff --git a/environment.h b/environment.h\nindex ee9e1b9514..89036a9460 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -43,6 +43,7 @@\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n #define GIT_ALLOW_UNSAFE \"GIT_ALLOW_UNSAFE\"\n+#define GIT_ASSUME_UNSAFE \"GIT_ASSUME_UNSAFE\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/git.c b/git.c\nindex a7581a6805..40ef89558d 100644\n--- a/git.c\n+++ b/git.c\n@@ -42,7 +42,7 @@ const char git_usage_string[] =\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\\n\"\n \t   \"           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\\n\"\n \t   \"           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\\n\"\n-\t   \"           [--allow-unsafe]\\n\"\n+\t   \"           [--allow-unsafe] [--assume-unsafe]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -359,6 +359,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_ALLOW_UNSAFE, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"--assume-unsafe\")) {\n+\t\t\tsetenv(GIT_ASSUME_UNSAFE, \"1\", 1);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n \t\t} else {\n \t\t\tfprintf(stderr, _(\"unknown option: %s\\n\"), cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/setup.c b/setup.c\nindex 10975fd9a3..0d6cddfcb9 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1238,6 +1238,12 @@ static int safe_directory_cb(const char *key, const char *value,\n {\n \tstruct safe_directory_data *data = d;\n \n+\tif (!strcmp(key, \"safe.assumeunsafe\")) {\n+\t\tif (git_config_bool(key, value))\n+\t\t\tsetenv(GIT_ASSUME_UNSAFE, value, 0);\n+\t\treturn 0;\n+\t}\n+\n \tif (strcmp(key, \"safe.directory\"))\n \t\treturn 0;\n \n@@ -1330,6 +1336,9 @@ static int ensure_safe_repository(const char *gitfile,\n \tif (data.is_safe)\n \t\treturn 1;\n \n+\tif (git_env_bool(\"GIT_ASSUME_UNSAFE\", 0))\n+\t\treturn 0;\n+\n \tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n \t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n \t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\ndiff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh\nindex 4b98e815ff..3a86336541 100755\n--- a/t/t0036-allow-unsafe-directory.sh\n+++ b/t/t0036-allow-unsafe-directory.sh\n@@ -25,4 +25,46 @@ test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '\n \t    git status\n '\n \n+test_expect_success '--assume-unsafe prevents execution if not in safe.directory' '\n+\tsane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&\n+\tgit status &&\n+\ttest_must_fail git --assume-unsafe status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+test_expect_success 'GIT_ASSUME_UNSAFE prevents execution if not in safe.directory' '\n+\ttest_must_fail env GIT_ASSUME_UNSAFE=1 \\\n+\t\t\t   git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe on the command line' '\n+\ttest_must_fail git -c safe.assumeUnsafe=\"true\" status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe in the environment' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t    GIT_CONFIG_KEY_0=\"safe.assumeUnsafe\" \\\n+\t    GIT_CONFIG_VALUE_0=\"true\" \\\n+\t    git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe in GIT_CONFIG_PARAMETERS' '\n+\ttest_must_fail env GIT_CONFIG_PARAMETERS=\"${SQ}safe.assumeUnsafe${SQ}=${SQ}true${SQ}\" \\\n+\t    git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'ignoring safe.assumeUnsafe in repo config' '\n+\tgit config safe.assumeUnsafe \"false\" &&\n+\tgit config --global safe.assumeUnsafe \"true\" &&\n+\ttest_must_fail git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'allow-unsafe must override assume-unsafe' '\n+\tenv GIT_ASSUME_UNSAFE=1 git --allow-unsafe status\n+'\n+\n test_done\n-- \n2.50.1 (Apple Git-155)\n\n"},{"id":"528757","messageId":"xmqq347lxmr7.fsf@gitster.g","threadId":"64302","inReplyTo":"20251013094152.23597-3-git@lohmann.sh","subject":"Re: [PATCH 2/5] setup: rename `die_upon_assumed_unsafe_repo()` to align with check","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-14T20:16:12Z","receivedAt":"2025-10-14T20:16:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Lohmann <git@lohmann.sh> writes:\n\n> This function dies if the repo in question is deemed to be unsafe and\n> the ownership is only part of the verification. In addition it already\n> checks for \"safe.directory\" config, making the name\n> `ensure_valid_ownership()` not expressive.\n> When additional options to check if a repository is considered to be\n> safe are added, this name is more indicative of the content.\n\nThe new name chosen in the previous step makes perfect sense, and\nthe previous step sounds like a good thing to do.  Likewise, I can\nunderstand the reason why we want to rename this helper here, as the\nreason why we die no longer is based solely on ownership.\n\nBut why \"assumed unsafe\", instead of just \"die_upon_unsafe_repo()\"?\n"},{"id":"528758","messageId":"xmqqy0pdw7g6.fsf@gitster.g","threadId":"64302","inReplyTo":"20251013094152.23597-4-git@lohmann.sh","subject":"Re: [PATCH 3/5] setup: refactor `ensure_safe_repository()` testing priorities","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-14T20:32:09Z","receivedAt":"2025-10-14T20:32:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Lohmann <git@lohmann.sh> writes:\n\n> The implicit ownership test takes precedence over the explicit\n> allow-listing of a path by \"safe.directory\" config. Sort by \"priority\"\n> (explicitness). This also allows to more easily integrate additional\n> checks.\n>\n> Make the explicit safe.directory check take precedence over owner check.\n\nI do not think the above argument makes much sense, with the code\nwith or without this patch.\n\nIt would be a very different story if the explicit specification\nallowed users to configure a set of directories to be rejected, in\nwhich case a user can mark a directory as unsafe even the\nownership-based rules would allow it, and explicit rules may have\nhigher \"priority\".\n\nBut that is not what safe_directory_cb() does.\n\nIn other words, there is no \"priority\" among the rules considered by\nensure_safe_repository() helper.  At least, with the shape of the\nhelper function at this step in the series, all rules are equally\ncapable of declaring a directory \"safe\".\n\nIf you are in later steps (I haven't read them) introducing ways to\nsay \"this and that directories are explicitly forbidden\", perhaps\nreordering like this should be done at that point.\n\nAlternatively, you can leave the change here in the middle of the\nseries, but explain the rationale differently, e.g.,\n\n    With the current code, this change does not make any difference\n    because there is no explicit rule that lets you reject a\n    directory that the ownership-based rule may accept.  In a later\n    step in this series, however, we will introduce a mechanism to\n    allow such an explicit rule, at which point the order of checks,\n    i.e. seeing the explicit rule reject a directory and failing the\n    operation before consulting the ownership-based rule, will start\n    to matter.  As a preliminary change, reorder the existing\n    checks.\n\nor something like that, perhaps.\n\n> Signed-off-by: Michael Lohmann <git@lohmann.sh>\n> ---\n>  setup.c | 17 ++++++++++-------\n>  1 file changed, 10 insertions(+), 7 deletions(-)\n>\n> diff --git a/setup.c b/setup.c\n> index 69f6d1b36c..41a12a85ab 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -1307,12 +1307,6 @@ static int ensure_safe_repository(const char *gitfile,\n>  {\n>  \tstruct safe_directory_data data = { 0 };\n>  \n> -\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n> -\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n> -\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n> -\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n> -\t\treturn 1;\n> -\n>  \t/*\n>  \t * normalize the data.path for comparison with normalized paths\n>  \t * that come from the configuration file.  The path is unsafe\n> @@ -1330,7 +1324,16 @@ static int ensure_safe_repository(const char *gitfile,\n>  \tgit_protected_config(safe_directory_cb, &data);\n>  \n>  \tfree(data.path);\n> -\treturn data.is_safe;\n> +\tif (data.is_safe)\n> +\t\treturn 1;\n> +\n> +\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n> +\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n> +\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n> +\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n> +\t\treturn 1;\n> +\n> +\treturn 0;\n>  }\n>  \n>  void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n"},{"id":"528927","messageId":"20251016053322.44495-4-git@lohmann.sh","threadId":"64302","inReplyTo":"20251016053322.44495-1-git@lohmann.sh","subject":"[PATCH v3 3/5] setup: refactor `ensure_safe_repository()` testing priorities","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-16T05:33:20Z","receivedAt":"2025-10-16T05:33:53Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"With the current code, this change does not make any difference because\nthere is no explicit rule that lets you reject a directory that the\nownership-based rule may accept.  In a later step in this series,\nhowever, we will introduce a mechanism to allow such an explicit rule,\nat which point the order of checks, i.e. seeing the explicit rule reject\na directory and failing the operation before consulting the\nownership-based rule, will start to matter.  As a preliminary change,\nreorder the existing checks.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n setup.c | 17 ++++++++++-------\n 1 file changed, 10 insertions(+), 7 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex c6e1204c05..5ec68be379 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1307,12 +1307,6 @@ static int ensure_safe_repository(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n-\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n-\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n-\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n-\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n-\t\treturn 1;\n-\n \t/*\n \t * normalize the data.path for comparison with normalized paths\n \t * that come from the configuration file.  The path is unsafe\n@@ -1330,7 +1324,16 @@ static int ensure_safe_repository(const char *gitfile,\n \tgit_protected_config(safe_directory_cb, &data);\n \n \tfree(data.path);\n-\treturn data.is_safe;\n+\tif (data.is_safe)\n+\t\treturn 1;\n+\n+\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n+\t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n+\t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n+\t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n+\t\treturn 1;\n+\n+\treturn 0;\n }\n \n void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n-- \n2.51.1.476.g147428281d\n\n"},{"id":"528928","messageId":"20251016053322.44495-2-git@lohmann.sh","threadId":"64302","inReplyTo":"20251016053322.44495-1-git@lohmann.sh","subject":"[PATCH v3 1/5] setup: rename `ensure_safe_repository()` for clarity","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-16T05:33:18Z","receivedAt":"2025-10-16T05:33:53Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"In addition to ownership it checks for \"safe.directory\" config, making\nthe name `ensure_valid_ownership()` not expressive. This function\nensures that a repository is considered to be safe.\nWhen additional options to check if a repository is considered to be\nsafe are added, this name is more indicative of the content.\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n setup.c | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 7086741e6c..2c41874774 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1301,7 +1301,7 @@ static int safe_directory_cb(const char *key, const char *value,\n  * config settings; for non-bare repositories, their worktree needs to be\n  * added, for bare ones their git directory.\n  */\n-static int ensure_valid_ownership(const char *gitfile,\n+static int ensure_safe_repository(const char *gitfile,\n \t\t\t\t  const char *worktree, const char *gitdir,\n \t\t\t\t  struct strbuf *report)\n {\n@@ -1339,7 +1339,7 @@ void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\n \tconst char *path;\n \n-\tif (ensure_valid_ownership(gitfile, worktree, gitdir, &report))\n+\tif (ensure_safe_repository(gitfile, worktree, gitdir, &report))\n \t\treturn;\n \n \tstrbuf_complete(&report, '\\n');\n@@ -1526,7 +1526,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\t\tconst char *gitdir_candidate =\n \t\t\t\tgitdir_path ? gitdir_path : gitdirenv;\n \n-\t\t\tif (ensure_valid_ownership(gitfile, dir->buf,\n+\t\t\tif (ensure_safe_repository(gitfile, dir->buf,\n \t\t\t\t\t\t   gitdir_candidate, report)) {\n \t\t\t\tstrbuf_addstr(gitdir, gitdirenv);\n \t\t\t\tret = GIT_DIR_DISCOVERED;\n@@ -1554,7 +1554,7 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,\n \t\t\tif (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&\n \t\t\t    !is_implicit_bare_repo(dir->buf))\n \t\t\t\treturn GIT_DIR_DISALLOWED_BARE;\n-\t\t\tif (!ensure_valid_ownership(NULL, NULL, dir->buf, report))\n+\t\t\tif (!ensure_safe_repository(NULL, NULL, dir->buf, report))\n \t\t\t\treturn GIT_DIR_INVALID_OWNERSHIP;\n \t\t\tstrbuf_addstr(gitdir, \".\");\n \t\t\treturn GIT_DIR_BARE;\n-- \n2.51.1.476.g147428281d\n\n"},{"id":"528929","messageId":"20251016053322.44495-1-git@lohmann.sh","threadId":"64302","inReplyTo":"20251013094152.23597-1-git@lohmann.sh","subject":"[PATCH v3 0/5] Allow skipping ownership of repo in safety consideration","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-16T05:33:17Z","receivedAt":"2025-10-16T05:33:53Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"Introduction\n------------\n\nAs a first step to allow making git more resistant against accidental\narbitrary code execution, Jeff King suggested in\n\n https://lore.kernel.org/git/20251009224317.77565-1-git@lohmann.sh/T/#m6cce96f9ae58a4341ae3fbbc02110e20547c58bc\n\nto make the \"safe.directory\" config enforceable.\n\nNote about the different patches\n--------------------------------\n\nPatches 1/5 and 2/5 are renaming of functions to clarify their\nfunctionality (especially needed once the additional options to mark\nrepositories as (un)safe are introduced)\n\nPatch 3/5 is a refactoring that on its own has no change in behavior,\nbut it makes Patch 5/5 cleaner by it now only adding the check for\n\"GIT_ASSUME_UNSAFE\" in one place instead of having to refactor it in\nparallel.\n\nPatch 4/5 adds `--allow-unsafe` flag to temporarily skip the\n\"safe.directory\" checks\n\nPatch 5/5 adds `--assume-unsafe` flag to skip ownership check when\nevaluating if a repository is to be considered safe. This allows e.g.\nrunning a command line git status only in repositories explicitly marked\nas a \"safe.directory\" to prevent accidental arbitrary code invocations.\n\nChanges since v2\n----------------\n\nThanks to Junio C Hamano for a thorough review!\n\n* patch 2: rename function with more concise name\n* patch 2: fix commit message mentioning function name from patch 1\n  instead\n* patch 3: clarify that this patch on its own does not make sense, but\n  is preparation for a later commit. (Sorry if it took additional time\n  to understand the patch - I tried \"not to tell the future\" with the\n  commit message, but an explicit \"this is preparation for a later\n  patch\" is much better)\n* patch 5: add missing newline in test\n\nNote: I accidentally replied to the review comment with v2 instead of\nthe cover letter:\n\n https://lore.kernel.org/git/20251013214608.33581-1-git@lohmann.sh/#t\n\nTests\n-----\n\nRan all tests. On my setup even on the main branch\nt/t3900-i18n-commit.sh is failing the three test cases on ISO-2022-JP.\nSince no code related to commit or i18n was changed, it is very unlikely\nthat this patch set has any impact on said tests.\n\nRange-diff since v2\n-------------------\n\nMichael Lohmann (5):\n  setup: rename `ensure_safe_repository()` for clarity\n  setup: rename `die_upon_unsafe_repo()` to align with check\n  setup: refactor `ensure_safe_repository()` testing priorities\n  setup: allow temporary bypass of `ensure_safe_repository()` checks\n  setup: allow not marking self owned repos as safe in\n    `ensure_safe_repository()`\n\n Documentation/config/safe.adoc    |  9 ++++\n Documentation/git.adoc            | 25 +++++++++++\n builtin/clone.c                   |  2 +-\n environment.h                     |  2 +\n git.c                             |  9 ++++\n path.c                            |  4 +-\n setup.c                           | 45 ++++++++++++++------\n setup.h                           |  2 +-\n t/meson.build                     |  1 +\n t/t0036-allow-unsafe-directory.sh | 71 +++++++++++++++++++++++++++++++\n 10 files changed, 154 insertions(+), 16 deletions(-)\n create mode 100755 t/t0036-allow-unsafe-directory.sh\n\nRange-diff against v2:\n1:  3f8805eb96 = 1:  5d886c0461 setup: rename `ensure_safe_repository()` for clarity\n2:  aa09159dec ! 2:  6fbbf4185d setup: rename `die_upon_assumed_unsafe_repo()` to align with check\n    @@ Metadata\n     Author: Michael Lohmann <git@lohmann.sh>\n     \n      ## Commit message ##\n    -    setup: rename `die_upon_assumed_unsafe_repo()` to align with check\n    +    setup: rename `die_upon_unsafe_repo()` to align with check\n     \n         This function dies if the repo in question is deemed to be unsafe and\n         the ownership is only part of the verification. In addition it already\n         checks for \"safe.directory\" config, making the name\n    -    `ensure_valid_ownership()` not expressive.\n    +    `die_upon_dubious_ownership()` not expressive.\n         When additional options to check if a repository is considered to be\n         safe are added, this name is more indicative of the content.\n     \n    +    Helped-by: Junio C Hamano <gitster@pobox.com>\n         Signed-off-by: Michael Lohmann <git@lohmann.sh>\n     \n      ## builtin/clone.c ##\n    @@ builtin/clone.c: static void copy_or_link_directory(struct strbuf *src, struct s\n      \t * potentially-untrusted user. We thus refuse to do so by default.\n      \t */\n     -\tdie_upon_dubious_ownership(NULL, NULL, src_repo);\n    -+\tdie_upon_assumed_unsafe_repo(NULL, NULL, src_repo);\n    ++\tdie_upon_unsafe_repo(NULL, NULL, src_repo);\n      \n      \tmkdir_if_missing(dest->buf, 0777);\n      \n    @@ path.c: const char *enter_repo(const char *path, unsigned flags)\n      \t\tgitfile = read_gitfile(used_path.buf);\n      \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n     -\t\t\tdie_upon_dubious_ownership(gitfile, NULL, used_path.buf);\n    -+\t\t\tdie_upon_assumed_unsafe_repo(gitfile, NULL, used_path.buf);\n    ++\t\t\tdie_upon_unsafe_repo(gitfile, NULL, used_path.buf);\n      \t\tif (gitfile) {\n      \t\t\tstrbuf_reset(&used_path);\n      \t\t\tstrbuf_addstr(&used_path, gitfile);\n    @@ path.c: const char *enter_repo(const char *path, unsigned flags)\n      \t\tconst char *gitfile = read_gitfile(path);\n      \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n     -\t\t\tdie_upon_dubious_ownership(gitfile, NULL, path);\n    -+\t\t\tdie_upon_assumed_unsafe_repo(gitfile, NULL, path);\n    ++\t\t\tdie_upon_unsafe_repo(gitfile, NULL, path);\n      \t\tif (gitfile)\n      \t\t\tpath = gitfile;\n      \t\tif (chdir(path))\n    @@ setup.c: static int ensure_safe_repository(const char *gitfile,\n      }\n      \n     -void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n    -+void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n    ++void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n      \t\t\t\tconst char *gitdir)\n      {\n      \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\n    @@ setup.h: const char *resolve_gitdir_gently(const char *suspect, int *return_erro\n       * added, for bare ones their git directory.\n       */\n     -void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n    -+void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n    ++void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n      \t\t\t\tconst char *gitdir);\n      \n      void setup_work_tree(void);\n3:  ad4f64fdb8 ! 3:  1925b3f093 setup: refactor `ensure_safe_repository()` testing priorities\n    @@ Metadata\n      ## Commit message ##\n         setup: refactor `ensure_safe_repository()` testing priorities\n     \n    -    The implicit ownership test takes precedence over the explicit\n    -    allow-listing of a path by \"safe.directory\" config. Sort by \"priority\"\n    -    (explicitness). This also allows to more easily integrate additional\n    -    checks.\n    -\n    -    Make the explicit safe.directory check take precedence over owner check.\n    +    With the current code, this change does not make any difference because\n    +    there is no explicit rule that lets you reject a directory that the\n    +    ownership-based rule may accept.  In a later step in this series,\n    +    however, we will introduce a mechanism to allow such an explicit rule,\n    +    at which point the order of checks, i.e. seeing the explicit rule reject\n    +    a directory and failing the operation before consulting the\n    +    ownership-based rule, will start to matter.  As a preliminary change,\n    +    reorder the existing checks.\n     \n         Signed-off-by: Michael Lohmann <git@lohmann.sh>\n     \n    @@ setup.c: static int ensure_safe_repository(const char *gitfile,\n     +\treturn 0;\n      }\n      \n    - void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n    + void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n4:  db31fdef4e ! 4:  385250b16c setup: allow temporary bypass of `ensure_safe_repository()` checks\n    @@ setup.c: static int ensure_safe_repository(const char *gitfile,\n      \t/*\n      \t * normalize the data.path for comparison with normalized paths\n      \t * that come from the configuration file.  The path is unsafe\n    -@@ setup.c: void die_upon_assumed_unsafe_repo(const char *gitfile, const char *worktree,\n    +@@ setup.c: void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n      \t      \"%s\"\n      \t      \"To add an exception for this directory, call:\\n\"\n      \t      \"\\n\"\n5:  6f710af1da ! 5:  ba8eb928b4 setup: allow not marking self owned repos as safe in `ensure_safe_repository()`\n    @@ t/t0036-allow-unsafe-directory.sh: test_expect_success 'GIT_ALLOW_UNSAFE bool al\n     +\ttest_must_fail git --assume-unsafe status 2>err &&\n     +\tgrep \"dubious ownership\" err\n     +'\n    ++\n     +test_expect_success 'GIT_ASSUME_UNSAFE prevents execution if not in safe.directory' '\n     +\ttest_must_fail env GIT_ASSUME_UNSAFE=1 \\\n     +\t\t\t   git status 2>err &&\n-- \n2.51.1.476.g147428281d\n\n"},{"id":"528930","messageId":"20251016053322.44495-3-git@lohmann.sh","threadId":"64302","inReplyTo":"20251016053322.44495-1-git@lohmann.sh","subject":"[PATCH v3 2/5] setup: rename `die_upon_unsafe_repo()` to align with check","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-16T05:33:19Z","receivedAt":"2025-10-16T05:33:53Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"This function dies if the repo in question is deemed to be unsafe and\nthe ownership is only part of the verification. In addition it already\nchecks for \"safe.directory\" config, making the name\n`die_upon_dubious_ownership()` not expressive.\nWhen additional options to check if a repository is considered to be\nsafe are added, this name is more indicative of the content.\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n builtin/clone.c | 2 +-\n path.c          | 4 ++--\n setup.c         | 2 +-\n setup.h         | 2 +-\n 4 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex c990f398ef..08b04f5cf2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -263,7 +263,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,\n \t * operation as the hardlinked files can be rewritten at will by the\n \t * potentially-untrusted user. We thus refuse to do so by default.\n \t */\n-\tdie_upon_dubious_ownership(NULL, NULL, src_repo);\n+\tdie_upon_unsafe_repo(NULL, NULL, src_repo);\n \n \tmkdir_if_missing(dest->buf, 0777);\n \ndiff --git a/path.c b/path.c\nindex 7f56eaf993..c2ea450304 100644\n--- a/path.c\n+++ b/path.c\n@@ -810,7 +810,7 @@ const char *enter_repo(const char *path, unsigned flags)\n \t\t\treturn NULL;\n \t\tgitfile = read_gitfile(used_path.buf);\n \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, used_path.buf);\n+\t\t\tdie_upon_unsafe_repo(gitfile, NULL, used_path.buf);\n \t\tif (gitfile) {\n \t\t\tstrbuf_reset(&used_path);\n \t\t\tstrbuf_addstr(&used_path, gitfile);\n@@ -822,7 +822,7 @@ const char *enter_repo(const char *path, unsigned flags)\n \telse {\n \t\tconst char *gitfile = read_gitfile(path);\n \t\tif (!(flags & ENTER_REPO_ANY_OWNER_OK))\n-\t\t\tdie_upon_dubious_ownership(gitfile, NULL, path);\n+\t\t\tdie_upon_unsafe_repo(gitfile, NULL, path);\n \t\tif (gitfile)\n \t\t\tpath = gitfile;\n \t\tif (chdir(path))\ndiff --git a/setup.c b/setup.c\nindex 2c41874774..c6e1204c05 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1333,7 +1333,7 @@ static int ensure_safe_repository(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n+void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n \t\t\t\tconst char *gitdir)\n {\n \tstruct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;\ndiff --git a/setup.h b/setup.h\nindex 8522fa8575..3f7ef03bf9 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -51,7 +51,7 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);\n  * config settings; for non-bare repositories, their worktree needs to be\n  * added, for bare ones their git directory.\n  */\n-void die_upon_dubious_ownership(const char *gitfile, const char *worktree,\n+void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n \t\t\t\tconst char *gitdir);\n \n void setup_work_tree(void);\n-- \n2.51.1.476.g147428281d\n\n"},{"id":"528931","messageId":"20251016053322.44495-5-git@lohmann.sh","threadId":"64302","inReplyTo":"20251016053322.44495-1-git@lohmann.sh","subject":"[PATCH v3 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-16T05:33:21Z","receivedAt":"2025-10-16T05:33:54Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"So far, the only option to allow executing git in what it considers to\nbe an \"unsafe context\" is to set this repository as \"safe.directory\". If\na user only wants to temporarily execute one command, they would need to\nset the path as safe, execute the command and then remove the path\nagain. Forgetting to do the latter would make the user vulnerable if\nthis repo was changed afterwards in a malicious way.\n\nAllow temporarily bypassing `ensure_safe_repository()` checks with a new\nflag \"--allow-unsafe\" or environment variable \"GIT_ALLOW_UNSAFE\".\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n Documentation/git.adoc            | 13 +++++++++++++\n environment.h                     |  1 +\n git.c                             |  5 +++++\n setup.c                           | 13 +++++++++++--\n t/meson.build                     |  1 +\n t/t0036-allow-unsafe-directory.sh | 28 ++++++++++++++++++++++++++++\n 6 files changed, 59 insertions(+), 2 deletions(-)\n create mode 100755 t/t0036-allow-unsafe-directory.sh\n\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex ce099e78b8..7df51c38f9 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -14,6 +14,7 @@ SYNOPSIS\n     [-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     <command> [<args>]\n \n DESCRIPTION\n@@ -231,6 +232,12 @@ If you just want to run git as if it was started in `<path>` then use\n \tlinkgit:gitattributes[5]. This is equivalent to setting the\n \t`GIT_ATTR_SOURCE` environment variable.\n \n+--allow-unsafe::\n+\tTemporarily trust the repository regardless of \"safe.directory\"\n+\tconfiguration or ownership, potentially resulting in arbitrary code\n+\texecution by hooks or configuration settings. Equivalent to setting\n+\tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n+\n GIT COMMANDS\n ------------\n \n@@ -493,6 +500,12 @@ These environment variables apply to 'all' core Git commands. Nb: it\n is worth noting that they may be used/overridden by SCMS sitting above\n Git so take care if using a foreign front-end.\n \n+`GIT_ALLOW_UNSAFE`::\n+\tThis Boolean environment variable can be set to true to skip the\n+\tsafety checks of \"safe.directory\" configuration and if the user\n+\towns the repository before potentially executing arbitrary code\n+\tfrom hooks or config.\n+\n `GIT_INDEX_FILE`::\n \tThis environment variable specifies an alternate\n \tindex file. If not specified, the default of `$GIT_DIR/index`\ndiff --git a/environment.h b/environment.h\nindex 51898c99cd..ee9e1b9514 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -42,6 +42,7 @@\n #define GIT_OPTIONAL_LOCKS_ENVIRONMENT \"GIT_OPTIONAL_LOCKS\"\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n+#define GIT_ALLOW_UNSAFE \"GIT_ALLOW_UNSAFE\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/git.c b/git.c\nindex c5fad56813..a7581a6805 100644\n--- a/git.c\n+++ b/git.c\n@@ -42,6 +42,7 @@ const char git_usage_string[] =\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\\n\"\n \t   \"           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\\n\"\n \t   \"           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\\n\"\n+\t   \"           [--allow-unsafe]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -354,6 +355,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_ADVICE_ENVIRONMENT, \"0\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"--allow-unsafe\")) {\n+\t\t\tsetenv(GIT_ALLOW_UNSAFE, \"1\", 1);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n \t\t} else {\n \t\t\tfprintf(stderr, _(\"unknown option: %s\\n\"), cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/setup.c b/setup.c\nindex 5ec68be379..515d1eedc0 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1307,6 +1307,9 @@ static int ensure_safe_repository(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n+\tif (git_env_bool(\"GIT_ALLOW_UNSAFE\", 0))\n+\t\treturn 1;\n+\n \t/*\n \t * normalize the data.path for comparison with normalized paths\n \t * that come from the configuration file.  The path is unsafe\n@@ -1353,7 +1356,10 @@ void die_upon_unsafe_repo(const char *gitfile, const char *worktree,\n \t      \"%s\"\n \t      \"To add an exception for this directory, call:\\n\"\n \t      \"\\n\"\n-\t      \"\\tgit config --global --add safe.directory %s\"),\n+\t      \"\\tgit config --global --add safe.directory %s\\n\"\n+\t      \"\\n\"\n+\t      \"To temporarily bypass safety-checks, run 'git --allow-unsafe <command>'\\n\"\n+\t      \"or set the environment variable 'GIT_ALLOW_UNSAFE=true'.\"),\n \t    path, report.buf, quoted.buf);\n }\n \n@@ -1797,7 +1803,10 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\t\t      \"%s\"\n \t\t\t      \"To add an exception for this directory, call:\\n\"\n \t\t\t      \"\\n\"\n-\t\t\t      \"\\tgit config --global --add safe.directory %s\"),\n+\t\t\t      \"\\tgit config --global --add safe.directory %s\\n\"\n+\t\t\t      \"\\n\"\n+\t\t\t      \"To temporarily bypass safety-checks, run 'git --allow-unsafe <command>'\\n\"\n+\t\t\t      \"or set the environment variable 'GIT_ALLOW_UNSAFE=true'.\"),\n \t\t\t    dir.buf, report.buf, quoted.buf);\n \t\t}\n \t\t*nongit_ok = 1;\ndiff --git a/t/meson.build b/t/meson.build\nindex 401b24e50e..911cd45638 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -100,6 +100,7 @@ integration_tests = [\n   't0033-safe-directory.sh',\n   't0034-root-safe-directory.sh',\n   't0035-safe-bare-repository.sh',\n+  't0036-allow-unsafe-directory.sh',\n   't0040-parse-options.sh',\n   't0041-usage.sh',\n   't0050-filesystem.sh',\ndiff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh\nnew file mode 100755\nindex 0000000000..4b98e815ff\n--- /dev/null\n+++ b/t/t0036-allow-unsafe-directory.sh\n@@ -0,0 +1,28 @@\n+#!/bin/sh\n+\n+test_description='verify safe.directory checks'\n+\n+. ./test-lib.sh\n+\n+GIT_TEST_ASSUME_DIFFERENT_OWNER=1\n+export GIT_TEST_ASSUME_DIFFERENT_OWNER\n+\n+expect_rejected_dir () {\n+\ttest_must_fail git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+}\n+\n+test_expect_success 'safe.directory is not set' '\n+\texpect_rejected_dir\n+'\n+\n+test_expect_success '--allow-unsafe allows execution in unsafe directory' '\n+\tgit --allow-unsafe status\n+'\n+\n+test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '\n+\tenv GIT_ALLOW_UNSAFE=true \\\n+\t    git status\n+'\n+\n+test_done\n-- \n2.51.1.476.g147428281d\n\n"},{"id":"528932","messageId":"20251016053322.44495-6-git@lohmann.sh","threadId":"64302","inReplyTo":"20251016053322.44495-1-git@lohmann.sh","subject":"[PATCH v3 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`","fromName":"Michael Lohmann","fromEmail":"git@lohmann.sh","sentAt":"2025-10-16T05:33:22Z","receivedAt":"2025-10-16T05:33:57Z","isPatch":true,"sender":{"key":"git@lohmann.sh","avatar":"https://avatars.githubusercontent.com/u/6929993?v=4"},"body":"Git considers all repositories as safe, if they are either\n - explicitly set in \"safe.directory\" config, or\n - the user owns the repo\n\nSince a user could unzip a folder they downloaded from the internet and\nunknown to them, it is a repository with malicious hooks/config, an\nattacker could easily get code execution. Even a command line prompt\nwould automatically trigger this if executing `git status` after\nentering the malicious directory.\n\nAllow not to automatically treat all repos owned by the user as safe.\nThis can either be done by \"--assume-unsafe\", the environment variable\n\"GIT_ASSUME_UNSAFE\" or by setting the configuration \"safe.assumeUnsafe\"\nin a safe context (so not the repo config, as it should not be able to\nallow list itself).\n\nSigned-off-by: Michael Lohmann <git@lohmann.sh>\n---\n Documentation/config/safe.adoc    |  9 +++++++\n Documentation/git.adoc            | 14 +++++++++-\n environment.h                     |  1 +\n git.c                             |  6 ++++-\n setup.c                           |  9 +++++++\n t/t0036-allow-unsafe-directory.sh | 43 +++++++++++++++++++++++++++++++\n 6 files changed, 80 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/config/safe.adoc b/Documentation/config/safe.adoc\nindex 2d45c98b12..d93881d6c0 100644\n--- a/Documentation/config/safe.adoc\n+++ b/Documentation/config/safe.adoc\n@@ -60,3 +60,12 @@ which id the original user has.\n If that is not what you would prefer and want git to only trust\n repositories that are owned by root instead, then you can remove\n the `SUDO_UID` variable from root's environment before invoking git.\n+\n+safe.assumeUnsafe::\n+\tBoolean to indicate that the ownership of a repository should not\n+\tbe taken into account when checking if the repository is safe. It\n+\twill prevent against accidental arbitrary code execution.\n++\n+To temporarily allow git execution in case of an assumed unsafe repository,\n+run the command with `--allow-unsafe`. To permanently trust this path, add\n+it to the `safe.directory` config.\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex 7df51c38f9..e24dafc2a9 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -14,7 +14,7 @@ SYNOPSIS\n     [-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>]\n \n DESCRIPTION\n@@ -238,6 +238,13 @@ If you just want to run git as if it was started in `<path>` then use\n \texecution by hooks or configuration settings. Equivalent to setting\n \tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n \n+--assume-unsafe::\n+\tPrevent arbitrary code execution by hooks or configuration if not\n+\texecuted in a \"safe.directory\". With setting this, filesystem ownership\n+\tof the repository in question no longer satisfies to mark it as safe.\n+\tEquivalent to setting `GIT_ASSUME_UNSAFE=1`. This is overridden if\n+\t`--allow-unsafe` is passed as well.\n+\n GIT COMMANDS\n ------------\n \n@@ -506,6 +513,11 @@ Git so take care if using a foreign front-end.\n \towns the repository before potentially executing arbitrary code\n \tfrom hooks or config.\n \n+`GIT_ASSUME_UNSAFE`::\n+\tThis Boolean environment variable can be set to true enforce\n+\texplicit \"safe.directory\" configuration for the repository. This\n+\tcan be overridden by setting `GIT_ALLOW_UNSAFE`.\n+\n `GIT_INDEX_FILE`::\n \tThis environment variable specifies an alternate\n \tindex file. If not specified, the default of `$GIT_DIR/index`\ndiff --git a/environment.h b/environment.h\nindex ee9e1b9514..89036a9460 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -43,6 +43,7 @@\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n #define GIT_ALLOW_UNSAFE \"GIT_ALLOW_UNSAFE\"\n+#define GIT_ASSUME_UNSAFE \"GIT_ASSUME_UNSAFE\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/git.c b/git.c\nindex a7581a6805..40ef89558d 100644\n--- a/git.c\n+++ b/git.c\n@@ -42,7 +42,7 @@ const char git_usage_string[] =\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\\n\"\n \t   \"           [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\\n\"\n \t   \"           [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\\n\"\n-\t   \"           [--allow-unsafe]\\n\"\n+\t   \"           [--allow-unsafe] [--assume-unsafe]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -359,6 +359,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tsetenv(GIT_ALLOW_UNSAFE, \"1\", 1);\n \t\t\tif (envchanged)\n \t\t\t\t*envchanged = 1;\n+\t\t} else if (!strcmp(cmd, \"--assume-unsafe\")) {\n+\t\t\tsetenv(GIT_ASSUME_UNSAFE, \"1\", 1);\n+\t\t\tif (envchanged)\n+\t\t\t\t*envchanged = 1;\n \t\t} else {\n \t\t\tfprintf(stderr, _(\"unknown option: %s\\n\"), cmd);\n \t\t\tusage(git_usage_string);\ndiff --git a/setup.c b/setup.c\nindex 515d1eedc0..0c056438a6 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1238,6 +1238,12 @@ static int safe_directory_cb(const char *key, const char *value,\n {\n \tstruct safe_directory_data *data = d;\n \n+\tif (!strcmp(key, \"safe.assumeunsafe\")) {\n+\t\tif (git_config_bool(key, value))\n+\t\t\tsetenv(GIT_ASSUME_UNSAFE, value, 0);\n+\t\treturn 0;\n+\t}\n+\n \tif (strcmp(key, \"safe.directory\"))\n \t\treturn 0;\n \n@@ -1330,6 +1336,9 @@ static int ensure_safe_repository(const char *gitfile,\n \tif (data.is_safe)\n \t\treturn 1;\n \n+\tif (git_env_bool(\"GIT_ASSUME_UNSAFE\", 0))\n+\t\treturn 0;\n+\n \tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n \t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n \t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\ndiff --git a/t/t0036-allow-unsafe-directory.sh b/t/t0036-allow-unsafe-directory.sh\nindex 4b98e815ff..98087322a2 100755\n--- a/t/t0036-allow-unsafe-directory.sh\n+++ b/t/t0036-allow-unsafe-directory.sh\n@@ -25,4 +25,47 @@ test_expect_success 'GIT_ALLOW_UNSAFE bool allows unsafe directory' '\n \t    git status\n '\n \n+test_expect_success '--assume-unsafe prevents execution if not in safe.directory' '\n+\tsane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&\n+\tgit status &&\n+\ttest_must_fail git --assume-unsafe status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'GIT_ASSUME_UNSAFE prevents execution if not in safe.directory' '\n+\ttest_must_fail env GIT_ASSUME_UNSAFE=1 \\\n+\t\t\t   git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe on the command line' '\n+\ttest_must_fail git -c safe.assumeUnsafe=\"true\" status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe in the environment' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t    GIT_CONFIG_KEY_0=\"safe.assumeUnsafe\" \\\n+\t    GIT_CONFIG_VALUE_0=\"true\" \\\n+\t    git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'safe.assumeUnsafe in GIT_CONFIG_PARAMETERS' '\n+\ttest_must_fail env GIT_CONFIG_PARAMETERS=\"${SQ}safe.assumeUnsafe${SQ}=${SQ}true${SQ}\" \\\n+\t    git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'ignoring safe.assumeUnsafe in repo config' '\n+\tgit config safe.assumeUnsafe \"false\" &&\n+\tgit config --global safe.assumeUnsafe \"true\" &&\n+\ttest_must_fail git status 2>err &&\n+\tgrep \"dubious ownership\" err\n+'\n+\n+test_expect_success 'allow-unsafe must override assume-unsafe' '\n+\tenv GIT_ASSUME_UNSAFE=1 git --allow-unsafe status\n+'\n+\n test_done\n-- \n2.51.1.476.g147428281d\n\n"},{"id":"528990","messageId":"xmqqv7ke3axu.fsf@gitster.g","threadId":"64302","inReplyTo":"20251016053322.44495-5-git@lohmann.sh","subject":"Re: [PATCH v3 4/5] setup: allow temporary bypass of `ensure_safe_repository()` checks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-16T19:26:21Z","receivedAt":"2025-10-16T19:26:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Lohmann <git@lohmann.sh> writes:\n\n> So far, the only option to allow executing git in what it considers to\n> be an \"unsafe context\" is to set this repository as \"safe.directory\". If\n> a user only wants to temporarily execute one command, they would need to\n> set the path as safe, execute the command and then remove the path\n> again. Forgetting to do the latter would make the user vulnerable if\n> this repo was changed afterwards in a malicious way.\n\nIf you want to do a one-shot thing, wouldn't ...\n\n\t$ cd $there\n\t$ GIT_DIR=$(pwd)/.git GIT_WORK_TREE=$(pwd) git ...\n\n... be more or less the standard practice?  If you are at the top\nlevel of the working tree (which is why the above example uses\n$(pwd)/.git for GIT_DIR), you do not even have to specify\nGIT_WORK_TREE and get away with\n\n\t$ GIT_DIR=.git git ...\n\nIn other words, the above argument does not sound like a very strong\njustification.\n\n> +--allow-unsafe::\n> +\tTemporarily trust the repository regardless of \"safe.directory\"\n> +\tconfiguration or ownership, potentially resulting in arbitrary code\n> +\texecution by hooks or configuration settings.\n\nAs the only justification for this new feature to exist that was\nexplained in the proposed log message was \"one shot execution\", this\ncommand line option does look justifiable.  Even though with the\ncurrent system, you do not have to muck with configuration files and\nonly have to set the GIT_DIR environment variable, passing this\ncommand line option that does not take a value may still be slightly\neasier.\n\n> + Equivalent to setting\n> +\tthe environment variable `GIT_ALLOW_UNSAFE=1`.\n\nBut such an enviornment variable is not justified.  Setting an\nengironment variable would last until you unset it, and it implies\nthat it is no longer a single shot use case that this new feature\ntargets.\n\n> +`GIT_ALLOW_UNSAFE`::\n> +\tThis Boolean environment variable can be set to true to skip the\n> +\tsafety checks of \"safe.directory\" configuration and if the user\n> +\towns the repository before potentially executing arbitrary code\n> +\tfrom hooks or config.\n\nPlease don't add this.  It has the same \"Forgetting to unset the\nenvironment variable will make the user vulnerable\" downside as\ntemporarily editing your configuration file.\n\nNot convinced why this feature must exist, at least not yet.\n"},{"id":"528992","messageId":"xmqqo6q63al6.fsf@gitster.g","threadId":"64302","inReplyTo":"20251016053322.44495-6-git@lohmann.sh","subject":"Re: [PATCH v3 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-16T19:33:57Z","receivedAt":"2025-10-16T19:33:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Lohmann <git@lohmann.sh> writes:\n\n> +safe.assumeUnsafe::\n> +--assume-unsafe::\n> +`GIT_ASSUME_UNSAFE`::\n\nI haven't thought things through thoroughly yet, but this probably\nis a good thing to have.  I cannot say the same to [4/5], though.\n\n> @@ -1330,6 +1336,9 @@ static int ensure_safe_repository(const char *gitfile,\n>  \tif (data.is_safe)\n>  \t\treturn 1;\n>  \n> +\tif (git_env_bool(\"GIT_ASSUME_UNSAFE\", 0))\n> +\t\treturn 0;\n> +\n>  \tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n>  \t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n>  \t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n\nI think you didn't have to do anything in [3/5] for this, though.\n\nIt is sufficient to pretend as if GIT_TEST_ASSUME_DIFFERENT_OWNER is\nset when GIT_ASSUME_UNSAFE (and its config/option equivalents) is\nset, no?  IOW, wouldn't it be equivalent to your series, if you\ndropped [3/5] and replace this hunk with the following?\n\n setup.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git i/setup.c w/setup.c\nindex 7086741e6c..e3c81a6fae 100644\n--- i/setup.c\n+++ w/setup.c\n@@ -1307,7 +1307,8 @@ static int ensure_valid_ownership(const char *gitfile,\n {\n \tstruct safe_directory_data data = { 0 };\n \n-\tif (!git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n+\tif (!git_env_bool(\"GIT_ASSUME_UNSAFE\", 0) &&\n+\t    !git_env_bool(\"GIT_TEST_ASSUME_DIFFERENT_OWNER\", 0) &&\n \t    (!gitfile || is_path_owned_by_current_user(gitfile, report)) &&\n \t    (!worktree || is_path_owned_by_current_user(worktree, report)) &&\n \t    (!gitdir || is_path_owned_by_current_user(gitdir, report)))\n\n"},{"id":"528995","messageId":"xmqqjz0u39g0.fsf@gitster.g","threadId":"64302","inReplyTo":"20251016053322.44495-6-git@lohmann.sh","subject":"Re: [PATCH v3 5/5] setup: allow not marking self owned repos as safe in `ensure_safe_repository()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-10-16T19:58:39Z","receivedAt":"2025-10-16T19:58:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Michael Lohmann <git@lohmann.sh> writes:\n\n> Git considers all repositories as safe, if they are either\n>  - explicitly set in \"safe.directory\" config, or\n>  - the user owns the repo\n\nIf you are going to reroll this step, please add a few more cases to\nthe list above.  There are other code paths in setup.c that does not\ncall ensure_valid_ownership().  Treating an explicitly specified git\ndirectory as safe is one of them (there may or may not be others, I\ndidn't check).\n"}]}