clawmoat 0.8.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.
Files changed (171) hide show
  1. package/.dockerignore +9 -0
  2. package/CHANGELOG.md +18 -0
  3. package/DEMO.md +87 -0
  4. package/Dockerfile +5 -18
  5. package/README.md +232 -8
  6. package/THREAT_MODEL.md +129 -0
  7. package/agent/README.md +131 -0
  8. package/agent/index.js +471 -0
  9. package/agent/install-service.sh +94 -0
  10. package/agent/openclaw-hook.js +453 -0
  11. package/agent/provider-setup.js +649 -0
  12. package/agent/setup.js +274 -0
  13. package/assets/BADGE-USAGE.md +20 -0
  14. package/assets/clawmoat-badge.svg +21 -0
  15. package/bin/clawmoat.js +468 -111
  16. package/docs/affiliates/dashboard.html +124 -0
  17. package/docs/affiliates/index.html +236 -0
  18. package/docs/agent-install.html +183 -0
  19. package/docs/ai-agent-security-scanner.html +10 -6
  20. package/docs/badge/index.html +149 -0
  21. package/docs/badge/scanning.svg +23 -0
  22. package/docs/blog/386-malicious-skills.html +11 -4
  23. package/docs/blog/40000-exposed-openclaw-instances.html +11 -4
  24. package/docs/blog/agent-trust-protocol.html +5 -4
  25. package/docs/blog/ai-agent-earns-commissions.html +230 -0
  26. package/docs/blog/bugmageddon-agent-firewall.html +174 -0
  27. package/docs/blog/calculator-math.html +180 -0
  28. package/docs/blog/clawmoat-vs-llamafirewall-nemo-guardrails.html +10 -4
  29. package/docs/blog/host-guardian-launch.html +18 -8
  30. package/docs/blog/ibm-experts-agent-runtime-protection.html +15 -6
  31. package/docs/blog/index.html +67 -9
  32. package/docs/blog/langchain-security-tutorial.html +18 -8
  33. package/docs/blog/mcp-30-cves-security-crisis.html +11 -4
  34. package/docs/blog/meta-researcher-rogue-agent.html +201 -0
  35. package/docs/blog/microsoft-openclaw-workstation-security.html +5 -4
  36. package/docs/blog/nist-ai-agent-standards-clawmoat.html +16 -8
  37. package/docs/blog/oasis-websocket-hijack.html +11 -4
  38. package/docs/blog/ollama-openclaw-security.html +10 -4
  39. package/docs/blog/openclaw-enterprise-readiness-claw10.html +5 -4
  40. package/docs/blog/openclaw-security-reckoning-2026.html +11 -4
  41. package/docs/blog/owasp-agentic-ai-top10.html +18 -8
  42. package/docs/blog/securing-ai-agents.html +18 -8
  43. package/docs/blog/supply-chain-agents.html +18 -8
  44. package/docs/business/index.html +11 -16
  45. package/docs/business/install.html +21 -7
  46. package/docs/checklist.html +10 -4
  47. package/docs/compare/index.html +122 -0
  48. package/docs/compare/lakera/index.html +62 -0
  49. package/docs/compare/llm-guard/index.html +49 -0
  50. package/docs/compare/snyk-agent-scan/index.html +63 -0
  51. package/docs/compare.html +10 -6
  52. package/docs/dashboard/index.html +520 -0
  53. package/docs/finance/index.html +9 -6
  54. package/docs/guides/business-deployment.html +770 -0
  55. package/docs/hall-of-fame.html +11 -5
  56. package/docs/index.html +266 -137
  57. package/docs/integrations/langchain.html +14 -6
  58. package/docs/integrations/openai.html +14 -6
  59. package/docs/integrations/openclaw.html +55 -7
  60. package/docs/plans/2026-03-26-threat-intel-api.md +255 -0
  61. package/docs/plans/2026-04-14-bugmageddon-marketing-pack.md +329 -0
  62. package/docs/plans/2026-04-14-clawmoat-v1-bugmageddon.md +248 -0
  63. package/docs/plans/2026-04-14-v1-release-update.md +91 -0
  64. package/docs/plans/2026-04-19-supabase-audit.md +68 -0
  65. package/docs/plans/2026-05-12-sales-push.md +303 -0
  66. package/docs/playground/index.html +893 -0
  67. package/docs/playground.html +4 -7
  68. package/docs/rfcs/defense-in-depth.md +467 -0
  69. package/docs/scan/index.html +156 -12
  70. package/docs/services/case-study.html +255 -0
  71. package/docs/services/downloads/install-openclaw.bat +45 -0
  72. package/docs/services/downloads/install-openclaw.command +38 -0
  73. package/docs/services/downloads/install-openclaw.sh +38 -0
  74. package/docs/services/get-started.html +165 -0
  75. package/docs/services/index.html +598 -0
  76. package/docs/services/multi-agent-security.html +284 -0
  77. package/docs/services/one-pager.html +99 -0
  78. package/docs/services/pitch-deck.html +229 -0
  79. package/docs/services/roi-calculator.html +258 -0
  80. package/docs/sitemap.xml +62 -2
  81. package/docs/support/index.html +12 -1
  82. package/docs/templates/customer-service/HEARTBEAT.md +61 -0
  83. package/docs/templates/customer-service/MEMORY.md +89 -0
  84. package/docs/templates/customer-service/SOUL.md +41 -0
  85. package/docs/templates/customer-service/USER.md +56 -0
  86. package/docs/templates/executive/HEARTBEAT.md +86 -0
  87. package/docs/templates/executive/MEMORY.md +92 -0
  88. package/docs/templates/executive/SOUL.md +44 -0
  89. package/docs/templates/executive/USER.md +62 -0
  90. package/docs/templates/finance/HEARTBEAT.md +58 -0
  91. package/docs/templates/finance/MEMORY.md +87 -0
  92. package/docs/templates/finance/SOUL.md +38 -0
  93. package/docs/templates/finance/USER.md +53 -0
  94. package/docs/templates/index.html +115 -0
  95. package/docs/templates/operations/HEARTBEAT.md +63 -0
  96. package/docs/templates/operations/MEMORY.md +68 -0
  97. package/docs/templates/operations/SOUL.md +38 -0
  98. package/docs/templates/operations/USER.md +49 -0
  99. package/docs/templates/sales/HEARTBEAT.md +55 -0
  100. package/docs/templates/sales/MEMORY.md +89 -0
  101. package/docs/templates/sales/SOUL.md +34 -0
  102. package/docs/templates/sales/USER.md +54 -0
  103. package/eslint.config.js +32 -0
  104. package/evals/README.md +29 -0
  105. package/evals/cases.json +390 -0
  106. package/evals/results.md +68 -0
  107. package/evals/run.js +180 -0
  108. package/examples/demo-attack/demo.js +186 -0
  109. package/examples/python-quickstart/README.md +54 -0
  110. package/examples/python-quickstart/clawmoat_client.py +167 -0
  111. package/examples/video-demo/README.md +14 -0
  112. package/examples/video-demo/scene-a-normal.js +29 -0
  113. package/examples/video-demo/scene-b-attack-arrives.js +31 -0
  114. package/examples/video-demo/scene-c-hijack.js +44 -0
  115. package/examples/video-demo/scene-d-clawmoat.js +46 -0
  116. package/integrations/crewai/README.md +32 -0
  117. package/integrations/crewai/clawmoat_crewai/__init__.py +17 -0
  118. package/integrations/crewai/clawmoat_crewai/guard.py +103 -0
  119. package/integrations/crewai/pyproject.toml +21 -0
  120. package/integrations/langchain/README.md +91 -0
  121. package/integrations/langchain/clawmoat_langchain/__init__.py +17 -0
  122. package/integrations/langchain/clawmoat_langchain/callback.py +489 -0
  123. package/integrations/langchain/pyproject.toml +32 -0
  124. package/integrations/litellm/README.md +324 -0
  125. package/integrations/litellm/clawmoat_litellm/__init__.py +21 -0
  126. package/integrations/litellm/clawmoat_litellm/callback.py +329 -0
  127. package/integrations/litellm/clawmoat_litellm/proxy_middleware.py +224 -0
  128. package/integrations/litellm/pyproject.toml +74 -0
  129. package/integrations/openai-agents/README.md +392 -0
  130. package/integrations/openai-agents/clawmoat_openai_agents/__init__.py +20 -0
  131. package/integrations/openai-agents/clawmoat_openai_agents/guardrail.py +431 -0
  132. package/integrations/openai-agents/clawmoat_openai_agents/middleware.py +311 -0
  133. package/integrations/openai-agents/pyproject.toml +76 -0
  134. package/package.json +6 -5
  135. package/plugins/openclaw-adapter/PHASE1.md +439 -0
  136. package/plugins/openclaw-adapter/README.md +103 -0
  137. package/plugins/openclaw-adapter/SPEC.md +1644 -0
  138. package/plugins/openclaw-adapter/package.json +31 -0
  139. package/plugins/openclaw-adapter/src/index.test.ts +226 -0
  140. package/plugins/openclaw-adapter/src/index.ts +140 -0
  141. package/plugins/openclaw-adapter/tsconfig.json +14 -0
  142. package/server/data/threats.json +290 -0
  143. package/server/index.js +142 -7
  144. package/src/adapters/express.js +161 -0
  145. package/src/adapters/index.js +92 -0
  146. package/src/adapters/langchain.js +185 -0
  147. package/src/approval/index.js +456 -0
  148. package/src/ban-scanner.js +200 -0
  149. package/src/boundary-scanner.js +296 -0
  150. package/src/ci-scanner.js +279 -0
  151. package/src/code-scanner.js +245 -0
  152. package/src/enforce.js +166 -0
  153. package/src/formatters/json.js +80 -0
  154. package/src/formatters/sarif.js +388 -0
  155. package/src/guardian/alerts.js +34 -3
  156. package/src/guardian/index.js +41 -2
  157. package/src/index.js +102 -0
  158. package/src/integrations/agentmesh.js +501 -0
  159. package/src/language-detector.js +201 -0
  160. package/src/mcp-scanner.js +253 -0
  161. package/src/multimodal/index.js +579 -0
  162. package/src/obfuscation-scanner.js +457 -0
  163. package/src/policy-engine.js +402 -0
  164. package/src/scanners/dependency-attacks.js +128 -0
  165. package/src/scanners/prompt-injection.js +18 -0
  166. package/src/scanners/supply-chain.js +14 -0
  167. package/src/templates/default-config.yml +90 -0
  168. package/src/vuln-ops/exploitability.js +46 -0
  169. package/src/watch/live-monitor.js +720 -0
  170. package/clawmoat-0.8.0.tgz +0 -0
  171. package/server/index.js.patch +0 -1
@@ -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
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * JSON Report Formatter for ClawMoat
3
+ *
4
+ * Converts scan and report data into structured JSON format
5
+ * for programmatic consumption by monitoring dashboards, CI/CD, etc.
6
+ */
7
+
8
+ const VERSION = require('../../package.json').version;
9
+
10
+ function formatReport(reportData) {
11
+ const timestamp = new Date().toISOString();
12
+ const period = '24h'; // Last 24 hours, as per current implementation
13
+
14
+ return {
15
+ timestamp,
16
+ period,
17
+ version: VERSION,
18
+ summary: {
19
+ sessions_active: reportData.recentFiles || 0,
20
+ total_entries: reportData.totalEntries || 0,
21
+ tool_calls: reportData.toolCalls || 0,
22
+ threats_detected: reportData.threats || 0,
23
+ insider_threats: reportData.insiderThreats || 0,
24
+ highest_insider_risk_score: reportData.insiderHighScore || 0
25
+ },
26
+ tool_usage: reportData.toolUsage || {},
27
+ network_egress: {
28
+ total_urls: reportData.netResult?.totalUrls || 0,
29
+ unique_domains: reportData.netResult?.domains?.length || 0,
30
+ flagged_domains: reportData.netResult?.flagged || [],
31
+ bad_domains: reportData.netResult?.badDomains || []
32
+ },
33
+ findings: reportData.findings || [],
34
+ period_start: new Date(Date.now() - 86400000).toISOString(),
35
+ period_end: timestamp
36
+ };
37
+ }
38
+
39
+ function formatScanResult(result) {
40
+ const timestamp = new Date().toISOString();
41
+
42
+ return {
43
+ timestamp,
44
+ version: VERSION,
45
+ safe: result.safe || false,
46
+ total_findings: result.findings?.length || 0,
47
+ findings: (result.findings || []).map(finding => ({
48
+ type: finding.type,
49
+ subtype: finding.subtype || null,
50
+ severity: finding.severity || 'medium',
51
+ message: finding.reason || finding.type,
52
+ matched_text: finding.matched || null,
53
+ confidence: finding.confidence || 1.0
54
+ })),
55
+ scan_context: result.context || 'unknown'
56
+ };
57
+ }
58
+
59
+ function formatAuditResult(auditData) {
60
+ const timestamp = new Date().toISOString();
61
+
62
+ return {
63
+ timestamp,
64
+ version: VERSION,
65
+ summary: {
66
+ files_scanned: auditData.filesScanned || 0,
67
+ total_findings: auditData.totalFindings || 0,
68
+ sessions_directory: auditData.sessionDir || 'unknown'
69
+ },
70
+ findings_by_file: auditData.findingsByFile || {},
71
+ findings: auditData.findings || [],
72
+ scan_context: 'session_audit'
73
+ };
74
+ }
75
+
76
+ module.exports = {
77
+ formatReport,
78
+ formatScanResult,
79
+ formatAuditResult
80
+ };