#!/usr/bin/env python3 """Pre-commit guard: Bearer ``bearer:disable`` directives must be well-formed. Bearer SILENTLY ignores a suppression directive (the finding stays open) unless it is written exactly as: * a line comment (``#`` in Python, ``//`` in JavaScript) on its OWN line, directly above the statement — a *same-line* trailing directive (``code() # bearer:disable rule``) is ignored; and * the bare rule id(s) only, with NO trailing prose — ``# bearer:disable rule -- why`` is ignored. Put the rationale on a separate comment line above the bare directive; and * NOT inside a block comment — Bearer ignores ``/* bearer:disable rule */`` and JSDoc ``* bearer:disable rule`` too. Each failure mode is silent, so a malformed directive looks like protection while suppressing nothing. This hook fails the commit when it finds one. Implementation notes: * Python uses ``tokenize`` — directives mentioned inside docstrings or string literals are not flagged, only real ``#`` comments. * JavaScript uses a small char scanner that tracks strings/templates and block comments, so a ``//`` inside a string (e.g. a URL) is not mistaken for a comment. Not special-cased (rare edges, accepted): regex literals, and code inside template ``${...}`` interpolations (a directive written inside ``${...}`` is treated as template text and would be missed) — no real directive is written in either place. """ from __future__ import annotations import io import re import sys import tokenize # A Bearer rule id is always namespaced with underscores, e.g. # python_lang_sql_injection / javascript_lang_dangerous_insert_html — never a # bare English word. Requiring an underscore stops lowercase prose # ("because reasons") from masquerading as a rule id. _RULE = r"[a-z][a-z0-9]*(?:_[a-z0-9]+)+" # After `bearer:disable`: one or more rule ids, comma-separated only (Bearer's # documented multi-rule syntax), and nothing else. _VALID_AFTER = re.compile(rf"^[ \t]+{_RULE}(?:[ \t]*,[ \t]*{_RULE})*[ \t]*$") # A Python `#` comment whose content begins with the directive. _DIRECTIVE_START = re.compile(r"^#[ \t]*bearer:disable\b(.*)$") # An embedded `# bearer:disable` (a directive trailing another comment). _EMBEDDED = re.compile(r"#[ \t]*bearer:disable\b") # A `// bearer:disable ...` line comment (used to read the bit after the rule). _JS_LINE_DIRECTIVE = re.compile(r"^//[ \t]*bearer:disable\b(.*)$") # A block-comment line that STARTS with the directive (after `/*` / JSDoc `*`), # vs. one that merely mentions it in prose. _BLOCK_DIRECTIVE_LINE = re.compile( r"^[ \t]*(?:/\*+|\*+)?[ \t]*bearer:disable\b" ) _SAME_LINE_MSG = ( "same-line `bearer:disable` is silently ignored by Bearer — put the bare " "directive on its own line directly above the statement" ) _TRAILING_MSG = ( "trailing text after the rule id is silently ignored by Bearer — keep the " "directive line as the bare rule id and move the rationale to a separate " "comment line above it" ) _BLOCK_MSG = ( "`bearer:disable` in a block comment is silently ignored by Bearer — use a " "line comment (// or #) on its own line directly above the statement" ) _MISSING_MSG = "`bearer:disable` is missing a rule id" def _after_violation(after: str) -> str | None: """Validate the text that follows ``bearer:disable``.""" if not after.strip(): return _MISSING_MSG if not _VALID_AFTER.match(after): return _TRAILING_MSG return None def _check_python(content: str) -> list[tuple[int, str]]: errors: list[tuple[int, str]] = [] lines = content.splitlines() try: tokens = list(tokenize.generate_tokens(io.StringIO(content).readline)) except (tokenize.TokenError, IndentationError, SyntaxError): return errors # malformed files are caught by other tools for tok in tokens: if tok.type != tokenize.COMMENT or "bearer:disable" not in tok.string: continue row, col = tok.start code_before = ( lines[row - 1][:col].strip() if row - 1 < len(lines) else "" ) m = _DIRECTIVE_START.match(tok.string) if m: if code_before: errors.append((row, _SAME_LINE_MSG)) else: msg = _after_violation(m.group(1)) if msg: errors.append((row, msg)) elif code_before and _EMBEDDED.search(tok.string): # A real directive trailing another comment on a code line, e.g. # `run(q) # noqa: S608 # bearer:disable rule`. errors.append((row, _SAME_LINE_MSG)) # else: a comment that only mentions the text in prose — not a directive. return errors def _classify_js_line_comment( lineno: int, code_before: str, comment: str, errors: list[tuple[int, str]] ) -> None: m = _JS_LINE_DIRECTIVE.match(comment) if not m: return # `// other prose ... bearer:disable ...` — not a directive if code_before.strip(): errors.append((lineno, _SAME_LINE_MSG)) else: msg = _after_violation(m.group(1)) if msg: errors.append((lineno, msg)) def _check_js(content: str) -> list[tuple[int, str]]: """Char scanner: only `bearer:disable` reached as a real comment counts.""" errors: list[tuple[int, str]] = [] n = len(content) i = 0 line = 1 line_start = 0 state = "code" # code | block | sq | dq | tmpl block_start_i = 0 block_start_line = 0 def flag_block(text: str, start_line: int) -> None: # Flag only a block line that STARTS with the directive (a real # block-comment suppression), not prose that mentions it. for off, ln in enumerate(text.splitlines()): if _BLOCK_DIRECTIVE_LINE.match(ln): errors.append((start_line + off, _BLOCK_MSG)) return while i < n: ch = content[i] nxt = content[i + 1] if i + 1 < n else "" if ch == "\n": line += 1 line_start = i + 1 i += 1 continue if state == "code": if ch == "/" and nxt == "/": eol = content.find("\n", i) if eol == -1: eol = n _classify_js_line_comment( line, content[line_start:i], content[i:eol], errors ) i = eol elif ch == "/" and nxt == "*": state, block_start_i, block_start_line = "block", i, line i += 2 elif ch == '"': state = "dq" i += 1 elif ch == "'": state = "sq" i += 1 elif ch == "`": state = "tmpl" i += 1 else: i += 1 elif state == "block": if ch == "*" and nxt == "/": flag_block(content[block_start_i : i + 2], block_start_line) state = "code" i += 2 else: i += 1 else: # sq | dq | tmpl quote = {"sq": "'", "dq": '"', "tmpl": "`"}[state] if ch == "\\": i += 2 elif ch == quote: state = "code" i += 1 else: i += 1 if state == "block": # unterminated block comment flag_block(content[block_start_i:n], block_start_line) return errors def check_file(filename: str) -> list[tuple[int, str]]: try: # utf-8-sig strips a leading BOM so it is not mistaken for code. with open(filename, "r", encoding="utf-8-sig") as fh: content = fh.read() except (UnicodeDecodeError, OSError): return [] if filename.endswith(".py"): return _check_python(content) if filename.endswith(".js"): return _check_js(content) return [] def main(argv: list[str]) -> int: failed = False for filename in argv: errors = check_file(filename) if errors: failed = True print(f"\n{filename}:") for line_num, msg in sorted(errors): print(f" Line {line_num}: {msg}") if failed: print( "\n❌ Malformed `bearer:disable` directive(s). Bearer only honors a " "directive that is\n the bare rule id on its own line directly " "above the statement:\n" " # bearer:disable python_lang_sql_injection\n" " \n" " Put any rationale on separate comment line(s) above it." ) return 1 return 0 if __name__ == "__main__": sys.exit(main(sys.argv[1:]))