mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
- Add insaits-security-monitor.py: real-time AI security monitoring hook that catches credential exposure, prompt injection, hallucinations, and 20+ other anomaly types - Update hooks.json with InsAIts PostToolUse entry - Update hooks/README.md with InsAIts in PostToolUse table - Add InsAIts MCP server entry to mcp-configs/mcp-servers.json InsAIts (https://github.com/Nomadu27/InsAIts) is an open-source runtime security layer for multi-agent AI. It runs 100% locally and writes tamper-evident audit logs to .insaits_audit_session.jsonl. Install: pip install insa-its Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
201 lines
5.6 KiB
Python
201 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
InsAIts Security Monitor — PostToolUse Hook for Claude Code
|
|
============================================================
|
|
|
|
Real-time security monitoring for Claude Code tool outputs.
|
|
Catches credential exposure, prompt injection, behavioral anomalies,
|
|
hallucination chains, and 20+ other anomaly types — runs 100% locally.
|
|
|
|
Writes audit events to .insaits_audit_session.jsonl for forensic tracing.
|
|
|
|
Setup:
|
|
pip install insa-its
|
|
|
|
Add to .claude/settings.json:
|
|
{
|
|
"hooks": {
|
|
"PostToolUse": [
|
|
{
|
|
"matcher": ".*",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "python scripts/hooks/insaits-security-monitor.py"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
How it works:
|
|
Claude Code passes tool result as JSON on stdin.
|
|
This script runs InsAIts anomaly detection on the output.
|
|
Exit code 0 = clean (pass through).
|
|
Exit code 2 = critical issue found (blocks action, shows feedback to Claude).
|
|
|
|
Detections include:
|
|
- Credential exposure (API keys, tokens, passwords in output)
|
|
- Prompt injection patterns
|
|
- Hallucination indicators (phantom citations, fact contradictions)
|
|
- Behavioral anomalies (context loss, semantic drift)
|
|
- Tool description divergence
|
|
- Shorthand emergence / jargon drift
|
|
|
|
All processing is local — no data leaves your machine.
|
|
|
|
Author: Cristi Bogdan — YuyAI (https://github.com/Nomadu27/InsAIts)
|
|
License: Apache 2.0
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import os
|
|
import hashlib
|
|
import time
|
|
|
|
# Try importing InsAIts SDK
|
|
try:
|
|
from insa_its import insAItsMonitor
|
|
INSAITS_AVAILABLE = True
|
|
except ImportError:
|
|
INSAITS_AVAILABLE = False
|
|
|
|
AUDIT_FILE = ".insaits_audit_session.jsonl"
|
|
|
|
|
|
def extract_content(data):
|
|
"""Extract inspectable text from a Claude Code tool result."""
|
|
tool_name = data.get("tool_name", "")
|
|
tool_input = data.get("tool_input", {})
|
|
tool_result = data.get("tool_response", {})
|
|
|
|
text = ""
|
|
context = ""
|
|
|
|
if tool_name in ("Write", "Edit", "MultiEdit"):
|
|
text = tool_input.get("content", "") or tool_input.get("new_string", "")
|
|
context = "file:" + tool_input.get("file_path", "")[:80]
|
|
elif tool_name == "Bash":
|
|
if isinstance(tool_result, dict):
|
|
text = tool_result.get("output", "") or tool_result.get("stdout", "")
|
|
elif isinstance(tool_result, str):
|
|
text = tool_result
|
|
context = "bash:" + str(tool_input.get("command", ""))[:80]
|
|
elif "content" in data:
|
|
content = data["content"]
|
|
if isinstance(content, list):
|
|
text = "\n".join(
|
|
b.get("text", "") for b in content if b.get("type") == "text"
|
|
)
|
|
elif isinstance(content, str):
|
|
text = content
|
|
context = data.get("task", "")
|
|
|
|
return text, context
|
|
|
|
|
|
def write_audit(event):
|
|
"""Append an audit event to the JSONL audit log."""
|
|
try:
|
|
event["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
event["hash"] = hashlib.sha256(
|
|
json.dumps(event, sort_keys=True).encode()
|
|
).hexdigest()[:16]
|
|
with open(AUDIT_FILE, "a", encoding="utf-8") as f:
|
|
f.write(json.dumps(event) + "\n")
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def format_feedback(anomalies):
|
|
"""Format detected anomalies as feedback for Claude Code."""
|
|
lines = [
|
|
"== InsAIts Security Monitor — Issues Detected ==",
|
|
"",
|
|
]
|
|
for i, a in enumerate(anomalies, 1):
|
|
sev = getattr(a, "severity", "MEDIUM")
|
|
atype = getattr(a, "type", "UNKNOWN")
|
|
detail = getattr(a, "detail", "")
|
|
lines.extend([
|
|
f"{i}. [{sev}] {atype}",
|
|
f" {detail[:120]}",
|
|
"",
|
|
])
|
|
lines.extend([
|
|
"-" * 56,
|
|
"Fix the issues above before continuing.",
|
|
"Audit log: " + AUDIT_FILE,
|
|
])
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
raw = sys.stdin.read().strip()
|
|
if not raw:
|
|
sys.exit(0)
|
|
|
|
try:
|
|
data = json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
data = {"content": raw}
|
|
|
|
text, context = extract_content(data)
|
|
|
|
# Skip very short or binary content
|
|
if len(text.strip()) < 10:
|
|
sys.exit(0)
|
|
|
|
if not INSAITS_AVAILABLE:
|
|
print(
|
|
"[InsAIts] Not installed. Run: pip install insa-its",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(0)
|
|
|
|
# Enable dev mode (no API key needed for local detection)
|
|
os.environ.setdefault("INSAITS_DEV_MODE", "true")
|
|
|
|
monitor = insAItsMonitor(session_name="claude-code-hook")
|
|
result = monitor.send_message(
|
|
text=text[:4000],
|
|
sender_id="claude-code",
|
|
llm_id=os.environ.get("INSAITS_MODEL", "claude-opus"),
|
|
)
|
|
|
|
anomalies = result.get("anomalies", [])
|
|
|
|
# Write audit event regardless of findings
|
|
write_audit({
|
|
"tool": data.get("tool_name", "unknown"),
|
|
"context": context,
|
|
"anomaly_count": len(anomalies),
|
|
"anomaly_types": [getattr(a, "type", "") for a in anomalies],
|
|
"text_length": len(text),
|
|
})
|
|
|
|
if not anomalies:
|
|
if os.environ.get("INSAITS_VERBOSE"):
|
|
print("[InsAIts] Clean — no anomalies.", file=sys.stderr)
|
|
sys.exit(0)
|
|
|
|
# Check severity
|
|
has_critical = any(
|
|
getattr(a, "severity", "") in ("CRITICAL", "critical") for a in anomalies
|
|
)
|
|
|
|
feedback = format_feedback(anomalies)
|
|
|
|
if has_critical:
|
|
print(feedback) # stdout -> Claude Code shows to model
|
|
sys.exit(2) # block action
|
|
else:
|
|
print(feedback, file=sys.stderr) # stderr -> logged only
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|