clawmoat 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +9 -0
- package/CHANGELOG.md +18 -0
- package/CONTRIBUTING.md +4 -2
- package/DEMO.md +87 -0
- package/Dockerfile +5 -18
- package/README.md +294 -8
- package/SECURITY.md +58 -10
- package/THREAT_MODEL.md +129 -0
- package/agent/README.md +131 -0
- package/agent/index.js +471 -0
- package/agent/install-service.sh +94 -0
- package/agent/openclaw-hook.js +453 -0
- package/agent/provider-setup.js +649 -0
- package/agent/setup.js +274 -0
- package/assets/BADGE-USAGE.md +20 -0
- package/assets/clawmoat-badge.svg +21 -0
- package/bin/clawmoat.js +468 -111
- package/docs/affiliates/dashboard.html +124 -0
- package/docs/affiliates/index.html +236 -0
- package/docs/agent-install.html +183 -0
- package/docs/ai-agent-security-scanner.html +10 -6
- package/docs/badge/index.html +149 -0
- package/docs/badge/scanning.svg +23 -0
- package/docs/blog/386-malicious-skills.html +262 -0
- package/docs/blog/40000-exposed-openclaw-instances.html +201 -0
- package/docs/blog/agent-trust-protocol.html +198 -0
- package/docs/blog/ai-agent-earns-commissions.html +230 -0
- package/docs/blog/bugmageddon-agent-firewall.html +174 -0
- package/docs/blog/calculator-math.html +180 -0
- package/docs/blog/clawmoat-vs-llamafirewall-nemo-guardrails.html +229 -0
- package/docs/blog/host-guardian-launch.html +18 -8
- package/docs/blog/ibm-experts-agent-runtime-protection.html +247 -0
- package/docs/blog/index.html +211 -9
- package/docs/blog/langchain-security-tutorial.html +18 -8
- package/docs/blog/mcp-30-cves-security-crisis.html +286 -0
- package/docs/blog/meta-researcher-rogue-agent.html +201 -0
- package/docs/blog/microsoft-openclaw-workstation-security.html +235 -0
- package/docs/blog/nist-ai-agent-standards-clawmoat.html +377 -0
- package/docs/blog/oasis-websocket-hijack.html +212 -0
- package/docs/blog/ollama-openclaw-security.html +160 -0
- package/docs/blog/openclaw-enterprise-readiness-claw10.html +199 -0
- package/docs/blog/openclaw-security-reckoning-2026.html +368 -0
- package/docs/blog/owasp-agentic-ai-top10.html +18 -8
- package/docs/blog/securing-ai-agents.html +18 -8
- package/docs/blog/supply-chain-agents.html +18 -8
- package/docs/business/index.html +525 -0
- package/docs/business/install.html +261 -0
- package/docs/checklist.html +174 -0
- package/docs/compare/index.html +122 -0
- package/docs/compare/lakera/index.html +62 -0
- package/docs/compare/llm-guard/index.html +49 -0
- package/docs/compare/snyk-agent-scan/index.html +63 -0
- package/docs/compare.html +10 -6
- package/docs/dashboard/index.html +520 -0
- package/docs/finance/index.html +220 -0
- package/docs/guides/business-deployment.html +770 -0
- package/docs/hall-of-fame.html +174 -0
- package/docs/index.html +447 -154
- package/docs/install.sh +557 -0
- package/docs/integrations/langchain.html +14 -6
- package/docs/integrations/openai.html +14 -6
- package/docs/integrations/openclaw.html +55 -7
- package/docs/plans/2026-03-26-threat-intel-api.md +255 -0
- package/docs/plans/2026-04-14-bugmageddon-marketing-pack.md +329 -0
- package/docs/plans/2026-04-14-clawmoat-v1-bugmageddon.md +248 -0
- package/docs/plans/2026-04-14-v1-release-update.md +91 -0
- package/docs/plans/2026-04-19-supabase-audit.md +68 -0
- package/docs/plans/2026-05-12-sales-push.md +303 -0
- package/docs/playground/index.html +893 -0
- package/docs/playground.html +4 -7
- package/docs/privacy-policy/index.html +122 -0
- package/docs/rfcs/defense-in-depth.md +467 -0
- package/docs/scan/index.html +358 -0
- package/docs/services/case-study.html +255 -0
- package/docs/services/downloads/install-openclaw.bat +45 -0
- package/docs/services/downloads/install-openclaw.command +38 -0
- package/docs/services/downloads/install-openclaw.sh +38 -0
- package/docs/services/get-started.html +165 -0
- package/docs/services/index.html +598 -0
- package/docs/services/multi-agent-security.html +284 -0
- package/docs/services/one-pager.html +99 -0
- package/docs/services/pitch-deck.html +229 -0
- package/docs/services/roi-calculator.html +258 -0
- package/docs/sitemap.xml +192 -2
- package/docs/support/index.html +135 -0
- package/docs/templates/customer-service/HEARTBEAT.md +61 -0
- package/docs/templates/customer-service/MEMORY.md +89 -0
- package/docs/templates/customer-service/SOUL.md +41 -0
- package/docs/templates/customer-service/USER.md +56 -0
- package/docs/templates/executive/HEARTBEAT.md +86 -0
- package/docs/templates/executive/MEMORY.md +92 -0
- package/docs/templates/executive/SOUL.md +44 -0
- package/docs/templates/executive/USER.md +62 -0
- package/docs/templates/finance/HEARTBEAT.md +58 -0
- package/docs/templates/finance/MEMORY.md +87 -0
- package/docs/templates/finance/SOUL.md +38 -0
- package/docs/templates/finance/USER.md +53 -0
- package/docs/templates/index.html +115 -0
- package/docs/templates/operations/HEARTBEAT.md +63 -0
- package/docs/templates/operations/MEMORY.md +68 -0
- package/docs/templates/operations/SOUL.md +38 -0
- package/docs/templates/operations/USER.md +49 -0
- package/docs/templates/sales/HEARTBEAT.md +55 -0
- package/docs/templates/sales/MEMORY.md +89 -0
- package/docs/templates/sales/SOUL.md +34 -0
- package/docs/templates/sales/USER.md +54 -0
- package/docs/terms-of-service/index.html +122 -0
- package/eslint.config.js +32 -0
- package/evals/README.md +29 -0
- package/evals/cases.json +390 -0
- package/evals/results.md +68 -0
- package/evals/run.js +180 -0
- package/examples/basic-usage.js +38 -0
- package/examples/demo-attack/demo.js +186 -0
- package/examples/python-quickstart/README.md +54 -0
- package/examples/python-quickstart/clawmoat_client.py +167 -0
- package/examples/video-demo/README.md +14 -0
- package/examples/video-demo/scene-a-normal.js +29 -0
- package/examples/video-demo/scene-b-attack-arrives.js +31 -0
- package/examples/video-demo/scene-c-hijack.js +44 -0
- package/examples/video-demo/scene-d-clawmoat.js +46 -0
- package/integrations/crewai/README.md +32 -0
- package/integrations/crewai/clawmoat_crewai/__init__.py +17 -0
- package/integrations/crewai/clawmoat_crewai/guard.py +103 -0
- package/integrations/crewai/pyproject.toml +21 -0
- package/integrations/langchain/README.md +91 -0
- package/integrations/langchain/clawmoat_langchain/__init__.py +17 -0
- package/integrations/langchain/clawmoat_langchain/callback.py +489 -0
- package/integrations/langchain/pyproject.toml +32 -0
- package/integrations/litellm/README.md +324 -0
- package/integrations/litellm/clawmoat_litellm/__init__.py +21 -0
- package/integrations/litellm/clawmoat_litellm/callback.py +329 -0
- package/integrations/litellm/clawmoat_litellm/proxy_middleware.py +224 -0
- package/integrations/litellm/pyproject.toml +74 -0
- package/integrations/openai-agents/README.md +392 -0
- package/integrations/openai-agents/clawmoat_openai_agents/__init__.py +20 -0
- package/integrations/openai-agents/clawmoat_openai_agents/guardrail.py +431 -0
- package/integrations/openai-agents/clawmoat_openai_agents/middleware.py +311 -0
- package/integrations/openai-agents/pyproject.toml +76 -0
- package/package.json +6 -5
- package/plugins/openclaw-adapter/PHASE1.md +439 -0
- package/plugins/openclaw-adapter/README.md +103 -0
- package/plugins/openclaw-adapter/SPEC.md +1644 -0
- package/plugins/openclaw-adapter/package.json +31 -0
- package/plugins/openclaw-adapter/src/index.test.ts +226 -0
- package/plugins/openclaw-adapter/src/index.ts +140 -0
- package/plugins/openclaw-adapter/tsconfig.json +14 -0
- package/server/data/threats.json +290 -0
- package/server/index.js +224 -10
- package/src/adapters/express.js +161 -0
- package/src/adapters/index.js +92 -0
- package/src/adapters/langchain.js +185 -0
- package/src/approval/index.js +456 -0
- package/src/ban-scanner.js +200 -0
- package/src/boundary-scanner.js +296 -0
- package/src/ci-scanner.js +279 -0
- package/src/code-scanner.js +245 -0
- package/src/enforce.js +166 -0
- package/src/finance/index.js +585 -0
- package/src/finance/mcp-firewall.js +486 -0
- package/src/formatters/json.js +80 -0
- package/src/formatters/sarif.js +388 -0
- package/src/guardian/alerts.js +34 -3
- package/src/guardian/gateway-monitor.js +590 -0
- package/src/guardian/index.js +41 -2
- package/src/index.js +105 -0
- package/src/integrations/agentmesh.js +501 -0
- package/src/language-detector.js +201 -0
- package/src/mcp-scanner.js +253 -0
- package/src/multimodal/index.js +579 -0
- package/src/obfuscation-scanner.js +457 -0
- package/src/policy-engine.js +402 -0
- package/src/scanners/dependency-attacks.js +128 -0
- package/src/scanners/prompt-injection.js +18 -0
- package/src/scanners/supply-chain.js +14 -0
- package/src/templates/default-config.yml +90 -0
- package/src/vuln-ops/exploitability.js +46 -0
- package/src/watch/live-monitor.js +720 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dangerous Code & Tool Argument Scanner
|
|
3
|
+
*
|
|
4
|
+
* Detects dangerous code patterns in tool arguments, prompts, and outputs:
|
|
5
|
+
* - Shell injection (bash, cmd, powershell)
|
|
6
|
+
* - Filesystem destruction
|
|
7
|
+
* - Credential/secret access
|
|
8
|
+
* - Network exfiltration
|
|
9
|
+
* - SQL injection
|
|
10
|
+
* - Code execution (eval, exec, subprocess)
|
|
11
|
+
* - Path traversal
|
|
12
|
+
* - Environment manipulation
|
|
13
|
+
*
|
|
14
|
+
* Context-aware: scores differently based on target tool.
|
|
15
|
+
*
|
|
16
|
+
* @module code-scanner
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
// Dangerous shell patterns
|
|
22
|
+
const SHELL_PATTERNS = [
|
|
23
|
+
{ re: /rm\s+(-[rf]+\s+)*[\/~]/, name: 'recursive delete from root/home', severity: 'critical' },
|
|
24
|
+
{ re: /rm\s+-[rf]*\s+\*/, name: 'wildcard recursive delete', severity: 'critical' },
|
|
25
|
+
{ re: /mkfs\b/, name: 'filesystem format', severity: 'critical' },
|
|
26
|
+
{ re: /dd\s+if=.*of=\/dev\//, name: 'disk overwrite', severity: 'critical' },
|
|
27
|
+
{ re: /:\(\)\s*\{\s*:\|:\s*&\s*\}\s*;?\s*:/, name: 'fork bomb', severity: 'critical' },
|
|
28
|
+
{ re: />\s*\/dev\/[sh]d[a-z]/, name: 'write to disk device', severity: 'critical' },
|
|
29
|
+
{ re: /curl\s+[^|]*\|\s*(ba)?sh/, name: 'curl pipe to shell', severity: 'critical' },
|
|
30
|
+
{ re: /wget\s+[^|]*\|\s*(ba)?sh/, name: 'wget pipe to shell', severity: 'critical' },
|
|
31
|
+
{ re: /\bchmod\s+[0-7]*777\b/, name: 'world-writable permissions', severity: 'high' },
|
|
32
|
+
{ re: /\bchmod\s+[+]s\b/, name: 'setuid permission', severity: 'critical' },
|
|
33
|
+
{ re: /\bchown\s+root\b/, name: 'change owner to root', severity: 'high' },
|
|
34
|
+
{ re: /nc\s+(-[a-z]+\s+)*.*\d+\s*(<|>|\|)/, name: 'netcat with redirect', severity: 'critical' },
|
|
35
|
+
{ re: /nc\s+-[a-z]*e\s/, name: 'netcat exec', severity: 'critical' },
|
|
36
|
+
{ re: /\bnohup\b.*&/, name: 'persistent background process', severity: 'medium' },
|
|
37
|
+
{ re: /crontab\s+-[er]/, name: 'crontab modification', severity: 'high' },
|
|
38
|
+
{ re: /\/etc\/passwd/, name: 'password file access', severity: 'high' },
|
|
39
|
+
{ re: /\/etc\/shadow/, name: 'shadow file access', severity: 'critical' },
|
|
40
|
+
{ re: /\bsudo\s/, name: 'privilege escalation (sudo)', severity: 'high' },
|
|
41
|
+
{ re: /\bsu\s+-?\s*root/, name: 'switch to root', severity: 'critical' },
|
|
42
|
+
{ re: /\bkill\s+-9\s/, name: 'force kill process', severity: 'medium' },
|
|
43
|
+
{ re: /\bkillall\b/, name: 'kill all matching processes', severity: 'high' },
|
|
44
|
+
{ re: /iptables\s+-[FA]/, name: 'firewall rule flush', severity: 'critical' },
|
|
45
|
+
{ re: /systemctl\s+(stop|disable)\s/, name: 'service stop/disable', severity: 'high' },
|
|
46
|
+
{ re: /\bshutdown\b|\breboot\b|\binit\s+[06]/, name: 'system shutdown/reboot', severity: 'critical' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// Credential/secret access patterns
|
|
50
|
+
const CREDENTIAL_PATTERNS = [
|
|
51
|
+
{ re: /~\/\.ssh\/(id_|authorized_keys|known_hosts|config)/, name: 'SSH key access', severity: 'critical' },
|
|
52
|
+
{ re: /~\/\.aws\/(credentials|config)/, name: 'AWS credential access', severity: 'critical' },
|
|
53
|
+
{ re: /~\/\.gnupg\//, name: 'GPG key access', severity: 'critical' },
|
|
54
|
+
{ re: /~\/\.env\b|\.env\b/, name: 'environment file access', severity: 'high' },
|
|
55
|
+
{ re: /~\/\.gitconfig\b/, name: 'git config access', severity: 'medium' },
|
|
56
|
+
{ re: /~\/\.npmrc\b/, name: 'npm config (may contain tokens)', severity: 'high' },
|
|
57
|
+
{ re: /~\/\.docker\/config\.json/, name: 'Docker config (may contain auth)', severity: 'high' },
|
|
58
|
+
{ re: /~\/\.kube\/config/, name: 'Kubernetes config', severity: 'critical' },
|
|
59
|
+
{ re: /keychain|keyring|wallet\.dat|\.bitcoin/, name: 'keychain/wallet access', severity: 'critical' },
|
|
60
|
+
{ re: /\/var\/run\/secrets\//, name: 'Kubernetes secrets mount', severity: 'critical' },
|
|
61
|
+
{ re: /process\.env\[?['"][A-Z_]*(KEY|SECRET|TOKEN|PASS|AUTH)/, name: 'env var credential access', severity: 'high' },
|
|
62
|
+
{ re: /os\.environ\[?['"][A-Z_]*(KEY|SECRET|TOKEN|PASS|AUTH)/, name: 'Python env credential access', severity: 'high' },
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
// Network exfiltration patterns
|
|
66
|
+
const EXFIL_PATTERNS = [
|
|
67
|
+
{ re: /curl\s+.*-d\s+.*@/, name: 'curl POST with file data', severity: 'high' },
|
|
68
|
+
{ re: /curl\s+.*--data-binary\s+@/, name: 'curl binary upload', severity: 'high' },
|
|
69
|
+
{ re: /curl\s+.*-T\s/, name: 'curl file upload', severity: 'high' },
|
|
70
|
+
{ re: /wget\s+--post-(?:data|file)/, name: 'wget data upload', severity: 'high' },
|
|
71
|
+
{ re: /scp\s+.*@.*:/, name: 'SCP file transfer', severity: 'high' },
|
|
72
|
+
{ re: /rsync\s+.*@.*:/, name: 'rsync to remote', severity: 'high' },
|
|
73
|
+
{ re: /base64\s+.*\|\s*curl/, name: 'base64 encode and exfiltrate', severity: 'critical' },
|
|
74
|
+
{ re: /tar\s+.*\|\s*(nc|curl|wget)/, name: 'archive and exfiltrate', severity: 'critical' },
|
|
75
|
+
{ re: /dns.*txt.*\|\s*(nc|curl)/, name: 'DNS exfiltration', severity: 'critical' },
|
|
76
|
+
{ re: /fetch\(['"]https?:\/\/(?!localhost|127\.0\.0\.1)/, name: 'JS fetch to external URL', severity: 'medium' },
|
|
77
|
+
{ re: /XMLHttpRequest|\.ajax\(|axios\.(post|put)/, name: 'HTTP client outbound', severity: 'medium' },
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
// SQL injection patterns
|
|
81
|
+
const SQL_PATTERNS = [
|
|
82
|
+
{ re: /'\s*(OR|AND)\s+'?\d*'?\s*=\s*'?\d*'?\s*--/, name: 'SQL injection (OR/AND tautology)', severity: 'critical' },
|
|
83
|
+
{ re: /UNION\s+(ALL\s+)?SELECT/i, name: 'SQL UNION injection', severity: 'critical' },
|
|
84
|
+
{ re: /;\s*DROP\s+TABLE/i, name: 'SQL DROP TABLE', severity: 'critical' },
|
|
85
|
+
{ re: /;\s*DELETE\s+FROM/i, name: 'SQL DELETE', severity: 'high' },
|
|
86
|
+
{ re: /;\s*UPDATE\s+.*SET\s+/i, name: 'SQL UPDATE injection', severity: 'high' },
|
|
87
|
+
{ re: /;\s*INSERT\s+INTO/i, name: 'SQL INSERT injection', severity: 'high' },
|
|
88
|
+
{ re: /EXEC(\s+|UTE\s+)(xp_|sp_)/i, name: 'SQL stored procedure execution', severity: 'critical' },
|
|
89
|
+
{ re: /INTO\s+OUTFILE/i, name: 'SQL file write', severity: 'critical' },
|
|
90
|
+
{ re: /LOAD_FILE\s*\(/i, name: 'SQL file read', severity: 'critical' },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
// Code execution patterns
|
|
94
|
+
const EXEC_PATTERNS = [
|
|
95
|
+
{ re: /\beval\s*\(/, name: 'eval() call', severity: 'high' },
|
|
96
|
+
{ re: /\bexec\s*\(/, name: 'exec() call', severity: 'high' },
|
|
97
|
+
{ re: /Function\s*\(/, name: 'Function constructor', severity: 'high' },
|
|
98
|
+
{ re: /child_process/, name: 'Node.js child_process', severity: 'high' },
|
|
99
|
+
{ re: /subprocess\.(run|Popen|call)\s*\(/, name: 'Python subprocess', severity: 'high' },
|
|
100
|
+
{ re: /os\.system\s*\(/, name: 'Python os.system', severity: 'high' },
|
|
101
|
+
{ re: /os\.popen\s*\(/, name: 'Python os.popen', severity: 'high' },
|
|
102
|
+
{ re: /\b__import__\s*\(/, name: 'Python dynamic import', severity: 'high' },
|
|
103
|
+
{ re: /Runtime\.getRuntime\(\)\.exec/, name: 'Java Runtime.exec', severity: 'high' },
|
|
104
|
+
{ re: /ProcessBuilder/, name: 'Java ProcessBuilder', severity: 'high' },
|
|
105
|
+
{ re: /System\.Diagnostics\.Process/, name: '.NET Process.Start', severity: 'high' },
|
|
106
|
+
{ re: /\bimport\s+ctypes\b/, name: 'Python ctypes (FFI)', severity: 'medium' },
|
|
107
|
+
{ re: /require\s*\(\s*['"]child_process['"]/, name: 'require child_process', severity: 'high' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
// Supply chain / CI patterns
|
|
111
|
+
const SUPPLY_CHAIN_PATTERNS = [
|
|
112
|
+
{ re: /npm\s+install\s+[\w@/-]*(?:telnyx@4\.87\.[12]|event-stream@3\.3\.6|ua-parser-js@0\.7\.29|coa@2\.0\.3|rc@1\.2\.9)/, name: 'known compromised package', severity: 'critical' },
|
|
113
|
+
{ re: /pip\s+install\s+[\w-]*(?:telnyx==4\.87\.[12])/, name: 'known compromised PyPI package', severity: 'critical' },
|
|
114
|
+
{ re: /\$\{\{\s*github\.event\./, name: 'GitHub Actions expression injection', severity: 'high' },
|
|
115
|
+
{ re: /\$\{\{\s*(?:inputs|secrets|env)\./, name: 'CI variable injection', severity: 'high' },
|
|
116
|
+
{ re: /"postinstall"\s*:\s*"[^"]*(?:curl|wget|bash|sh|nc|python)/, name: 'suspicious postinstall script', severity: 'critical' },
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
// Path traversal patterns
|
|
120
|
+
const TRAVERSAL_PATTERNS = [
|
|
121
|
+
{ re: /\.\.\/\.\.\//, name: 'path traversal (..)', severity: 'high' },
|
|
122
|
+
{ re: /\.\.\\\.\.\\/, name: 'path traversal (Windows)', severity: 'high' },
|
|
123
|
+
{ re: /%2e%2e(%2f|%5c)/i, name: 'URL-encoded path traversal', severity: 'high' },
|
|
124
|
+
{ re: /\/proc\/self\//, name: 'Linux proc self access', severity: 'critical' },
|
|
125
|
+
{ re: /\/dev\/(tcp|udp)\//, name: 'Bash /dev/tcp socket', severity: 'critical' },
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
// Tool risk multipliers — context matters
|
|
129
|
+
const TOOL_RISK = {
|
|
130
|
+
// High-risk tools: any finding is amplified
|
|
131
|
+
'exec': 2.0, 'shell': 2.0, 'bash': 2.0, 'terminal': 2.0, 'run_command': 2.0,
|
|
132
|
+
'write_file': 1.5, 'create_file': 1.5, 'edit_file': 1.2,
|
|
133
|
+
'http': 1.3, 'fetch': 1.3, 'request': 1.3, 'curl': 1.5,
|
|
134
|
+
'sql': 1.5, 'query': 1.3, 'database': 1.3,
|
|
135
|
+
// Lower-risk tools
|
|
136
|
+
'read_file': 0.8, 'list_files': 0.5, 'search': 0.5,
|
|
137
|
+
'chat': 0.7, 'respond': 0.5, 'think': 0.3,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Scan code/tool arguments for dangerous patterns
|
|
142
|
+
* @param {string} text - Code or tool args to scan
|
|
143
|
+
* @param {Object} [opts] - Options
|
|
144
|
+
* @param {string} [opts.tool] - Tool name for context-aware scoring
|
|
145
|
+
* @param {string} [opts.language] - Expected language ('shell', 'python', 'javascript', 'sql')
|
|
146
|
+
* @returns {Object} { safe, findings, score }
|
|
147
|
+
*/
|
|
148
|
+
function scanCode(text, opts = {}) {
|
|
149
|
+
const { tool, language } = opts;
|
|
150
|
+
|
|
151
|
+
const findings = [];
|
|
152
|
+
const allPatterns = [
|
|
153
|
+
...SHELL_PATTERNS.map(p => ({ ...p, category: 'shell' })),
|
|
154
|
+
...CREDENTIAL_PATTERNS.map(p => ({ ...p, category: 'credential_access' })),
|
|
155
|
+
...EXFIL_PATTERNS.map(p => ({ ...p, category: 'exfiltration' })),
|
|
156
|
+
...SQL_PATTERNS.map(p => ({ ...p, category: 'sql_injection' })),
|
|
157
|
+
...EXEC_PATTERNS.map(p => ({ ...p, category: 'code_execution' })),
|
|
158
|
+
...TRAVERSAL_PATTERNS.map(p => ({ ...p, category: 'path_traversal' })),
|
|
159
|
+
...SUPPLY_CHAIN_PATTERNS.map(p => ({ ...p, category: 'supply_chain' })),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
for (const pattern of allPatterns) {
|
|
163
|
+
// Skip SQL patterns if tool is clearly not DB-related
|
|
164
|
+
if (pattern.category === 'sql_injection' && tool && !/(sql|query|database|db)/i.test(tool)) {
|
|
165
|
+
// Still check but reduce confidence
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const match = pattern.re.exec(text);
|
|
169
|
+
if (match) {
|
|
170
|
+
findings.push({
|
|
171
|
+
type: 'dangerous_code',
|
|
172
|
+
subtype: pattern.category,
|
|
173
|
+
severity: pattern.severity,
|
|
174
|
+
confidence: 0.85,
|
|
175
|
+
evidence: `${pattern.name}: matched "${match[0].substring(0, 80)}"`,
|
|
176
|
+
pattern_name: pattern.name,
|
|
177
|
+
category: pattern.category,
|
|
178
|
+
recommended_action: pattern.severity === 'critical' ? 'block' : 'require_confirmation',
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Apply tool risk multiplier
|
|
184
|
+
const toolMultiplier = tool ? (TOOL_RISK[tool.toLowerCase()] || 1.0) : 1.0;
|
|
185
|
+
|
|
186
|
+
const baseScore = findings.reduce((s, f) => {
|
|
187
|
+
const w = { critical: 40, high: 25, medium: 15, low: 5 };
|
|
188
|
+
return s + (w[f.severity] || 5);
|
|
189
|
+
}, 0);
|
|
190
|
+
|
|
191
|
+
const score = Math.min(100, Math.round(baseScore * toolMultiplier));
|
|
192
|
+
|
|
193
|
+
const maxSeverity = findings.reduce((max, f) => {
|
|
194
|
+
const order = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
195
|
+
return (order[f.severity] || 0) > (order[max] || 0) ? f.severity : max;
|
|
196
|
+
}, 'low');
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
safe: findings.length === 0,
|
|
200
|
+
findings,
|
|
201
|
+
score,
|
|
202
|
+
maxSeverity: findings.length > 0 ? maxSeverity : null,
|
|
203
|
+
toolMultiplier,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Quick check if a tool call looks dangerous
|
|
209
|
+
* @param {string} tool - Tool name
|
|
210
|
+
* @param {Object|string} args - Tool arguments
|
|
211
|
+
* @returns {Object} { safe, risk, reason }
|
|
212
|
+
*/
|
|
213
|
+
function quickToolCheck(tool, args) {
|
|
214
|
+
const text = typeof args === 'string' ? args : JSON.stringify(args);
|
|
215
|
+
const result = scanCode(text, { tool });
|
|
216
|
+
|
|
217
|
+
if (result.findings.length === 0) {
|
|
218
|
+
return { safe: true, risk: 'none', reason: null };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const topFinding = result.findings.reduce((top, f) => {
|
|
222
|
+
const order = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
223
|
+
return (order[f.severity] || 0) > (order[top.severity] || 0) ? f : top;
|
|
224
|
+
}, result.findings[0]);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
safe: false,
|
|
228
|
+
risk: topFinding.severity,
|
|
229
|
+
reason: topFinding.evidence,
|
|
230
|
+
findings: result.findings.length,
|
|
231
|
+
score: result.score,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
scanCode,
|
|
237
|
+
quickToolCheck,
|
|
238
|
+
SHELL_PATTERNS,
|
|
239
|
+
CREDENTIAL_PATTERNS,
|
|
240
|
+
EXFIL_PATTERNS,
|
|
241
|
+
SQL_PATTERNS,
|
|
242
|
+
EXEC_PATTERNS,
|
|
243
|
+
TRAVERSAL_PATTERNS,
|
|
244
|
+
TOOL_RISK,
|
|
245
|
+
};
|
package/src/enforce.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawMoat Enforcement Mode — Actually block, not just detect
|
|
3
|
+
* Express/Fastify/Connect middleware + standalone enforce() wrapper
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Lazy-load to avoid circular dependency with index.js
|
|
7
|
+
let _moat = null;
|
|
8
|
+
function getMoat() {
|
|
9
|
+
if (!_moat) {
|
|
10
|
+
const ClawMoat = require('./index');
|
|
11
|
+
_moat = new ClawMoat();
|
|
12
|
+
}
|
|
13
|
+
return _moat;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ClawMoatBlocked extends Error {
|
|
17
|
+
constructor(findings) {
|
|
18
|
+
const top = findings[0];
|
|
19
|
+
super(`ClawMoat blocked: ${top.label} (${top.severity})`);
|
|
20
|
+
this.name = 'ClawMoatBlocked';
|
|
21
|
+
this.findings = findings;
|
|
22
|
+
this.severity = top.severity;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Enforce inbound content — throws ClawMoatBlocked on critical/high findings
|
|
28
|
+
* @param {string} text - Text to scan
|
|
29
|
+
* @param {object} options
|
|
30
|
+
* @param {string[]} options.blockOn - Severities to block on (default: ['critical', 'high'])
|
|
31
|
+
* @param {object} options.scanOptions - Options passed to scanInbound
|
|
32
|
+
* @returns {object} scan result if clean
|
|
33
|
+
* @throws {ClawMoatBlocked} if blocked
|
|
34
|
+
*/
|
|
35
|
+
function enforceInbound(text, options = {}) {
|
|
36
|
+
const { blockOn = ['critical', 'high'], scanOptions = {} } = options;
|
|
37
|
+
const result = getMoat().scanInbound(text, scanOptions);
|
|
38
|
+
const blocked = result.findings.filter(f => blockOn.includes(f.severity));
|
|
39
|
+
if (blocked.length > 0) {
|
|
40
|
+
throw new ClawMoatBlocked(blocked);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Enforce outbound content — throws ClawMoatBlocked on critical/high findings
|
|
47
|
+
* @param {string} text - Text to scan
|
|
48
|
+
* @param {object} options
|
|
49
|
+
* @returns {object} scan result if clean
|
|
50
|
+
* @throws {ClawMoatBlocked} if blocked
|
|
51
|
+
*/
|
|
52
|
+
function enforceOutbound(text, options = {}) {
|
|
53
|
+
const { blockOn = ['critical', 'high'], scanOptions = {} } = options;
|
|
54
|
+
const result = getMoat().scanOutbound(text, scanOptions);
|
|
55
|
+
const blocked = result.findings.filter(f => blockOn.includes(f.severity));
|
|
56
|
+
if (blocked.length > 0) {
|
|
57
|
+
throw new ClawMoatBlocked(blocked);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Express/Connect/Fastify middleware — blocks requests with dangerous content
|
|
64
|
+
* @param {object} options
|
|
65
|
+
* @param {string[]} options.blockOn - Severities to block on
|
|
66
|
+
* @param {boolean} options.scanBody - Scan request body (default: true)
|
|
67
|
+
* @param {boolean} options.scanQuery - Scan query params (default: true)
|
|
68
|
+
* @param {Function} options.onBlocked - Custom handler (req, res, findings) => void
|
|
69
|
+
* @returns {Function} middleware
|
|
70
|
+
*/
|
|
71
|
+
function middleware(options = {}) {
|
|
72
|
+
const {
|
|
73
|
+
blockOn = ['critical', 'high'],
|
|
74
|
+
scanBody = true,
|
|
75
|
+
scanQuery = true,
|
|
76
|
+
onBlocked = null,
|
|
77
|
+
} = options;
|
|
78
|
+
|
|
79
|
+
return function clawmoatFirewall(req, res, next) {
|
|
80
|
+
const texts = [];
|
|
81
|
+
|
|
82
|
+
// Collect text to scan
|
|
83
|
+
if (scanBody && req.body) {
|
|
84
|
+
if (typeof req.body === 'string') {
|
|
85
|
+
texts.push(req.body);
|
|
86
|
+
} else {
|
|
87
|
+
texts.push(JSON.stringify(req.body));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (scanQuery && req.query) {
|
|
91
|
+
texts.push(Object.values(req.query).join(' '));
|
|
92
|
+
}
|
|
93
|
+
if (req.params) {
|
|
94
|
+
texts.push(Object.values(req.params).join(' '));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const combined = texts.join('\n');
|
|
98
|
+
if (!combined.trim()) return next();
|
|
99
|
+
|
|
100
|
+
const result = getMoat().scanInbound(combined);
|
|
101
|
+
const blocked = result.findings.filter(f => blockOn.includes(f.severity));
|
|
102
|
+
|
|
103
|
+
if (blocked.length > 0) {
|
|
104
|
+
if (onBlocked) {
|
|
105
|
+
return onBlocked(req, res, blocked);
|
|
106
|
+
}
|
|
107
|
+
res.status(403);
|
|
108
|
+
if (typeof res.json === 'function') {
|
|
109
|
+
return res.json({
|
|
110
|
+
error: 'Blocked by ClawMoat Agent Firewall',
|
|
111
|
+
severity: blocked[0].severity,
|
|
112
|
+
findings: blocked.map(f => ({
|
|
113
|
+
label: f.label,
|
|
114
|
+
severity: f.severity,
|
|
115
|
+
category: f.category,
|
|
116
|
+
})),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
res.end(JSON.stringify({ error: 'Blocked by ClawMoat Agent Firewall' }));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Attach scan result for downstream use
|
|
124
|
+
req.clawmoat = result;
|
|
125
|
+
next();
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Wrap an async function with ClawMoat enforcement
|
|
131
|
+
* @param {Function} fn - async function(input) => output
|
|
132
|
+
* @param {object} options
|
|
133
|
+
* @returns {Function} wrapped function that scans input and output
|
|
134
|
+
*/
|
|
135
|
+
function wrap(fn, options = {}) {
|
|
136
|
+
const { blockOn = ['critical', 'high'] } = options;
|
|
137
|
+
|
|
138
|
+
return async function clawmoatWrapped(input, ...args) {
|
|
139
|
+
// Scan input
|
|
140
|
+
if (typeof input === 'string') {
|
|
141
|
+
enforceInbound(input, { blockOn });
|
|
142
|
+
} else if (input && typeof input === 'object') {
|
|
143
|
+
enforceInbound(JSON.stringify(input), { blockOn });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Execute
|
|
147
|
+
const output = await fn(input, ...args);
|
|
148
|
+
|
|
149
|
+
// Scan output
|
|
150
|
+
if (typeof output === 'string') {
|
|
151
|
+
enforceOutbound(output, { blockOn });
|
|
152
|
+
} else if (output && typeof output === 'object') {
|
|
153
|
+
enforceOutbound(JSON.stringify(output), { blockOn });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return output;
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
enforceInbound,
|
|
162
|
+
enforceOutbound,
|
|
163
|
+
middleware,
|
|
164
|
+
wrap,
|
|
165
|
+
ClawMoatBlocked,
|
|
166
|
+
};
|