mmoore 4b3d2a2a96
Claude Security Plugin for Claude Code 0.10.0
🏠 Remote-Dev: homespace
2026-07-22 16:17:51 +00:00

100 lines
3.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""Show the Claude Security banner as a display-only systemMessage.
Always exits 0 with either the banner or no output.
"""
import contextlib
import json
import os
import sys
from typing import cast
PLUGIN_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
LAUNCH_NOTICE = "Launching Claude Security..."
BOX_INNER = 53
MIN_PYTHON = (3, 9)
def plugin_version() -> str:
"""The plugin's version from plugin.json, or "unknown". Never raises."""
try:
path = os.path.join(PLUGIN_ROOT, ".claude-plugin", "plugin.json")
with open(path, encoding="utf-8") as handle:
loaded = cast("object", json.load(handle))
except Exception:
return "unknown"
if not isinstance(loaded, dict):
return "unknown"
version = cast("dict[str, object]", loaded).get("version")
return version if isinstance(version, str) and version else "unknown"
def box_line(text: str) -> str:
"""One boxed body line, centered so the right border always aligns."""
if len(text) > BOX_INNER:
text = text[:BOX_INNER]
return "" + text.center(BOX_INNER) + ""
def bottom_border(version: str) -> str:
"""The box's bottom edge with the version set into it, right-aligned."""
tag = f" v{version} "
fill = BOX_INNER - len(tag) - 3
if fill < 1:
return "" + "" * BOX_INNER + ""
return "" + "" * fill + tag + "" * 3 + ""
def banner() -> str:
lines = [
"",
" ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗",
" ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝",
" ██║ ██║ ███████║██║ ██║██║ ██║█████╗",
" ██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝",
" ╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗",
" ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝",
" ──────── S · E · C · U · R · I · T · Y ────────",
"" + "" * BOX_INNER + "",
box_line("Find and fix vulnerabilities in source code"),
bottom_border(plugin_version()),
"",
]
return "\n".join(lines)
def emit(message: str) -> None:
"""Write one systemMessage. Never raises; a failed write is just no banner."""
try:
sys.stdout.write(json.dumps({"systemMessage": message}))
sys.stdout.flush()
except Exception:
# Also silence the interpreter's exit-time flush of the buffered message.
with contextlib.suppress(Exception):
os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
def main() -> int:
if sys.version_info < MIN_PYTHON:
need = f"{MIN_PYTHON[0]}.{MIN_PYTHON[1]}"
have = ".".join(str(part) for part in sys.version_info[:3])
emit(
f"\n\u26a0\ufe0f Claude Security needs python3 {need} or newer, but this "
f"python3 is {have}. Scanning and fixing will fail until a newer "
"python3 is first on PATH.\n"
)
return 0
try:
message = "\n" + LAUNCH_NOTICE + "\n\n" + banner()
except Exception:
message = "\n" + LAUNCH_NOTICE + "\n"
emit(message)
return 0
if __name__ == "__main__":
sys.exit(main())