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

Commit07a3889

Browse files
committed
safe mode to disable executing any external programs except git
1 parent751cb2d commit07a3889

File tree

3 files changed

+236
-6
lines changed

3 files changed

+236
-6
lines changed

‎git/cmd.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
CommandError,
2727
GitCommandError,
2828
GitCommandNotFound,
29+
UnsafeExecutionError,
2930
UnsafeOptionError,
3031
UnsafeProtocolError,
3132
)
@@ -398,6 +399,7 @@ class Git(metaclass=_GitMeta):
398399

399400
__slots__= (
400401
"_working_dir",
402+
"_safe",
401403
"cat_file_all",
402404
"cat_file_header",
403405
"_version_info",
@@ -944,17 +946,56 @@ def __del__(self) -> None:
944946
self._stream.read(bytes_left+1)
945947
# END handle incomplete read
946948

947-
def__init__(self,working_dir:Union[None,PathLike]=None)->None:
949+
def__init__(self,working_dir:Union[None,PathLike]=None,safe:bool=False)->None:
948950
"""Initialize this instance with:
949951
950952
:param working_dir:
951953
Git directory we should work in. If ``None``, we always work in the current
952954
directory as returned by :func:`os.getcwd`.
953955
This is meant to be the working tree directory if available, or the
954956
``.git`` directory in case of bare repositories.
957+
958+
:param safe:
959+
Lock down the configuration to make it as safe as possible
960+
when working with publicly accessible, untrusted
961+
repositories. This disables all known options that can run
962+
external programs and limits networking to the HTTP protocol
963+
via ``https://`` URLs. This might not cover Git config
964+
options that were added since this was implemented, or
965+
options that have unknown exploit vectors. It is a best
966+
effort defense rather than an exhaustive protection measure.
967+
968+
In order to make this more likely to work with submodules,
969+
some attempts are made to rewrite remote URLs to ``https://``
970+
using `insteadOf` in the config. This might not work on all
971+
projects, so submodules should always use ``https://`` URLs.
972+
973+
:envvar:`GIT_TERMINAL_PROMPT` is set to `false` and these
974+
environment variables are forced to `/bin/true`:
975+
:envvar:`GIT_ASKPASS`, :envvar:`GIT_EDITOR`,
976+
:envvar:`GIT_PAGER`, :envvar:`GIT_SSH`,
977+
:envvar:`GIT_SSH_COMMAND`, and :envvar:`SSH_ASKPASS`.
978+
979+
Git config options are supplied via the command line to set
980+
up key parts of safe mode.
981+
982+
- Direct options for executing external commands are set to ``/bin/true``:
983+
``core.askpass``, ``core.sshCommand`` and ``credential.helper``.
984+
985+
- External password prompts are disabled by skipping authentication using
986+
``http.emptyAuth=true``.
987+
988+
- Any use of an fsmonitor daemon is disabled using ``core.fsmonitor=false``.
989+
990+
- Hook scripts are disabled using ``core.hooksPath=/dev/null``.
991+
992+
It was not possible to cover all config items that might execute an external
993+
command, for example, ``receive.procReceiveRefs``,
994+
``uploadpack.packObjectsHook`` and ``remote.<name>.vcs``.
955995
"""
956996
super().__init__()
957997
self._working_dir=expand_path(working_dir)
998+
self._safe=safe
958999
self._git_options:Union[List[str],Tuple[str, ...]]= ()
9591000
self._persistent_git_options:List[str]= []
9601001

@@ -1201,6 +1242,8 @@ def execute(
12011242
12021243
:raise git.exc.GitCommandError:
12031244
1245+
:raise git.exc.UnsafeExecutionError:
1246+
12041247
:note:
12051248
If you add additional keyword arguments to the signature of this method, you
12061249
must update the ``execute_kwargs`` variable housed in this module.
@@ -1210,6 +1253,51 @@ def execute(
12101253
ifself.GIT_PYTHON_TRACEand (self.GIT_PYTHON_TRACE!="full"oras_process):
12111254
_logger.info(" ".join(redacted_command))
12121255

1256+
ifself._safe:
1257+
ifisinstance(command,str)orcommand[0]!=self.GIT_PYTHON_GIT_EXECUTABLE:
1258+
raiseUnsafeExecutionError(
1259+
redacted_command,
1260+
f"Only{self.GIT_PYTHON_GIT_EXECUTABLE} can be executed when in safe mode.",
1261+
)
1262+
ifshell:
1263+
raiseUnsafeExecutionError(
1264+
redacted_command,
1265+
"Command cannot be executed in a shell when in safe mode.",
1266+
)
1267+
config_args= [
1268+
"-c",
1269+
"core.askpass=/bin/true",
1270+
"-c",
1271+
"core.fsmonitor=false",
1272+
"-c",
1273+
"core.hooksPath=/dev/null",
1274+
"-c",
1275+
"core.sshCommand=/bin/true",
1276+
"-c",
1277+
"credential.helper=/bin/true",
1278+
"-c",
1279+
"http.emptyAuth=true",
1280+
"-c",
1281+
"protocol.allow=never",
1282+
"-c",
1283+
"protocol.https.allow=always",
1284+
"-c",
1285+
"url.https://bitbucket.org/.insteadOf=git@bitbucket.org:",
1286+
"-c",
1287+
"url.https://codeberg.org/.insteadOf=git@codeberg.org:",
1288+
"-c",
1289+
"url.https://github.com/.insteadOf=git@github.com:",
1290+
"-c",
1291+
"url.https://gitlab.com/.insteadOf=git@gitlab.com:",
1292+
"-c",
1293+
"url.https://.insteadOf=git://",
1294+
"-c",
1295+
"url.https://.insteadOf=http://",
1296+
"-c",
1297+
"url.https://.insteadOf=ssh://",
1298+
]
1299+
command= [command.pop(0)]+config_args+command
1300+
12131301
# Allow the user to have the command executed in their working dir.
12141302
try:
12151303
cwd=self._working_diroros.getcwd()# type: Union[None, str]
@@ -1227,6 +1315,15 @@ def execute(
12271315
# just to be sure.
12281316
env["LANGUAGE"]="C"
12291317
env["LC_ALL"]="C"
1318+
# Globally disable things that can execute commands, including password prompts.
1319+
ifself._safe:
1320+
env["GIT_ASKPASS"]="/bin/true"
1321+
env["GIT_EDITOR"]="/bin/true"
1322+
env["GIT_PAGER"]="/bin/true"
1323+
env["GIT_SSH"]="/bin/true"
1324+
env["GIT_SSH_COMMAND"]="/bin/true"
1325+
env["GIT_TERMINAL_PROMPT"]="false"
1326+
env["SSH_ASKPASS"]="/bin/true"
12301327
env.update(self._environment)
12311328
ifinline_envisnotNone:
12321329
env.update(inline_env)

‎git/exc.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ def __init__(
159159
super().__init__(command,status,stderr,stdout)
160160

161161

162+
classUnsafeExecutionError(CommandError):
163+
"""Thrown if anything but git is executed when in safe mode."""
164+
165+
162166
classCheckoutError(GitError):
163167
"""Thrown if a file could not be checked out from the index as it contained
164168
changes.

‎git/repo/base.py

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ class Repo:
131131
git_dir:PathLike
132132
"""The ``.git`` repository directory."""
133133

134+
safe:None
135+
"""Whether this is operating using restricted protocol and execution access."""
136+
134137
_common_dir:PathLike=""
135138

136139
# Precompiled regex
@@ -175,6 +178,7 @@ def __init__(
175178
odbt:Type[LooseObjectDB]=GitCmdObjectDB,
176179
search_parent_directories:bool=False,
177180
expand_vars:bool=True,
181+
safe:bool=False,
178182
)->None:
179183
R"""Create a new :class:`Repo` instance.
180184
@@ -204,6 +208,44 @@ def __init__(
204208
Please note that this was the default behaviour in older versions of
205209
GitPython, which is considered a bug though.
206210
211+
:param safe:
212+
Lock down the configuration to make it as safe as possible
213+
when working with publicly accessible, untrusted
214+
repositories. This disables all known options that can run
215+
external programs and limits networking to the HTTP protocol
216+
via ``https://`` URLs. This might not cover Git config
217+
options that were added since this was implemented, or
218+
options that have unknown exploit vectors. It is a best
219+
effort defense rather than an exhaustive protection measure.
220+
221+
In order to make this more likely to work with submodules,
222+
some attempts are made to rewrite remote URLs to ``https://``
223+
using `insteadOf` in the config. This might not work on all
224+
projects, so submodules should always use ``https://`` URLs.
225+
226+
:envvar:`GIT_TERMINAL_PROMPT` is set to `false` and these
227+
environment variables are forced to `/bin/true`:
228+
:envvar:`GIT_ASKPASS`, :envvar:`GIT_EDITOR`,
229+
:envvar:`GIT_PAGER`, :envvar:`GIT_SSH`,
230+
:envvar:`GIT_SSH_COMMAND`, and :envvar:`SSH_ASKPASS`.
231+
232+
Git config options are supplied via the command line to set
233+
up key parts of safe mode.
234+
235+
- Direct options for executing external commands are set to ``/bin/true``:
236+
``core.askpass``, ``core.sshCommand`` and ``credential.helper``.
237+
238+
- External password prompts are disabled by skipping authentication using
239+
``http.emptyAuth=true``.
240+
241+
- Any use of an fsmonitor daemon is disabled using ``core.fsmonitor=false``.
242+
243+
- Hook scripts are disabled using ``core.hooksPath=/dev/null``.
244+
245+
It was not possible to cover all config items that might execute an external
246+
command, for example, ``receive.procReceiveRefs``,
247+
``uploadpack.packObjectsHook`` and ``remote.<name>.vcs``.
248+
207249
:raise git.exc.InvalidGitRepositoryError:
208250
209251
:raise git.exc.NoSuchPathError:
@@ -235,6 +277,8 @@ def __init__(
235277
ifnotos.path.exists(epath):
236278
raiseNoSuchPathError(epath)
237279

280+
self.safe=safe
281+
238282
# Walk up the path to find the `.git` dir.
239283
curpath=epath
240284
git_dir=None
@@ -309,7 +353,7 @@ def __init__(
309353
# END working dir handling
310354

311355
self.working_dir:PathLike=self._working_tree_dirorself.common_dir
312-
self.git=self.GitCommandWrapperType(self.working_dir)
356+
self.git=self.GitCommandWrapperType(self.working_dir,safe)
313357

314358
# Special handling, in special times.
315359
rootpath=osp.join(self.common_dir,"objects")
@@ -1305,6 +1349,7 @@ def init(
13051349
mkdir:bool=True,
13061350
odbt:Type[GitCmdObjectDB]=GitCmdObjectDB,
13071351
expand_vars:bool=True,
1352+
safe:bool=False,
13081353
**kwargs:Any,
13091354
)->"Repo":
13101355
"""Initialize a git repository at the given path if specified.
@@ -1329,6 +1374,44 @@ def init(
13291374
information disclosure, allowing attackers to access the contents of
13301375
environment variables.
13311376
1377+
:param safe:
1378+
Lock down the configuration to make it as safe as possible
1379+
when working with publicly accessible, untrusted
1380+
repositories. This disables all known options that can run
1381+
external programs and limits networking to the HTTP protocol
1382+
via ``https://`` URLs. This might not cover Git config
1383+
options that were added since this was implemented, or
1384+
options that have unknown exploit vectors. It is a best
1385+
effort defense rather than an exhaustive protection measure.
1386+
1387+
In order to make this more likely to work with submodules,
1388+
some attempts are made to rewrite remote URLs to ``https://``
1389+
using `insteadOf` in the config. This might not work on all
1390+
projects, so submodules should always use ``https://`` URLs.
1391+
1392+
:envvar:`GIT_TERMINAL_PROMPT` is set to `false` and these
1393+
environment variables are forced to `/bin/true`:
1394+
:envvar:`GIT_ASKPASS`, :envvar:`GIT_EDITOR`,
1395+
:envvar:`GIT_PAGER`, :envvar:`GIT_SSH`,
1396+
:envvar:`GIT_SSH_COMMAND`, and :envvar:`SSH_ASKPASS`.
1397+
1398+
Git config options are supplied via the command line to set
1399+
up key parts of safe mode.
1400+
1401+
- Direct options for executing external commands are set to ``/bin/true``:
1402+
``core.askpass``, ``core.sshCommand`` and ``credential.helper``.
1403+
1404+
- External password prompts are disabled by skipping authentication using
1405+
``http.emptyAuth=true``.
1406+
1407+
- Any use of an fsmonitor daemon is disabled using ``core.fsmonitor=false``.
1408+
1409+
- Hook scripts are disabled using ``core.hooksPath=/dev/null``.
1410+
1411+
It was not possible to cover all config items that might execute an external
1412+
command, for example, ``receive.procReceiveRefs``,
1413+
``uploadpack.packObjectsHook`` and ``remote.<name>.vcs``.
1414+
13321415
:param kwargs:
13331416
Keyword arguments serving as additional options to the
13341417
:manpage:`git-init(1)` command.
@@ -1342,9 +1425,9 @@ def init(
13421425
os.makedirs(path,0o755)
13431426

13441427
# git command automatically chdir into the directory
1345-
git=cls.GitCommandWrapperType(path)
1428+
git=cls.GitCommandWrapperType(path,safe)
13461429
git.init(**kwargs)
1347-
returncls(path,odbt=odbt)
1430+
returncls(path,odbt=odbt,safe=safe)
13481431

13491432
@classmethod
13501433
def_clone(
@@ -1357,6 +1440,7 @@ def _clone(
13571440
multi_options:Optional[List[str]]=None,
13581441
allow_unsafe_protocols:bool=False,
13591442
allow_unsafe_options:bool=False,
1443+
safe:Union[bool,None]=None,
13601444
**kwargs:Any,
13611445
)->"Repo":
13621446
odbt=kwargs.pop("odbt",odb_default_type)
@@ -1418,7 +1502,11 @@ def _clone(
14181502
ifnotosp.isabs(path):
14191503
path=osp.join(git._working_dir,path)ifgit._working_dirisnotNoneelsepath
14201504

1421-
repo=cls(path,odbt=odbt)
1505+
# if safe is not explicitly defined, then the new Repo instance should inherit the safe value
1506+
ifsafeisNone:
1507+
safe=git._safe
1508+
1509+
repo=cls(path,odbt=odbt,safe=safe)
14221510

14231511
# Retain env values that were passed to _clone().
14241512
repo.git.update_environment(**git.environment())
@@ -1501,6 +1589,7 @@ def clone_from(
15011589
multi_options:Optional[List[str]]=None,
15021590
allow_unsafe_protocols:bool=False,
15031591
allow_unsafe_options:bool=False,
1592+
safe:bool=False,
15041593
**kwargs:Any,
15051594
)->"Repo":
15061595
"""Create a clone from the given URL.
@@ -1531,13 +1620,52 @@ def clone_from(
15311620
:param allow_unsafe_options:
15321621
Allow unsafe options to be used, like ``--upload-pack``.
15331622
1623+
:param safe:
1624+
Lock down the configuration to make it as safe as possible
1625+
when working with publicly accessible, untrusted
1626+
repositories. This disables all known options that can run
1627+
external programs and limits networking to the HTTP protocol
1628+
via ``https://`` URLs. This might not cover Git config
1629+
options that were added since this was implemented, or
1630+
options that have unknown exploit vectors. It is a best
1631+
effort defense rather than an exhaustive protection measure.
1632+
1633+
In order to make this more likely to work with submodules,
1634+
some attempts are made to rewrite remote URLs to ``https://``
1635+
using `insteadOf` in the config. This might not work on all
1636+
projects, so submodules should always use ``https://`` URLs.
1637+
1638+
:envvar:`GIT_TERMINAL_PROMPT` is set to `false` and these
1639+
environment variables are forced to `/bin/true`:
1640+
:envvar:`GIT_ASKPASS`, :envvar:`GIT_EDITOR`,
1641+
:envvar:`GIT_PAGER`, :envvar:`GIT_SSH`,
1642+
:envvar:`GIT_SSH_COMMAND`, and :envvar:`SSH_ASKPASS`.
1643+
1644+
Git config options are supplied via the command line to set
1645+
up key parts of safe mode.
1646+
1647+
- Direct options for executing external commands are set to ``/bin/true``:
1648+
``core.askpass``, ``core.sshCommand`` and ``credential.helper``.
1649+
1650+
- External password prompts are disabled by skipping authentication using
1651+
``http.emptyAuth=true``.
1652+
1653+
- Any use of an fsmonitor daemon is disabled using ``core.fsmonitor=false``.
1654+
1655+
- Hook scripts are disabled using ``core.hooksPath=/dev/null``.
1656+
1657+
It was not possible to cover all config items that might execute an external
1658+
command, for example, ``receive.procReceiveRefs``,
1659+
``uploadpack.packObjectsHook`` and ``remote.<name>.vcs``.
1660+
15341661
:param kwargs:
15351662
See the :meth:`clone` method.
15361663
15371664
:return:
15381665
:class:`Repo` instance pointing to the cloned directory.
1666+
15391667
"""
1540-
git=cls.GitCommandWrapperType(os.getcwd())
1668+
git=cls.GitCommandWrapperType(os.getcwd(),safe)
15411669
ifenvisnotNone:
15421670
git.update_environment(**env)
15431671
returncls._clone(
@@ -1549,6 +1677,7 @@ def clone_from(
15491677
multi_options,
15501678
allow_unsafe_protocols=allow_unsafe_protocols,
15511679
allow_unsafe_options=allow_unsafe_options,
1680+
safe=safe,
15521681
**kwargs,
15531682
)
15541683

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp