From 425727ef9d0ca9da8fed3b623dc6869af9911646 Mon Sep 17 00:00:00 2001 From: Peter Jung Date: Mon, 3 Feb 2025 10:47:27 +0100 Subject: [PATCH] Web3 private key check hook --- pre_commit_hooks/detect_web3_private_keys.py | 79 ++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 pre_commit_hooks/detect_web3_private_keys.py diff --git a/pre_commit_hooks/detect_web3_private_keys.py b/pre_commit_hooks/detect_web3_private_keys.py new file mode 100644 index 0000000..b8a19ac --- /dev/null +++ b/pre_commit_hooks/detect_web3_private_keys.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +""" +This script checks files for potential Web3 private keys. +""" + +import argparse +import os +import re +import sys +from typing import Sequence + +from eth_account import Account +from eth_utils import decode_hex + +# Regular expression to match Ethereum private keys +KEY_PATTERN = re.compile(r"\b(0x)?[a-fA-F0-9]{64}\b") +IGNORE_COMMENT = "# web3-private-key-ok" + + +def is_private_key_valid(private_key_hex: str) -> bool: + try: + # Remove hex prefix if present + if private_key_hex.startswith("0x"): + private_key_hex = private_key_hex[2:] + + # Decode the hex string to bytes + private_key_bytes = decode_hex(private_key_hex) + + # Attempt to create an account object + Account.from_key(private_key_bytes) + + return True + + except Exception: + return False + + +def scan_file(file_path: str) -> bool: + """ + Scans a file for potential Web3 private keys. + """ + detected = False + try: + with open(file_path, "r", encoding="utf-8", errors="ignore") as f: + for idx, line in enumerate(f): + match = KEY_PATTERN.search(line) + if match and IGNORE_COMMENT not in line: + private_key_hex = match.group(0) + if is_private_key_valid(private_key_hex): + print( + f"Error: Valid Web3 private key detected in {file_path}:{idx + 1}" + ) + detected = True + except Exception as e: + print(f"Warning: Error reading file {file_path}: {e}") + return detected + + +def main(argv: Sequence[str] | None = None) -> None: + parser = argparse.ArgumentParser() + parser.add_argument("filenames", nargs="*", help="Filenames to check") + args = parser.parse_args(argv) + + files_with_keys = [] + for file_path in args.filenames: + if not os.path.isfile(file_path): + continue + + if scan_file(file_path): + files_with_keys.append(file_path) + + if files_with_keys: + sys.exit(1) + else: + sys.exit(0) + + +if __name__ == "__main__": + main()