#!/usr/bin/env python3 """Redact credential-shaped log values while enforcing an output byte limit.""" from __future__ import annotations import argparse import re import sys SENSITIVE_KEY = ( r"(?:authorization|password|token|access[_-]?token|refresh[_-]?token|" r"client[_-]?secret|secret|credential)" ) QUOTED_VALUE = re.compile( r"(?i)(?P[\"']?" + SENSITIVE_KEY + r"[\"']?\s*[:=]\s*)" r"(?P[\"'])(?P.*?)(?P=quote)" ) UNQUOTED_VALUE = re.compile( r"(?i)(?P(?:^|[?&;,\s])[\"']?" + SENSITIVE_KEY + r"[\"']?\s*[:=]\s*)(?P(?:Bearer\s+)?[^\"'\s,;}&]+)" ) BEARER_VALUE = re.compile(r"(?i)(\bBearer\s+)[A-Za-z0-9._~+/=-]+") TRUNCATION_MARKER = b"\n[log output truncated by byte limit]\n" def redact(text: str) -> str: text = QUOTED_VALUE.sub( lambda match: ( match.group("prefix") + match.group("quote") + "" + match.group("quote") ), text, ) text = UNQUOTED_VALUE.sub( lambda match: match.group("prefix") + "", text ) return BEARER_VALUE.sub(r"\1", text) def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--max-output-bytes", type=int, required=True) args = parser.parse_args() if args.max_output_bytes < len(TRUNCATION_MARKER): parser.error("--max-output-bytes is too small for the truncation marker") return args def main() -> int: args = parse_args() limit = args.max_output_bytes payload_limit = limit - len(TRUNCATION_MARKER) written = 0 truncated = False for raw in sys.stdin.buffer: safe = (redact(raw.decode("utf-8", errors="replace"))).encode("utf-8") if not truncated and written + len(safe) <= payload_limit: sys.stdout.buffer.write(safe) written += len(safe) else: truncated = True if truncated: sys.stdout.buffer.write(TRUNCATION_MARKER) written += len(TRUNCATION_MARKER) print(f"redacted_log_output_bytes={written} truncated={str(truncated).lower()}", file=sys.stderr) return 0 if __name__ == "__main__": raise SystemExit(main())