feat: consolidate all Anthropic plugins into ECC v2.0.0

Ports functionality from 10+ separate plugins into ECC so users only
need one plugin installed. Consolidates: pr-review-toolkit, feature-dev,
commit-commands, hookify, code-simplifier, security-guidance,
frontend-design, explanatory-output-style, and personal skills.

New agents (8): code-architect, code-explorer, code-simplifier,
comment-analyzer, conversation-analyzer, pr-test-analyzer,
silent-failure-hunter, type-design-analyzer

New commands (9): commit, commit-push-pr, clean-gone, review-pr,
feature-dev, hookify, hookify-list, hookify-configure, hookify-help

New skills (8): frontend-design, hookify-rules, github-ops,
knowledge-ops, lead-intelligence, oura-health, pmx-guidelines, remotion

Enhanced skills (8): article-writing, content-engine, market-research,
investor-materials, investor-outreach, x-api, security-scan,
autonomous-loops — merged with personal skill content

New hook: security-reminder.py (pattern-based OWASP vulnerability
warnings on file edits)

Totals: 36 agents, 69 commands, 128 skills, 29 hook scripts
This commit is contained in:
Affaan Mustafa
2026-03-31 21:54:03 -07:00
parent 19755f6c52
commit 4813ed753f
73 changed files with 5618 additions and 27 deletions

View File

@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""
Security Reminder Hook for Claude Code
Checks for security patterns in file edits and warns about potential vulnerabilities.
Ported from security-guidance plugin (David Dworken, Anthropic).
"""
import json
import os
import random
import sys
from datetime import datetime
SECURITY_PATTERNS = [
{
"ruleName": "github_actions_workflow",
"path_check": lambda path: ".github/workflows/" in path
and (path.endswith(".yml") or path.endswith(".yaml")),
"reminder": "You are editing a GitHub Actions workflow file. Never use untrusted input directly in run: commands. Use env: variables with proper quoting instead. See: https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/",
},
{
"ruleName": "child_process_exec",
"substrings": ["child_process.exec"],
"reminder": "Security Warning: child_process exec can lead to command injection. Use execFile instead which does not invoke a shell.",
},
{
"ruleName": "new_function_injection",
"substrings": ["new Function"],
"reminder": "Security Warning: new Function with dynamic strings can lead to code injection. Consider alternatives.",
},
{
"ruleName": "eval_injection",
"substrings": ["eval("],
"reminder": "Security Warning: eval executes arbitrary code. Use JSON.parse for data or alternative patterns.",
},
{
"ruleName": "document_write_xss",
"substrings": ["document.write"],
"reminder": "Security Warning: document.write can be exploited for XSS. Use DOM manipulation methods instead.",
},
{
"ruleName": "innerHTML_xss",
"substrings": [".innerHTML =", ".innerHTML="],
"reminder": "Security Warning: innerHTML with untrusted content leads to XSS. Use textContent or sanitize with DOMPurify.",
},
{
"ruleName": "pickle_deserialization",
"substrings": ["pickle"],
"reminder": "Security Warning: pickle with untrusted content can lead to arbitrary code execution. Use JSON instead.",
},
{
"ruleName": "os_system_injection",
"substrings": ["os.system", "from os import system"],
"reminder": "Security Warning: os.system should only use static arguments. Use subprocess.run with a list of arguments instead.",
},
]
def get_state_file(session_id):
return os.path.expanduser(f"~/.claude/security_warnings_state_{session_id}.json")
def cleanup_old_state_files():
try:
state_dir = os.path.expanduser("~/.claude")
if not os.path.exists(state_dir):
return
cutoff = datetime.now().timestamp() - (30 * 24 * 60 * 60)
for fn in os.listdir(state_dir):
if fn.startswith("security_warnings_state_") and fn.endswith(".json"):
fp = os.path.join(state_dir, fn)
try:
if os.path.getmtime(fp) < cutoff:
os.remove(fp)
except (OSError, IOError):
pass
except Exception:
pass
def load_state(session_id):
sf = get_state_file(session_id)
if os.path.exists(sf):
try:
with open(sf, "r") as f:
return set(json.load(f))
except (json.JSONDecodeError, IOError):
return set()
return set()
def save_state(session_id, shown):
sf = get_state_file(session_id)
try:
os.makedirs(os.path.dirname(sf), exist_ok=True)
with open(sf, "w") as f:
json.dump(list(shown), f)
except IOError:
pass
def check_patterns(file_path, content):
norm = file_path.lstrip("/")
for p in SECURITY_PATTERNS:
if "path_check" in p and p["path_check"](norm):
return p["ruleName"], p["reminder"]
if "substrings" in p and content:
for sub in p["substrings"]:
if sub in content:
return p["ruleName"], p["reminder"]
return None, None
def extract_content(tool_name, tool_input):
if tool_name == "Write":
return tool_input.get("content", "")
elif tool_name == "Edit":
return tool_input.get("new_string", "")
elif tool_name == "MultiEdit":
edits = tool_input.get("edits", [])
return " ".join(e.get("new_string", "") for e in edits) if edits else ""
return ""
def main():
if os.environ.get("ENABLE_SECURITY_REMINDER", "1") == "0":
sys.exit(0)
if random.random() < 0.1:
cleanup_old_state_files()
try:
data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
sys.exit(0)
session_id = data.get("session_id", "default")
tool_name = data.get("tool_name", "")
tool_input = data.get("tool_input", {})
if tool_name not in ["Edit", "Write", "MultiEdit"]:
sys.exit(0)
file_path = tool_input.get("file_path", "")
if not file_path:
sys.exit(0)
content = extract_content(tool_name, tool_input)
rule_name, reminder = check_patterns(file_path, content)
if rule_name and reminder:
key = f"{file_path}-{rule_name}"
shown = load_state(session_id)
if key not in shown:
shown.add(key)
save_state(session_id, shown)
print(reminder, file=sys.stderr)
sys.exit(2)
sys.exit(0)
if __name__ == "__main__":
main()