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
This commit is contained in:
ran 2026-04-11 12:17:27 +08:00
parent 5c0f3024d2
commit dd5ba1ab00
2 changed files with 37 additions and 1 deletions

View file

@ -23,6 +23,22 @@ logger = logging.getLogger('pre_commit')
check_string_regex = cfgv.check_and(cfgv.check_string, cfgv.check_regex)
def _check_log_file(val: str) -> None:
if val == '':
return
if os.path.isabs(val):
raise cfgv.ValidationError(
f'log_file must be a relative path, got absolute path: {val!r}',
)
if os.path.normpath(val).startswith('..'):
raise cfgv.ValidationError(
f'log_file must not reference a parent directory: {val!r}',
)
check_log_file = cfgv.check_and(cfgv.check_string, _check_log_file)
HOOK_TYPES = (
'commit-msg',
'post-checkout',
@ -258,7 +274,7 @@ MANIFEST_HOOK_DICT = cfgv.Map(
cfgv.Optional('pass_filenames', cfgv.check_bool, True),
cfgv.Optional('description', cfgv.check_string, ''),
cfgv.Optional('language_version', cfgv.check_string, C.DEFAULT),
cfgv.Optional('log_file', cfgv.check_string, ''),
cfgv.Optional('log_file', check_log_file, ''),
cfgv.Optional('require_serial', cfgv.check_bool, False),
StagesMigration('stages', []),
cfgv.Optional('verbose', cfgv.check_bool, False),