Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

safe mode to disable executing any external programs except git#2029

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
eighthave wants to merge1 commit intogitpython-developers:main
base:main
Choose a base branch
Loading
fromeighthave:safe-mode

Conversation

eighthave
Copy link

@eighthaveeighthave commentedMay 26, 2025
edited
Loading

As described in#2020, here is the core implementation of "safe mode". The core idea is to set up operations so that external programs are not executed bygit. This has been a major source of vulnerabilities.

This means that network connections are limited to HTTPS. As much as possible, this will rewrite remote URLs to HTTPS. This is necessary so that submodules work even when they do not use HTTPS URLs, as long as they are public, HTTPS-accessible repos.

This is a draft to confirm the approach. Then I will follow up and polish everything for merging.

closes#2020

Copy link
Member

@ByronByron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thanks a lot for the preview - I see now how this can work and like that it seems to be minimally invasive overall.

I assume that the testing will primarily be done by hand for ease of use but hope that some sort of 'practical' test-case could be contributed as well.

@eighthave
Copy link
Author

I would like to write tests, I looked around through the test suite, but it still escapes me how to structure these tests. Since these options affect howgit is executed, it seems like it would have to connect to the network? Or is there a test that already replaces the protocol helper, e.g.git-remote-https, to avoid network access?

@Byron
Copy link
Member

It really isn't easy to test it at all, and probably impossible to test it exhaustively. So I am fine admitting defeat on this one, particularly because the tests I could imagine would be so specificand spotty that they barely have any value.

@gcmarx
Copy link
Contributor

if we can only rungit, that could mean we pick#2027 over#2026. I don't think it makes sense to have an allowlist of other executables GitPython could run.@EliahKagan, your thoughts?

@EliahKagan
Copy link
Member

EliahKagan commentedMay 28, 2025
edited
Loading

if we can only rungit, that could mean we pick#2027 over#2026. I don't think it makes sense to have an allowlist of other executables GitPython could run.@EliahKagan, your thoughts?

I haven't reviewed this draft PR in detail, and I also don't know what it will be like and how it will document the "safe mode" feature once it's done. But my first impression is that the changes being proposed here should not directly impose any requirements on whetheris_cygwin_git runs an external subprocess besidesgit:

  • Although the PR title makes it seem like this prevents all external subprocesses exceptgit from being executed, it is otherwise described more specifically to preventgit subprocesses from executing other external subprocesses.
  • If this covers all subprocesses GitPython creates, rather than only those throughgit, then there are already more subprocesses that need to be suppressed:at leastps.
  • If these changes are to be "minimally invasive" as characterized in#2029 (review), then I think they shouldn't impose requirements on code that doesn't operate on any repository or use any repository or URL specific paths or state.

However, that may not be the whole story. Although I don't think "safe mode" should prohibit the use of subprocesses inis_cygwin_git, it may be that they should be avoided in general when not needed for the same reasons. As currently written, it takes some effort to verify thatis_cygwin_git does not introduce an untrusted search path vulnerability.

Furthermore, regarding the use ofuname inis_cygwin_git, while I think it's probably not a vulnerability, it's not really ideal to assume adjacent executables are similarly trusted. It might be considered a valuable security enhancement to avoid assuming that just because an executable has a standard name and resides in the same directory asgit that it is safe or reasonable to execute it.

Copy link
Member

@EliahKaganEliahKagan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I noticed a couple of things related to unusual but plausiblecommand arguments.

@eighthave
Copy link
Author

FYI this is my very simple test suite:

#!/usr/bin/python3importosimportgitimporttempfileforurlin ['git@gitlab.com:fdroid/ci-test-app.git','ssh://gitlab.com/fdroid/ci-test-app.git','git://gitlab.com/fdroid/ci-test-app.git',]:print('====================',url)d=tempfile.mkdtemp(prefix='foo_py_')repo=git.Repo.clone_from(url,d,safe=True)# clone from fake URL should prompt for passwordd=tempfile.mkdtemp(prefix='foo_py_')forurlin ['https://github.com/asdfasdfasdf/adsfasdfasdf.git','https://gitlab.com/asdfasdfasdf/adsfasdfasdf.git','https://codeberg.org/asdfasdfasdf/adsfasdfasdf.git',]:try:repo=git.Repo.clone_from(url,d,safe=True)exceptgit.exc.GitCommandErrorase:print('repo',e)url='https://gitlab.com/fdroid/ci-test-app.git'd=tempfile.mkdtemp(prefix='foo_py_')print(d)repo=git.Repo.init(d,safe=True)origin=repo.create_remote("origin",url)origin.fetch()d=tempfile.mkdtemp(prefix='foo_py_')print(d)repo=git.Repo.clone_from(url,d,safe=True)print(type(repo))print('SUCCESS')

@eighthaveeighthave marked this pull request as ready for reviewJune 2, 2025 09:01
@eighthaveeighthave changed the titleDraft: safe mode to disable executing any external programs except gitsafe mode to disable executing any external programs except gitJun 2, 2025
@Byron
Copy link
Member

I think I should leave it to@EliahKagan to review this PR, due to its nature of being very relevant to the security of the project.

To me it still is the question if there is a good case for supporting this - I'd think the answer should be "yes" if f-droid benefits, but I fear that it lures people into a false sense of safety. Maybesafe=true should be changed to something more specific at the very least, because it's definitely notsafe by all means.

eighthave reacted with thumbs up emoji

@eighthave
Copy link
Author

eighthave commentedJun 3, 2025
edited
Loading

F-Droid has been using this approach for years, and will switch to this code once it is merged. As for the name, I'm open to suggestions. I used the word "safe" following the example of parser libs I've seen, where it means features are disabled in the interest of security. Like ruamel.yaml uses "safe" mode to mean a YAML parser that does not parse anything but lists, dicts, and scalars (e.g. no objects). From what see, I think this clearly does make it safer to use.

Copy link
Member

@EliahKaganEliahKagan left a comment
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'll try to review this in full sometime soon! This mini-review is not that. Instead, this just points out a couple of things related directly to my previous comments. One is fairly minor and pertains to the clarity and technical accuracy of exception messages. The other is more significant: the current attempt to refuse to run the command in a shell does not always work.

@@ -1210,6 +1253,51 @@ def execute(
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
_logger.info(" ".join(redacted_command))

if self._safe:
if isinstance(command, str) or command[0] != self.GIT_PYTHON_GIT_EXECUTABLE:
Copy link
Member

@EliahKaganEliahKaganJun 4, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

To give clearer error messages, I recommend moving this check of thecommand type and value below theif shell check, and also splitting thiscommand check into two checks with two separate messages.

The reason I recommend doing this after theif shell check is that, if a shell would (be attempted to) be used, then the condition here could come outTrue even forgit commands. Technically the message is correct in that case, in that the shell is separate from thegit executable, but I don't think users would understand what was going on.

The reasons I recommend splitting this into separateif isinstance(command, str) andif command[0] != self.GIT_PYTHON_GIT_EXECUTABLE checks, with two separate exception messages, are:

  • On Windows, passing a string like"git version" is unambiguously an attempt to rungit with the argumentversion. So in that case the message would be incorrect (and very confusing, if someone is using GitPython in a Windows-only application and writingcommand arguments as strings).
  • It's possible for the message to be incorrect even outside Windows, because someone could pass"git" by itself (or whatever other valueGit.GIT_PYTHON_GIT_EXECUTABLE has been set to), either by accident or intentionally in order to see the summary of subcommands it outputs.
  • On any system, even if the message is correct, I think it's useful to distinguish the problem of passing a string rather than a sequence from the problem of passing a sequence that attempts to execute something that isn't a Git command.

Comment on lines +1262 to +1266
if shell:
raise UnsafeExecutionError(
redacted_command,
"Command cannot be executed in a shell when in safe mode.",
)
Copy link
Member

@EliahKaganEliahKaganJun 4, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This blocks using a shell when the reason the shell would be used is that ashell=True argument was passed explicitly. But it fails to block using a shell when the reason is instead thatGit.USE_SHELL has been set toTrue. See the first bullet point in#2029 (comment).

We can observe our own direct subprocess invocations (provided that they are done from Python and in the usual manner). Setting it up:

importsysimportgitdefpopen_hook(event,args):ifevent=="subprocess.Popen":print(event,args)sys.addaudithook(popen_hook)g=git.Git(".",safe=True)

Due to theGit instance being created withsafe=True, this rightly declines to execute anything:

g.execute(["git","version"],shell=True)# Blocked as of 07a3889.

But this executes a shell, even though it should also decline for the same reason, in the same way, and preferably through the same logic automatically:

git.Git.USE_SHELL=Trueg.execute(["git","version"])# NOT blocked as of 07a3889.

(This example is written in such a way that it demonstrates the problem on any platform, not just Windows, though exactly what unintendedgit … command is run in the shell differs across systems.)

The cause of the problem is that, at this point in the code, aNone value ofshell has not yet been replaced by the value of theUSE_SHELL class attribute. Theif shell logic should appearafter thisif shell is None check, instead of before:

GitPython/git/cmd.py

Lines 1343 to 1349 in07a3889

ifshellisNone:
# Get the value of USE_SHELL with no deprecation warning. Do this without
# warnings.catch_warnings, to avoid a race condition with application code
# configuring warnings. The value could be looked up in type(self).__dict__
# or Git.__dict__, but those can break under some circumstances. This works
# the same as self.USE_SHELL in more situations; see Git.__getattribute__.
shell=super().__getattribute__("USE_SHELL")

Although one option would be to move the newif self._safe code lower,that code for checking ifshell is None (and resettingshell to the value of theUSE_SHELL class attribute) could probably instead just be moved higher.

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

@ByronByronByron left review comments

@EliahKaganEliahKaganEliahKagan requested changes

Assignees
No one assigned
Labels
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

I would like to contribute a "safe mode" for working with untrusted repos
4 participants
@eighthave@Byron@gcmarx@EliahKagan

[8]ページ先頭

©2009-2025 Movatter.jp