65 lines
2.4 KiB
Bash
Executable File
65 lines
2.4 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
if ! command -v python3 >/dev/null 2>&1; then
|
|
printf '%s\n' 'BLOCKED: Python 3 is required to inspect this command.' >&2
|
|
exit 2
|
|
fi
|
|
|
|
exec python3 -c '
|
|
import itertools
|
|
import json
|
|
import shlex
|
|
import sys
|
|
|
|
def block(reason):
|
|
print("BLOCKED: " + reason, file=sys.stderr)
|
|
sys.exit(2)
|
|
|
|
try:
|
|
command = json.load(sys.stdin)["tool_input"]["command"]
|
|
if not isinstance(command, str) or not command.strip():
|
|
raise ValueError("missing command")
|
|
lexer = shlex.shlex(command.replace("\\\n", ""), posix=True, punctuation_chars=";&|()\n")
|
|
lexer.whitespace = " \t\r"
|
|
lexer.whitespace_split = True
|
|
tokens = list(lexer)
|
|
except (ValueError, KeyError, TypeError):
|
|
block("Cannot parse the command input.")
|
|
|
|
value_options = {"-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env"}
|
|
flag_options = {"-p", "--paginate", "-P", "--no-pager", "--bare", "--literal-pathspecs"}
|
|
# ponytail: conservative token inspection; shell expansion and aliases require sandbox enforcement.
|
|
for separator, group in itertools.groupby(tokens, lambda token: bool(token) and token[0] in ";&|()\n"):
|
|
if separator:
|
|
continue
|
|
words = list(group)
|
|
for index, word in enumerate(words):
|
|
if word.rsplit("/", 1)[-1] != "git":
|
|
continue
|
|
args = words[index + 1:]
|
|
while args and args[0].startswith("-"):
|
|
option = args.pop(0)
|
|
if option in value_options:
|
|
if not args:
|
|
block("Missing Git option value.")
|
|
args.pop(0)
|
|
elif option in flag_options or option.split("=", 1)[0] in value_options:
|
|
continue
|
|
else:
|
|
block("Unsupported Git option; inspect this command manually.")
|
|
if not args:
|
|
continue
|
|
operation, *args = args
|
|
short_flags = "".join(arg[1:] for arg in args if arg.startswith("-") and not arg.startswith("--"))
|
|
force = "--force" in args or "f" in short_flags
|
|
dangerous = (
|
|
operation == "push"
|
|
or operation == "reset" and "--hard" in args
|
|
or operation == "clean" and force
|
|
or operation == "branch" and ("D" in short_flags or force and ("d" in short_flags or "--delete" in args))
|
|
or operation in {"checkout", "restore"} and "." in args
|
|
)
|
|
if dangerous:
|
|
block("Git " + operation + " is disabled by this hook.")
|
|
'
|