mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
repos: git inherits only an allow-listed environment
git_env copied the whole process environment, and git consults its environment ahead of `-c`: GIT_ALLOW_PROTOCOL=file re-enabled file:// despite protocol.allow=never, and GIT_CONFIG_COUNT/KEY_*/VALUE_* could add an `insteadOf` that rewrites an approved URL to another host. Only PATH, HOME, TMPDIR, TZ and the SSL certificate paths are inherited now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1c214f4033
commit
1a28a5900a
2 changed files with 40 additions and 2 deletions
|
|
@ -219,9 +219,16 @@ def git_command(*args, config: tuple = ()) -> tuple:
|
|||
return GIT_BIN, *safety, *args
|
||||
|
||||
|
||||
# The only parts of WROLPi's environment git may see. Anything else (GIT_ALLOW_PROTOCOL, GIT_CONFIG_COUNT and
|
||||
# GIT_CONFIG_KEY_*/VALUE_*, GIT_CONFIG_PARAMETERS, GIT_SSL_NO_VERIFY, proxies, ...) could undo `git_command`'s
|
||||
# restrictions.
|
||||
GIT_ENV_ALLOWED = ('PATH', 'HOME', 'TMPDIR', 'TZ', 'SSL_CERT_FILE', 'SSL_CERT_DIR')
|
||||
|
||||
|
||||
def git_env() -> dict:
|
||||
"""Environment for git: never prompt for credentials, ignore the user's and system's git configs."""
|
||||
env = dict(os.environ)
|
||||
"""Environment for git: never prompt for credentials, ignore the user's and system's git configs, and inherit
|
||||
nothing that changes how git connects."""
|
||||
env = {k: v for k, v in os.environ.items() if k in GIT_ENV_ALLOWED}
|
||||
env.update(
|
||||
GIT_TERMINAL_PROMPT='0',
|
||||
GIT_ASKPASS='true',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from mock import mock
|
||||
|
||||
from modules.repos import lib
|
||||
from modules.repos.errors import InvalidRepo, RepoConflict
|
||||
|
|
@ -276,3 +279,31 @@ async def test_repo_directory_traversal(test_session, test_directory, async_clie
|
|||
lib.ReposConfig._import_repo(test_session, dict(url='https://github.com/a/b', directory='../outside'))
|
||||
test_session.rollback()
|
||||
assert test_session.query(Repository).one().directory == test_directory / 'repos/b'
|
||||
|
||||
|
||||
def test_git_env_is_not_inherited():
|
||||
"""The process environment cannot undo git_command's restrictions."""
|
||||
hostile = dict(
|
||||
GIT_ALLOW_PROTOCOL='file', GIT_CONFIG_COUNT='1', GIT_CONFIG_KEY_0='url.https://127.0.0.1/.insteadOf',
|
||||
GIT_CONFIG_VALUE_0='https://example.com/', GIT_CONFIG_PARAMETERS="'protocol.allow'='always'",
|
||||
GIT_SSL_NO_VERIFY='1', GIT_PROXY_COMMAND='evil', GIT_SSH_COMMAND='evil', HTTPS_PROXY='http://127.0.0.1:9',
|
||||
https_proxy='http://127.0.0.1:9', CURL_CA_BUNDLE='/tmp/evil.pem', GIT_DIR='/tmp/evil',
|
||||
)
|
||||
with mock.patch.dict(os.environ, dict(hostile, PATH='/usr/bin:/bin', SSL_CERT_FILE='/etc/ssl/cert.pem')):
|
||||
env = lib.git_env()
|
||||
assert not set(hostile) & set(env), set(hostile) & set(env)
|
||||
assert env['PATH'] == '/usr/bin:/bin'
|
||||
assert env['SSL_CERT_FILE'] == '/etc/ssl/cert.pem'
|
||||
assert env['GIT_TERMINAL_PROMPT'] == '0'
|
||||
|
||||
|
||||
def test_git_command_ignores_hostile_environment(tmp_path):
|
||||
"""With git's environment variables set against it, git still refuses file:// (only https is allowed)."""
|
||||
import subprocess
|
||||
origin = tmp_path / 'origin'
|
||||
subprocess.run(['git', 'init', '--quiet', str(origin)], check=True)
|
||||
with mock.patch.dict(os.environ, dict(GIT_ALLOW_PROTOCOL='file')):
|
||||
result = subprocess.run(lib.git_command('ls-remote', f'file://{origin}'), env=lib.git_env(),
|
||||
capture_output=True)
|
||||
assert result.returncode != 0
|
||||
assert b"transport 'file' not allowed" in result.stderr
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue