Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dd5ba1a

Browse files
author
ran
committed
Validate log_file paths to prevent arbitrary file writes
A hook manifest can specify a `log_file` with an absolute path or path traversal sequences (e.g. `../../../etc/cron.d/malicious`), causing pre-commit to write hook output to arbitrary locations on the host filesystem via `output.write_line_b`. Reject absolute paths and paths that traverse above the working directory during manifest validation. Fixes #3655
1 parent 5c0f302 commit dd5ba1a

2 files changed

Lines changed: 37 additions & 1 deletion

File tree

pre_commit/clientlib.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@
2323

2424
check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex)
2525

26+
27+
def _check_log_file(val: str) -> None:
28+
if val == '':
29+
return
30+
if os.path.isabs(val):
31+
raise cfgv.ValidationError(
32+
f'log_file must be a relative path, got absolute path: {val!r}',
33+
)
34+
if os.path.normpath(val).startswith('..'):
35+
raise cfgv.ValidationError(
36+
f'log_file must not reference a parent directory: {val!r}',
37+
)
38+
39+
40+
check_log_file = cfgv.check_and(cfgv.check_string, _check_log_file)
41+
2642
HOOK_TYPES = (
2743
'commit-msg',
2844
'post-checkout',
@@ -258,7 +274,7 @@ def check(self, dct: dict[str, Any]) -> None:
258274
cfgv.Optional('pass_filenames', cfgv.check_bool, True),
259275
cfgv.Optional('description', cfgv.check_string, ''),
260276
cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
261-
cfgv.Optional('log_file', cfgv.check_string, ''),
277+
cfgv.Optional('log_file', check_log_file, ''),
262278
cfgv.Optional('require_serial', cfgv.check_bool, False),
263279
StagesMigration('stages', []),
264280
cfgv.Optional('verbose', cfgv.check_bool, False),

tests/clientlib_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import pytest
88

99
import pre_commit.constants as C
10+
from pre_commit.clientlib import _check_log_file
1011
from pre_commit.clientlib import check_type_tag
1112
from pre_commit.clientlib import CONFIG_HOOK_DICT
1213
from pre_commit.clientlib import CONFIG_REPO_DICT
@@ -605,3 +606,22 @@ def test_manifest_v5_forward_compat(tmp_path):
605606
f'=====> pre-commit version 5 is required but version {C.VERSION} '
606607
f'is installed. Perhaps run `pip install --upgrade pre-commit`.'
607608
)
609+
610+
611+
@pytest.mark.parametrize('value', ('output.log', 'logs/hook.log', ''))
612+
def test_check_log_file_valid(value):
613+
_check_log_file(value)
614+
615+
616+
@pytest.mark.parametrize(
617+
'value',
618+
(
619+
'/tmp/evil.log',
620+
'/etc/cron.d/malicious',
621+
'../../../etc/passwd',
622+
'../outside.log',
623+
),
624+
)
625+
def test_check_log_file_invalid(value):
626+
with pytest.raises(cfgv.ValidationError):
627+
_check_log_file(value)

0 commit comments

Comments
 (0)