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

Commite6108c7

Browse files
committed
Block unsafe options and protocols by default
1 parent2625ed9 commite6108c7

File tree

5 files changed

+160
-36
lines changed

5 files changed

+160
-36
lines changed

‎git/cmd.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# This module is part of GitPython and is released under
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
from __future__importannotations
7+
importre
78
fromcontextlibimportcontextmanager
89
importio
910
importlogging
@@ -24,7 +25,7 @@
2425
fromgit.excimportCommandError
2526
fromgit.utilimportis_cygwin_git,cygpath,expand_path,remove_password_if_present
2627

27-
from .excimportGitCommandError,GitCommandNotFound
28+
from .excimportGitCommandError,GitCommandNotFound,UnsafeOptionError,UnsafeProtocolError
2829
from .utilimport (
2930
LazyMixin,
3031
stream_copy,
@@ -262,6 +263,8 @@ class Git(LazyMixin):
262263

263264
_excluded_= ("cat_file_all","cat_file_header","_version_info")
264265

266+
re_unsafe_protocol=re.compile("(.+)::.+")
267+
265268
def__getstate__(self)->Dict[str,Any]:
266269
returnslots_to_dict(self,exclude=self._excluded_)
267270

@@ -454,6 +457,48 @@ def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:
454457
url=url.replace("\\\\","\\").replace("\\","/")
455458
returnurl
456459

460+
@classmethod
461+
defcheck_unsafe_protocols(cls,url:str)->None:
462+
"""
463+
Check for unsafe protocols.
464+
465+
Apart from the usual protocols (http, git, ssh),
466+
Git allows "remote helpers" that have the form `<transport>::<address>`,
467+
one of these helpers (`ext::`) can be used to invoke any arbitrary command.
468+
469+
See:
470+
471+
- https://git-scm.com/docs/gitremote-helpers
472+
- https://git-scm.com/docs/git-remote-ext
473+
"""
474+
match=cls.re_unsafe_protocol.match(url)
475+
ifmatch:
476+
protocol=match.group(1)
477+
raiseUnsafeProtocolError(
478+
f"The `{protocol}::` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it."
479+
)
480+
481+
@classmethod
482+
defcheck_unsafe_options(cls,options:List[str],unsafe_options:List[str])->None:
483+
"""
484+
Check for unsafe options.
485+
486+
Some options that are passed to `git <command>` can be used to execute
487+
arbitrary commands, this are blocked by default.
488+
"""
489+
# Options can be of the form `foo` or `--foo bar` `--foo=bar`,
490+
# so we need to check if they start with "--foo" or if they are equal to "foo".
491+
bare_options= [
492+
option.lstrip("-")
493+
foroptioninunsafe_options
494+
]
495+
foroptioninoptions:
496+
forunsafe_option,bare_optioninzip(unsafe_options,bare_options):
497+
ifoption.startswith(unsafe_option)oroption==bare_option:
498+
raiseUnsafeOptionError(
499+
f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
500+
)
501+
457502
classAutoInterrupt(object):
458503
"""Kill/Interrupt the stored process instance once this instance goes out of scope. It is
459504
used to prevent processes piling up in case iterators stop reading.

‎git/exc.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,12 @@ class NoSuchPathError(GitError, OSError):
3737
"""Thrown if a path could not be access by the system."""
3838

3939

40-
classUnsafeOptionsUsedError(GitError):
41-
"""Thrown if unsafe protocols or options are passed without overridding."""
40+
classUnsafeProtocolError(GitError):
41+
"""Thrown if unsafe protocols are passed without being explicitly allowed."""
42+
43+
44+
classUnsafeOptionError(GitError):
45+
"""Thrown if unsafe options are passed without being explicitly allowed."""
4246

4347

4448
classCommandError(GitError):

‎git/remote.py

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,23 @@ class Remote(LazyMixin, IterableObj):
535535
__slots__= ("repo","name","_config_reader")
536536
_id_attribute_="name"
537537

538+
unsafe_git_fetch_options= [
539+
# This option allows users to execute arbitrary commands.
540+
# https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---upload-packltupload-packgt
541+
"--upload-pack",
542+
]
543+
unsafe_git_pull_options= [
544+
# This option allows users to execute arbitrary commands.
545+
# https://git-scm.com/docs/git-pull#Documentation/git-pull.txt---upload-packltupload-packgt
546+
"--upload-pack"
547+
]
548+
unsafe_git_push_options= [
549+
# This option allows users to execute arbitrary commands.
550+
# https://git-scm.com/docs/git-push#Documentation/git-push.txt---execltgit-receive-packgt
551+
"--receive-pack",
552+
"--exec",
553+
]
554+
538555
def__init__(self,repo:"Repo",name:str)->None:
539556
"""Initialize a remote instance
540557
@@ -611,7 +628,9 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator["Remote
611628
yieldRemote(repo,section[lbound+1 :rbound])
612629
# END for each configuration section
613630

614-
defset_url(self,new_url:str,old_url:Optional[str]=None,**kwargs:Any)->"Remote":
631+
defset_url(
632+
self,new_url:str,old_url:Optional[str]=None,allow_unsafe_protocols:bool=False,**kwargs:Any
633+
)->"Remote":
615634
"""Configure URLs on current remote (cf command git remote set_url)
616635
617636
This command manages URLs on the remote.
@@ -620,15 +639,17 @@ def set_url(self, new_url: str, old_url: Optional[str] = None, **kwargs: Any) ->
620639
:param old_url: when set, replaces this URL with new_url for the remote
621640
:return: self
622641
"""
642+
ifnotallow_unsafe_protocols:
643+
Git.check_unsafe_protocols(new_url)
623644
scmd="set-url"
624645
kwargs["insert_kwargs_after"]=scmd
625646
ifold_url:
626-
self.repo.git.remote(scmd,self.name,new_url,old_url,**kwargs)
647+
self.repo.git.remote(scmd,"--",self.name,new_url,old_url,**kwargs)
627648
else:
628-
self.repo.git.remote(scmd,self.name,new_url,**kwargs)
649+
self.repo.git.remote(scmd,"--",self.name,new_url,**kwargs)
629650
returnself
630651

631-
defadd_url(self,url:str,**kwargs:Any)->"Remote":
652+
defadd_url(self,url:str,allow_unsafe_protocols:bool=False,**kwargs:Any)->"Remote":
632653
"""Adds a new url on current remote (special case of git remote set_url)
633654
634655
This command adds new URLs to a given remote, making it possible to have
@@ -637,6 +658,8 @@ def add_url(self, url: str, **kwargs: Any) -> "Remote":
637658
:param url: string being the URL to add as an extra remote URL
638659
:return: self
639660
"""
661+
ifnotallow_unsafe_protocols:
662+
Git.check_unsafe_protocols(url)
640663
returnself.set_url(url,add=True)
641664

642665
defdelete_url(self,url:str,**kwargs:Any)->"Remote":
@@ -729,7 +752,7 @@ def stale_refs(self) -> IterableList[Reference]:
729752
returnout_refs
730753

731754
@classmethod
732-
defcreate(cls,repo:"Repo",name:str,url:str,**kwargs:Any)->"Remote":
755+
defcreate(cls,repo:"Repo",name:str,url:str,allow_unsafe_protocols:bool=False,**kwargs:Any)->"Remote":
733756
"""Create a new remote to the given repository
734757
:param repo: Repository instance that is to receive the new remote
735758
:param name: Desired name of the remote
@@ -739,7 +762,10 @@ def create(cls, repo: "Repo", name: str, url: str, **kwargs: Any) -> "Remote":
739762
:raise GitCommandError: in case an origin with that name already exists"""
740763
scmd="add"
741764
kwargs["insert_kwargs_after"]=scmd
742-
repo.git.remote(scmd,name,Git.polish_url(url),**kwargs)
765+
url=Git.polish_url(url)
766+
ifnotallow_unsafe_protocols:
767+
Git.check_unsafe_protocols(url)
768+
repo.git.remote(scmd,"--",name,url,**kwargs)
743769
returncls(repo,name)
744770

745771
# add is an alias
@@ -921,6 +947,8 @@ def fetch(
921947
progress:Union[RemoteProgress,None,"UpdateProgress"]=None,
922948
verbose:bool=True,
923949
kill_after_timeout:Union[None,float]=None,
950+
allow_unsafe_protocols:bool=False,
951+
allow_unsafe_options:bool=False,
924952
**kwargs:Any,
925953
)->IterableList[FetchInfo]:
926954
"""Fetch the latest changes for this remote
@@ -963,6 +991,14 @@ def fetch(
963991
else:
964992
args= [refspec]
965993

994+
ifnotallow_unsafe_protocols:
995+
forrefinargs:
996+
ifref:
997+
Git.check_unsafe_protocols(ref)
998+
999+
ifnotallow_unsafe_options:
1000+
Git.check_unsafe_options(options=list(kwargs.keys()),unsafe_options=self.unsafe_git_fetch_options)
1001+
9661002
proc=self.repo.git.fetch(
9671003
"--",self,*args,as_process=True,with_stdout=False,universal_newlines=True,v=verbose,**kwargs
9681004
)
@@ -976,6 +1012,8 @@ def pull(
9761012
refspec:Union[str,List[str],None]=None,
9771013
progress:Union[RemoteProgress,"UpdateProgress",None]=None,
9781014
kill_after_timeout:Union[None,float]=None,
1015+
allow_unsafe_protocols:bool=False,
1016+
allow_unsafe_options:bool=False,
9791017
**kwargs:Any,
9801018
)->IterableList[FetchInfo]:
9811019
"""Pull changes from the given branch, being the same as a fetch followed
@@ -990,6 +1028,16 @@ def pull(
9901028
# No argument refspec, then ensure the repo's config has a fetch refspec.
9911029
self._assert_refspec()
9921030
kwargs=add_progress(kwargs,self.repo.git,progress)
1031+
1032+
ifnotallow_unsafe_protocolsandrefspec:
1033+
ifisinstance(refspec,str):
1034+
Git.check_unsafe_protocols(refspec)
1035+
else:
1036+
forrefinrefspec:
1037+
Git.check_unsafe_protocols(ref)
1038+
ifnotallow_unsafe_options:
1039+
Git.check_unsafe_options(options=list(kwargs.keys()),unsafe_options=self.unsafe_git_pull_options)
1040+
9931041
proc=self.repo.git.pull(
9941042
"--",self,refspec,with_stdout=False,as_process=True,universal_newlines=True,v=True,**kwargs
9951043
)
@@ -1003,6 +1051,8 @@ def push(
10031051
refspec:Union[str,List[str],None]=None,
10041052
progress:Union[RemoteProgress,"UpdateProgress",Callable[...,RemoteProgress],None]=None,
10051053
kill_after_timeout:Union[None,float]=None,
1054+
allow_unsafe_protocols:bool=False,
1055+
allow_unsafe_options:bool=False,
10061056
**kwargs:Any,
10071057
)->IterableList[PushInfo]:
10081058
"""Push changes from source branch in refspec to target branch in refspec.
@@ -1033,6 +1083,17 @@ def push(
10331083
If the operation fails completely, the length of the returned IterableList will
10341084
be 0."""
10351085
kwargs=add_progress(kwargs,self.repo.git,progress)
1086+
1087+
ifnotallow_unsafe_protocolsandrefspec:
1088+
ifisinstance(refspec,str):
1089+
Git.check_unsafe_protocols(refspec)
1090+
else:
1091+
forrefinrefspec:
1092+
Git.check_unsafe_protocols(ref)
1093+
1094+
ifnotallow_unsafe_options:
1095+
Git.check_unsafe_options(options=list(kwargs.keys()),unsafe_options=self.unsafe_git_push_options)
1096+
10361097
proc=self.repo.git.push(
10371098
"--",
10381099
self,

‎git/repo/base.py

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
GitCommandError,
2626
InvalidGitRepositoryError,
2727
NoSuchPathError,
28-
UnsafeOptionsUsedError,
2928
)
3029
fromgit.indeximportIndexFile
3130
fromgit.objectsimportSubmodule,RootModule,Commit
@@ -133,7 +132,18 @@ class Repo(object):
133132
re_envvars=re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)")
134133
re_author_committer_start=re.compile(r"^(author|committer)")
135134
re_tab_full_line=re.compile(r"^\t(.*)$")
136-
re_config_protocol_option=re.compile(r"-[-]?c(|onfig)\s+protocol\.",re.I)
135+
136+
unsafe_git_clone_options= [
137+
# This option allows users to execute arbitrary commands.
138+
# https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---upload-packltupload-packgt
139+
"--upload-pack",
140+
"-u",
141+
# Users can override configuration variables
142+
# like `protocol.allow` or `core.gitProxy` to execute arbitrary commands.
143+
# https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt
144+
"--config",
145+
"-c",
146+
]
137147

138148
# invariants
139149
# represents the configuration level of a configuration file
@@ -961,7 +971,7 @@ def blame(
961971
file:str,
962972
incremental:bool=False,
963973
rev_opts:Optional[List[str]]=None,
964-
**kwargs:Any
974+
**kwargs:Any,
965975
)->List[List[Commit|List[str|bytes]|None]]|Iterator[BlameEntry]|None:
966976
"""The blame information for the given file at the given revision.
967977
@@ -1152,6 +1162,8 @@ def _clone(
11521162
odb_default_type:Type[GitCmdObjectDB],
11531163
progress:Union["RemoteProgress","UpdateProgress",Callable[...,"RemoteProgress"],None]=None,
11541164
multi_options:Optional[List[str]]=None,
1165+
allow_unsafe_protocols:bool=False,
1166+
allow_unsafe_options:bool=False,
11551167
**kwargs:Any,
11561168
)->"Repo":
11571169
odbt=kwargs.pop("odbt",odb_default_type)
@@ -1173,6 +1185,12 @@ def _clone(
11731185
multi=None
11741186
ifmulti_options:
11751187
multi=shlex.split(" ".join(multi_options))
1188+
1189+
ifnotallow_unsafe_protocols:
1190+
Git.check_unsafe_protocols(str(url))
1191+
ifnotallow_unsafe_optionsandmulti_options:
1192+
Git.check_unsafe_options(options=multi_options,unsafe_options=cls.unsafe_git_clone_options)
1193+
11761194
proc=git.clone(
11771195
multi,
11781196
"--",
@@ -1221,27 +1239,13 @@ def _clone(
12211239
# END handle remote repo
12221240
returnrepo
12231241

1224-
@classmethod
1225-
defunsafe_options(
1226-
cls,
1227-
url:str,
1228-
multi_options:Optional[List[str]]=None,
1229-
)->bool:
1230-
if"ext::"inurl:
1231-
returnTrue
1232-
ifmulti_optionsisnotNone:
1233-
ifany(["--upload-pack"inmforminmulti_options]):
1234-
returnTrue
1235-
ifany([re.match(cls.re_config_protocol_option,m)forminmulti_options]):
1236-
returnTrue
1237-
returnFalse
1238-
12391242
defclone(
12401243
self,
12411244
path:PathLike,
12421245
progress:Optional[Callable]=None,
12431246
multi_options:Optional[List[str]]=None,
1244-
unsafe_protocols:bool=False,
1247+
allow_unsafe_protocols:bool=False,
1248+
allow_unsafe_options:bool=False,
12451249
**kwargs:Any,
12461250
)->"Repo":
12471251
"""Create a clone from this repository.
@@ -1259,15 +1263,15 @@ def clone(
12591263
* All remaining keyword arguments are given to the git-clone command
12601264
12611265
:return: ``git.Repo`` (the newly cloned repo)"""
1262-
ifnotunsafe_protocolsandself.unsafe_options(path,multi_options):
1263-
raiseUnsafeOptionsUsedError(f"{path} requires unsafe_protocols flag")
12641266
returnself._clone(
12651267
self.git,
12661268
self.common_dir,
12671269
path,
12681270
type(self.odb),
12691271
progress,
12701272
multi_options,
1273+
allow_unsafe_protocols=allow_unsafe_protocols,
1274+
allow_unsafe_options=allow_unsafe_options,
12711275
**kwargs,
12721276
)
12731277

@@ -1279,7 +1283,8 @@ def clone_from(
12791283
progress:Optional[Callable]=None,
12801284
env:Optional[Mapping[str,str]]=None,
12811285
multi_options:Optional[List[str]]=None,
1282-
unsafe_protocols:bool=False,
1286+
allow_unsafe_protocols:bool=False,
1287+
allow_unsafe_options:bool=False,
12831288
**kwargs:Any,
12841289
)->"Repo":
12851290
"""Create a clone from the given URL
@@ -1300,9 +1305,17 @@ def clone_from(
13001305
git=cls.GitCommandWrapperType(os.getcwd())
13011306
ifenvisnotNone:
13021307
git.update_environment(**env)
1303-
ifnotunsafe_protocolsandcls.unsafe_options(url,multi_options):
1304-
raiseUnsafeOptionsUsedError(f"{url} requires unsafe_protocols flag")
1305-
returncls._clone(git,url,to_path,GitCmdObjectDB,progress,multi_options,**kwargs)
1308+
returncls._clone(
1309+
git,
1310+
url,
1311+
to_path,
1312+
GitCmdObjectDB,
1313+
progress,
1314+
multi_options,
1315+
allow_unsafe_protocols=allow_unsafe_protocols,
1316+
allow_unsafe_options=allow_unsafe_options,
1317+
**kwargs,
1318+
)
13061319

13071320
defarchive(
13081321
self,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp