gitleaks-pre-commit-hook/gitleaks_scan.py
2025-06-25 23:45:15 -05:00

243 lines
8.2 KiB
Python
Executable File

#!/usr/bin/python3
import os
import sys
import subprocess
import tempfile
import shutil
USER_VERSION = "8.27.0"
FORCE_INSTALL = False
SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
def parse_args():
global USER_VERSION, FORCE_INSTALL
args = sys.argv[1:]
new_args = []
while args:
arg = args.pop(0)
if arg.startswith("--version="):
USER_VERSION = arg.split("=", 1)[1].lstrip("v")
elif arg == "--force":
FORCE_INSTALL = True
else:
new_args.append(arg)
return new_args
def is_gitleaks_installed():
try:
result = subprocess.run(["gitleaks", "version"], capture_output=True, text=True)
return result.returncode == 0
except FileNotFoundError:
return False
def get_installed_version():
try:
result = subprocess.run(["gitleaks", "version"], capture_output=True, text=True)
if result.returncode != 0:
return None
version = result.stdout.strip()
if version.startswith("v"):
version = version[1:]
return version
except Exception:
return None
def get_platform_and_arch():
import platform
system = platform.system().lower()
machine = platform.machine().lower()
if system.startswith("linux"):
plat = "linux"
elif system == "darwin":
plat = "darwin"
elif system == "windows":
plat = "windows"
elif "mingw" in os.environ.get("MSYSTEM", "").lower():
plat = "windows"
else:
print(f"[!] Unsupported operating system: {system}")
sys.exit(1)
if machine in ["x86_64", "amd64"]:
arch = "x64"
elif machine in ["arm64", "aarch64"]:
arch = "arm64"
elif machine.startswith("armv7"):
arch = "armv7"
elif machine == "i386":
arch = "x32"
elif machine == "armv6":
arch = "armv6"
else:
print(f"[!] Unsupported architecture: {machine}")
sys.exit(1)
if plat == "windows" and arch not in ["x32", "x64", "armv6", "armv7"]:
print(f"[!] Unsupported Windows architecture: {arch}")
sys.exit(1)
return plat, arch
def install_gitleaks(version, force):
installed = is_gitleaks_installed()
current_version = get_installed_version()
if installed and not force:
if current_version == version:
return
else:
print(f"Gitleaks {current_version} is installed, but version {version} is required.")
print(" Use --force to reinstall.")
return
print(f"Installing Gitleaks v{version}...")
plat, arch = get_platform_and_arch()
ext = "zip" if plat == "windows" else "tar.gz"
filename = f"gitleaks_{version}_{plat}_{arch}.{ext}"
url = f"https://github.com/gitleaks/gitleaks/releases/download/v{version}/{filename}"
with tempfile.TemporaryDirectory() as tmpdir:
archive_path = os.path.join(tmpdir, filename)
print(f"Downloading from: {url}")
subprocess.run(["curl", "-sSL", "-o", archive_path, url], check=True)
if ext == "zip":
import zipfile
with zipfile.ZipFile(archive_path, "r") as zip_ref:
zip_ref.extractall(tmpdir)
else:
subprocess.run(["tar", "-xzf", archive_path, "-C", tmpdir], check=True)
exe_name = "gitleaks.exe" if plat == "windows" else "gitleaks"
gitleaks_bin = os.path.join(tmpdir, exe_name)
if plat == "windows":
fallback = os.path.expanduser("~/.local/bin")
os.makedirs(fallback, exist_ok=True)
dest_path = os.path.join(fallback, exe_name)
shutil.move(gitleaks_bin, dest_path)
print(f"Gitleaks v{version} installed at {dest_path}")
print("Add ~/.local/bin to your PATH if not already present.")
else:
install_path = "/usr/local/bin/gitleaks"
elevate_cmd = None
if shutil.which("sudo"):
elevate_cmd = "sudo"
elif shutil.which("doas"):
elevate_cmd = "doas"
if elevate_cmd:
print(f"Installing with {elevate_cmd} at {install_path}...")
try:
subprocess.run([elevate_cmd, "mv", gitleaks_bin, install_path], check=True)
subprocess.run([elevate_cmd, "chmod", "+x", install_path], check=True)
print(f"Gitleaks v{version} installed at {install_path}")
except subprocess.CalledProcessError:
print(f"Error moving file with {elevate_cmd}. Installing locally...")
fallback = os.path.expanduser("~/.local/bin")
os.makedirs(fallback, exist_ok=True)
shutil.move(gitleaks_bin, os.path.join(fallback, "gitleaks"))
print(f"Gitleaks v{version} installed at {fallback}")
print("Ensure ~/.local/bin is in your PATH")
else:
print("Neither 'sudo' nor 'doas' available. Installing locally...")
fallback = os.path.expanduser("~/.local/bin")
os.makedirs(fallback, exist_ok=True)
shutil.move(gitleaks_bin, os.path.join(fallback, "gitleaks"))
print(f"Gitleaks v{version} installed at {fallback}")
print("Ensure ~/.local/bin is in your PATH")
def main():
args = parse_args()
install_gitleaks(USER_VERSION, FORCE_INSTALL)
# hook variables
TEMPLATE_NAME = "leet"
CONFIG_FILE_NAME = "gitleaks.toml"
FORMAT = "html"
REDACT = False
config_path = None
use_internal_rules = False
use_external_rules = False
for arg in args:
if arg.startswith("--template="):
TEMPLATE_NAME = arg.split("=",1)[1]
elif arg.startswith("--config="):
config_path = arg.split("=",1)[1]
elif arg.startswith("--format="):
FORMAT = arg.split("=",1)[1]
elif arg == "--redact":
REDACT = True
elif arg == "--rules-internal":
use_internal_rules = True
elif arg == "--rules-external":
use_external_rules = True
# By default, don't use any config file unless explicitly specified
if not (use_internal_rules or use_external_rules):
config_path = None
elif not config_path:
if use_internal_rules:
config_path = os.path.join(SCRIPT_DIR, "rules", CONFIG_FILE_NAME)
elif use_external_rules:
# You might want to specify a default external rules path here
print("Error: --rules-external requires a config file path with --config=")
sys.exit(1)
template_path = os.path.join(SCRIPT_DIR, "templates", f"{TEMPLATE_NAME}.tmpl")
report_dir = os.path.join(".pre-commit", "gitleaks", "report")
os.makedirs(report_dir, exist_ok=True)
if FORMAT not in ("html", "json"):
print(f"Unsupported format: {FORMAT}")
print(" Supported formats: html, json")
sys.exit(1)
if config_path and not os.path.isfile(config_path):
print(f"Config file not found: {config_path}")
sys.exit(1)
extra_args = []
if config_path:
extra_args.append(f"--config={config_path}")
if REDACT:
extra_args.append("--redact")
if FORMAT == "html":
if not os.path.isfile(template_path):
print(f"Template not found: {template_path}")
sys.exit(1)
report_path = os.path.join(report_dir, "index.html")
cmd = ["gitleaks", "git"] + extra_args + [
"--report-format=template",
f"--report-template={template_path}",
f"--report-path={report_path}"
]
else:
report_path = os.path.join(report_dir, "report.json")
cmd = ["gitleaks", "git"] + extra_args + [
"--report-format=json",
f"--report-path={report_path}"
]
print(f"Running: {' '.join(cmd)}")
result = subprocess.run(cmd)
if result.returncode != 0:
print("Error running gitleaks.")
sys.exit(result.returncode)
if os.path.isfile(report_path):
print(f"Report generated: {report_path}")
else:
print("Failed to generate report.")
sys.exit(1)
if __name__ == "__main__":
main()