From 68fc85ea492b8da946e89f190e917879005b417f Mon Sep 17 00:00:00 2001 From: Nomadu27 Date: Tue, 10 Mar 2026 18:25:23 +0100 Subject: [PATCH] fix: address cubic-dev-ai + coderabbit round 3 review cubic-dev-ai P2: dev_mode now defaults to "false" (strict mode). Users opt in to dev mode by setting INSAITS_DEV_MODE=true. cubic-dev-ai P2: Move null-status check above stdout/stderr writes in wrapper so partial/corrupt output is never leaked. Pass through original raw input on signal kill, matching the result.error path. coderabbit major: Wrap insAItsMonitor() and send_message() in try/except so SDK errors don't crash the hook. Logs warning and exits 0 (fail-open) on exception. coderabbit nitpick: write_audit now creates a new dict (enriched) instead of mutating the caller's event dict. coderabbit nitpick: Extract magic numbers to named constants: MIN_CONTENT_LENGTH=10, MAX_SCAN_LENGTH=4000, DEFAULT_MODEL. Also: added env var documentation to module docstring. Co-Authored-By: Claude Opus 4.6 --- scripts/hooks/insaits-security-monitor.py | 53 ++++++++++++++++------- scripts/hooks/insaits-security-wrapper.js | 10 +++-- 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/scripts/hooks/insaits-security-monitor.py b/scripts/hooks/insaits-security-monitor.py index 4cf64074..43e5be74 100644 --- a/scripts/hooks/insaits-security-monitor.py +++ b/scripts/hooks/insaits-security-monitor.py @@ -36,6 +36,12 @@ How it works: Exit code 2 = critical issue found (blocks tool execution). Stderr output = non-blocking warning shown to Claude. +Environment variables: + INSAITS_DEV_MODE Set to "true" to enable dev mode (no API key needed). + Defaults to "false" (strict mode). + INSAITS_MODEL LLM model identifier for fingerprinting. Default: claude-opus. + INSAITS_VERBOSE Set to any value to enable debug logging. + Detections include: - Credential exposure (API keys, tokens, passwords) - Prompt injection patterns @@ -75,7 +81,11 @@ try: except ImportError: INSAITS_AVAILABLE = False +# --- Constants --- AUDIT_FILE: str = ".insaits_audit_session.jsonl" +MIN_CONTENT_LENGTH: int = 10 +MAX_SCAN_LENGTH: int = 4000 +DEFAULT_MODEL: str = "claude-opus" def extract_content(data: Dict[str, Any]) -> Tuple[str, str]: @@ -113,14 +123,20 @@ def extract_content(data: Dict[str, Any]) -> Tuple[str, str]: def write_audit(event: Dict[str, Any]) -> None: - """Append an audit event to the JSONL audit log.""" + """Append an audit event to the JSONL audit log. + + Creates a new dict to avoid mutating the caller's *event*. + """ try: - event["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) - event["hash"] = hashlib.sha256( - json.dumps(event, sort_keys=True).encode() + enriched: Dict[str, Any] = { + **event, + "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + } + enriched["hash"] = hashlib.sha256( + json.dumps(enriched, sort_keys=True).encode() ).hexdigest()[:16] with open(AUDIT_FILE, "a", encoding="utf-8") as f: - f.write(json.dumps(event) + "\n") + f.write(json.dumps(enriched) + "\n") except OSError as exc: log.warning("Failed to write audit log %s: %s", AUDIT_FILE, exc) @@ -166,22 +182,29 @@ def main() -> None: text, context = extract_content(data) # Skip very short content (e.g. "OK", empty bash results) - if len(text.strip()) < 10: + if len(text.strip()) < MIN_CONTENT_LENGTH: sys.exit(0) if not INSAITS_AVAILABLE: log.warning("Not installed. Run: pip install insa-its") sys.exit(0) - monitor: insAItsMonitor = insAItsMonitor( - session_name="claude-code-hook", - dev_mode=os.environ.get("INSAITS_DEV_MODE", "true").lower() in ("1", "true", "yes"), - ) - result: Dict[str, Any] = monitor.send_message( - text=text[:4000], - sender_id="claude-code", - llm_id=os.environ.get("INSAITS_MODEL", "claude-opus"), - ) + # Wrap SDK calls so an internal error does not crash the hook + try: + monitor: insAItsMonitor = insAItsMonitor( + session_name="claude-code-hook", + dev_mode=os.environ.get( + "INSAITS_DEV_MODE", "false" + ).lower() in ("1", "true", "yes"), + ) + result: Dict[str, Any] = monitor.send_message( + text=text[:MAX_SCAN_LENGTH], + sender_id="claude-code", + llm_id=os.environ.get("INSAITS_MODEL", DEFAULT_MODEL), + ) + except Exception as exc: + log.warning("SDK error, skipping security scan: %s", exc) + sys.exit(0) anomalies: List[Any] = result.get("anomalies", []) diff --git a/scripts/hooks/insaits-security-wrapper.js b/scripts/hooks/insaits-security-wrapper.js index eba87128..2ae10857 100644 --- a/scripts/hooks/insaits-security-wrapper.js +++ b/scripts/hooks/insaits-security-wrapper.js @@ -62,16 +62,18 @@ process.stdin.on('end', () => { process.exit(0); } - if (result.stdout) process.stdout.write(result.stdout); - if (result.stderr) process.stderr.write(result.stderr); - // result.status is null when the process was killed by a signal or - // timed out. Treat that as an error rather than silently passing. + // timed out. Check BEFORE writing stdout to avoid leaking partial + // or corrupt monitor output. Pass through original raw input instead. if (!Number.isInteger(result.status)) { const signal = result.signal || 'unknown'; process.stderr.write(`[InsAIts] Security monitor killed (signal: ${signal}). Tool execution continues.\n`); + process.stdout.write(raw); process.exit(0); } + if (result.stdout) process.stdout.write(result.stdout); + if (result.stderr) process.stderr.write(result.stderr); + process.exit(result.status); });