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,388 @@
1
+ /**
2
+ * SARIF (Static Analysis Results Interchange Format) Output
3
+ *
4
+ * Converts ClawMoat scan results to SARIF v2.1.0 JSON format
5
+ * for integration with GitHub Code Scanning, Azure DevOps, etc.
6
+ */
7
+
8
+ const VERSION = require('../../package.json').version;
9
+
10
+ // Map ClawMoat severities to SARIF levels
11
+ const SEVERITY_MAP = {
12
+ 'critical': 'error',
13
+ 'high': 'error',
14
+ 'medium': 'warning',
15
+ 'low': 'note'
16
+ };
17
+
18
+ // Define SARIF rules for ClawMoat detectors
19
+ const RULES = {
20
+ 'prompt_injection': {
21
+ id: 'clawmoat/prompt-injection',
22
+ name: 'PromptInjection',
23
+ shortDescription: { text: 'Prompt injection attack detected' },
24
+ fullDescription: { text: 'Detects attempts to override AI system instructions or manipulate behavior through malicious prompts.' },
25
+ defaultConfiguration: { level: 'error' },
26
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/prompt-injection',
27
+ properties: {
28
+ category: 'security',
29
+ tags: ['ai-security', 'prompt-injection']
30
+ }
31
+ },
32
+ 'jailbreak': {
33
+ id: 'clawmoat/jailbreak',
34
+ name: 'JailbreakAttempt',
35
+ shortDescription: { text: 'AI jailbreak attempt detected' },
36
+ fullDescription: { text: 'Detects attempts to bypass AI safety mechanisms through role-playing or mode-switching attacks.' },
37
+ defaultConfiguration: { level: 'error' },
38
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/jailbreak',
39
+ properties: {
40
+ category: 'security',
41
+ tags: ['ai-security', 'jailbreak']
42
+ }
43
+ },
44
+ 'secret_leak': {
45
+ id: 'clawmoat/secret-leak',
46
+ name: 'SecretLeak',
47
+ shortDescription: { text: 'Secret or credential detected' },
48
+ fullDescription: { text: 'Detects API keys, tokens, passwords, or other sensitive credentials in content.' },
49
+ defaultConfiguration: { level: 'error' },
50
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/secret-detection',
51
+ properties: {
52
+ category: 'security',
53
+ tags: ['credentials', 'secrets']
54
+ }
55
+ },
56
+ 'pii_leak': {
57
+ id: 'clawmoat/pii-leak',
58
+ name: 'PersonallyIdentifiableInformation',
59
+ shortDescription: { text: 'PII detected in output' },
60
+ fullDescription: { text: 'Detects personally identifiable information such as SSN, credit card numbers, or email addresses.' },
61
+ defaultConfiguration: { level: 'warning' },
62
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/pii-detection',
63
+ properties: {
64
+ category: 'privacy',
65
+ tags: ['pii', 'privacy']
66
+ }
67
+ },
68
+ 'url_suspicious': {
69
+ id: 'clawmoat/suspicious-url',
70
+ name: 'SuspiciousUrl',
71
+ shortDescription: { text: 'Suspicious URL detected' },
72
+ fullDescription: { text: 'Detects potentially malicious URLs including IP addresses, data URLs, or suspicious domains.' },
73
+ defaultConfiguration: { level: 'warning' },
74
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/url-detection',
75
+ properties: {
76
+ category: 'security',
77
+ tags: ['urls', 'phishing']
78
+ }
79
+ },
80
+ 'memory_poisoning': {
81
+ id: 'clawmoat/memory-poisoning',
82
+ name: 'MemoryPoisoning',
83
+ shortDescription: { text: 'Memory poisoning attempt detected' },
84
+ fullDescription: { text: 'Detects attempts to modify AI memory files or inject persistent malicious instructions.' },
85
+ defaultConfiguration: { level: 'error' },
86
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/memory-poisoning',
87
+ properties: {
88
+ category: 'security',
89
+ tags: ['ai-security', 'memory-poisoning']
90
+ }
91
+ },
92
+ 'exfiltration': {
93
+ id: 'clawmoat/data-exfiltration',
94
+ name: 'DataExfiltration',
95
+ shortDescription: { text: 'Data exfiltration attempt detected' },
96
+ fullDescription: { text: 'Detects commands or patterns that could be used to steal or upload sensitive data.' },
97
+ defaultConfiguration: { level: 'error' },
98
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/exfiltration',
99
+ properties: {
100
+ category: 'security',
101
+ tags: ['exfiltration', 'data-theft']
102
+ }
103
+ },
104
+ 'excessive_agency': {
105
+ id: 'clawmoat/excessive-agency',
106
+ name: 'ExcessiveAgency',
107
+ shortDescription: { text: 'Excessive privilege or agency request' },
108
+ fullDescription: { text: 'Detects attempts to gain unauthorized privileges, bypass approvals, or operate autonomously.' },
109
+ defaultConfiguration: { level: 'warning' },
110
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/excessive-agency',
111
+ properties: {
112
+ category: 'security',
113
+ tags: ['privilege-escalation', 'agency']
114
+ }
115
+ },
116
+ 'supply_chain': {
117
+ id: 'clawmoat/supply-chain',
118
+ name: 'SupplyChainThreat',
119
+ shortDescription: { text: 'Supply chain threat detected' },
120
+ fullDescription: { text: 'Detects malicious patterns in skill content or external dependencies.' },
121
+ defaultConfiguration: { level: 'error' },
122
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/supply-chain',
123
+ properties: {
124
+ category: 'security',
125
+ tags: ['supply-chain', 'malware']
126
+ }
127
+ },
128
+ 'insider_threat': {
129
+ id: 'clawmoat/insider-threat',
130
+ name: 'InsiderThreat',
131
+ shortDescription: { text: 'Insider threat behavior detected' },
132
+ fullDescription: { text: 'Detects AI behaviors indicative of insider threats such as self-preservation, blackmail, or deception.' },
133
+ defaultConfiguration: { level: 'error' },
134
+ helpUri: 'https://github.com/darfaz/clawmoat/wiki/insider-threats',
135
+ properties: {
136
+ category: 'security',
137
+ tags: ['insider-threat', 'ai-alignment']
138
+ }
139
+ }
140
+ };
141
+
142
+ function formatScanResultAsSarif(scanResult, sourceFile = 'stdin') {
143
+ const timestamp = new Date().toISOString();
144
+ const usedRules = new Set();
145
+ const results = [];
146
+
147
+ // Convert findings to SARIF results
148
+ if (scanResult.findings) {
149
+ for (const finding of scanResult.findings) {
150
+ const ruleId = getRuleIdForFinding(finding);
151
+ usedRules.add(ruleId);
152
+
153
+ const result = {
154
+ ruleId,
155
+ ruleIndex: 0, // Will be updated after we know the rule order
156
+ level: SEVERITY_MAP[finding.severity] || 'warning',
157
+ message: {
158
+ text: finding.reason || finding.type || 'Security threat detected'
159
+ },
160
+ locations: [{
161
+ physicalLocation: {
162
+ artifactLocation: {
163
+ uri: sourceFile,
164
+ uriBaseId: '%SRCROOT%'
165
+ },
166
+ region: {
167
+ startLine: 1,
168
+ startColumn: 1,
169
+ endLine: 1,
170
+ endColumn: 100,
171
+ snippet: {
172
+ text: finding.matched || '(content not available)'
173
+ }
174
+ }
175
+ }
176
+ }],
177
+ partialFingerprints: {
178
+ primaryLocationLineHash: hashString(finding.matched || finding.type)
179
+ },
180
+ properties: {
181
+ confidence: finding.confidence || 1.0,
182
+ severity: finding.severity,
183
+ subtype: finding.subtype || null
184
+ }
185
+ };
186
+
187
+ results.push(result);
188
+ }
189
+ }
190
+
191
+ // Build rules array from used rules
192
+ const rulesArray = Array.from(usedRules).map(ruleId => {
193
+ return RULES[ruleId] || createDefaultRule(ruleId);
194
+ });
195
+
196
+ // Update rule indices in results
197
+ const ruleIndexMap = {};
198
+ rulesArray.forEach((rule, index) => {
199
+ ruleIndexMap[rule.id] = index;
200
+ });
201
+
202
+ results.forEach(result => {
203
+ result.ruleIndex = ruleIndexMap[result.ruleId] || 0;
204
+ });
205
+
206
+ return {
207
+ $schema: 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json',
208
+ version: '2.1.0',
209
+ runs: [{
210
+ tool: {
211
+ driver: {
212
+ name: 'ClawMoat',
213
+ version: VERSION,
214
+ informationUri: 'https://github.com/darfaz/clawmoat',
215
+ rules: rulesArray,
216
+ organization: 'ClawMoat Project',
217
+ shortDescription: { text: 'AI security moat for agents' },
218
+ fullDescription: { text: 'Runtime protection against prompt injection, tool misuse, and data exfiltration for AI agents.' }
219
+ }
220
+ },
221
+ results,
222
+ invocations: [{
223
+ executionSuccessful: true,
224
+ startTimeUtc: timestamp,
225
+ endTimeUtc: timestamp
226
+ }],
227
+ artifacts: [{
228
+ location: {
229
+ uri: sourceFile,
230
+ uriBaseId: '%SRCROOT%'
231
+ },
232
+ length: -1,
233
+ mimeType: 'text/plain'
234
+ }]
235
+ }]
236
+ };
237
+ }
238
+
239
+ function formatAuditResultAsSarif(auditData) {
240
+ const timestamp = new Date().toISOString();
241
+ const usedRules = new Set();
242
+ const results = [];
243
+ const artifacts = [];
244
+
245
+ // Process findings from all files
246
+ if (auditData.findings) {
247
+ for (const finding of auditData.findings) {
248
+ const ruleId = getRuleIdForFinding(finding);
249
+ usedRules.add(ruleId);
250
+
251
+ const sourceFile = finding.source || 'session.jsonl';
252
+
253
+ // Add to artifacts if not already present
254
+ if (!artifacts.find(a => a.location.uri === sourceFile)) {
255
+ artifacts.push({
256
+ location: {
257
+ uri: sourceFile,
258
+ uriBaseId: '%SRCROOT%'
259
+ },
260
+ length: -1,
261
+ mimeType: 'application/x-jsonlines'
262
+ });
263
+ }
264
+
265
+ const result = {
266
+ ruleId,
267
+ ruleIndex: 0,
268
+ level: SEVERITY_MAP[finding.severity] || 'warning',
269
+ message: {
270
+ text: finding.reason || finding.type || 'Security threat detected'
271
+ },
272
+ locations: [{
273
+ physicalLocation: {
274
+ artifactLocation: {
275
+ uri: sourceFile,
276
+ uriBaseId: '%SRCROOT%'
277
+ },
278
+ region: {
279
+ startLine: 1,
280
+ startColumn: 1
281
+ }
282
+ }
283
+ }],
284
+ partialFingerprints: {
285
+ primaryLocationLineHash: hashString(finding.matched || finding.type)
286
+ },
287
+ properties: {
288
+ confidence: finding.confidence || 1.0,
289
+ severity: finding.severity,
290
+ subtype: finding.subtype || null,
291
+ entry_id: finding.entry_id || null
292
+ }
293
+ };
294
+
295
+ results.push(result);
296
+ }
297
+ }
298
+
299
+ const rulesArray = Array.from(usedRules).map(ruleId => {
300
+ return RULES[ruleId] || createDefaultRule(ruleId);
301
+ });
302
+
303
+ const ruleIndexMap = {};
304
+ rulesArray.forEach((rule, index) => {
305
+ ruleIndexMap[rule.id] = index;
306
+ });
307
+
308
+ results.forEach(result => {
309
+ result.ruleIndex = ruleIndexMap[result.ruleId] || 0;
310
+ });
311
+
312
+ return {
313
+ $schema: 'https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json',
314
+ version: '2.1.0',
315
+ runs: [{
316
+ tool: {
317
+ driver: {
318
+ name: 'ClawMoat',
319
+ version: VERSION,
320
+ informationUri: 'https://github.com/darfaz/clawmoat',
321
+ rules: rulesArray,
322
+ organization: 'ClawMoat Project',
323
+ shortDescription: { text: 'AI security moat for agents' },
324
+ fullDescription: { text: 'Runtime protection against prompt injection, tool misuse, and data exfiltration for AI agents.' }
325
+ }
326
+ },
327
+ results,
328
+ invocations: [{
329
+ executionSuccessful: true,
330
+ startTimeUtc: timestamp,
331
+ endTimeUtc: timestamp
332
+ }],
333
+ artifacts
334
+ }]
335
+ };
336
+ }
337
+
338
+ function getRuleIdForFinding(finding) {
339
+ // Map ClawMoat finding types to SARIF rule IDs
340
+ const type = finding.type || 'unknown';
341
+ const subtype = finding.subtype;
342
+
343
+ if (type === 'prompt_injection') return 'clawmoat/prompt-injection';
344
+ if (type === 'jailbreak') return 'clawmoat/jailbreak';
345
+ if (type === 'secret' || type === 'secrets') return 'clawmoat/secret-leak';
346
+ if (type === 'pii') return 'clawmoat/pii-leak';
347
+ if (type === 'url') return 'clawmoat/suspicious-url';
348
+ if (type === 'memory_poisoning') return 'clawmoat/memory-poisoning';
349
+ if (type === 'exfiltration') return 'clawmoat/data-exfiltration';
350
+ if (type === 'excessive_agency') return 'clawmoat/excessive-agency';
351
+ if (type === 'supply_chain') return 'clawmoat/supply-chain';
352
+ if (type === 'insider_threat') return 'clawmoat/insider-threat';
353
+
354
+ // Fallback: create a generic rule ID
355
+ return `clawmoat/${type}`;
356
+ }
357
+
358
+ function createDefaultRule(ruleId) {
359
+ const cleanId = ruleId.replace('clawmoat/', '');
360
+ return {
361
+ id: ruleId,
362
+ name: cleanId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''),
363
+ shortDescription: { text: `${cleanId} threat detected` },
364
+ fullDescription: { text: `ClawMoat detected a potential ${cleanId} security threat.` },
365
+ defaultConfiguration: { level: 'warning' },
366
+ helpUri: 'https://github.com/darfaz/clawmoat',
367
+ properties: {
368
+ category: 'security',
369
+ tags: ['clawmoat', cleanId]
370
+ }
371
+ };
372
+ }
373
+
374
+ function hashString(str) {
375
+ // Simple hash for fingerprinting
376
+ let hash = 0;
377
+ for (let i = 0; i < str.length; i++) {
378
+ const char = str.charCodeAt(i);
379
+ hash = ((hash << 5) - hash) + char;
380
+ hash = hash & hash; // Convert to 32-bit integer
381
+ }
382
+ return hash.toString(16);
383
+ }
384
+
385
+ module.exports = {
386
+ formatScanResultAsSarif,
387
+ formatAuditResultAsSarif
388
+ };
@@ -22,11 +22,13 @@ class AlertManager {
22
22
  * @param {string} [opts.webhookUrl] - URL for webhook channel
23
23
  * @param {number} [opts.rateLimitMs] - Min ms between duplicate alerts (default: 300000 = 5 min)
24
24
  * @param {boolean} [opts.quiet] - Suppress console output
25
+ * @param {string} [opts.webhookTemplate] - Custom template for webhook payloads (mustache-style)
25
26
  */
26
27
  constructor(opts = {}) {
27
28
  this.channels = opts.channels || ['console'];
28
29
  this.logFile = opts.logFile || 'audit.log';
29
30
  this.webhookUrl = opts.webhookUrl || null;
31
+ this.webhookTemplate = opts.webhookTemplate || null;
30
32
  this.rateLimitMs = opts.rateLimitMs ?? 300000;
31
33
  this.quiet = opts.quiet || false;
32
34
  this._recentAlerts = new Map(); // key -> timestamp
@@ -110,20 +112,49 @@ class AlertManager {
110
112
  try {
111
113
  const url = new URL(this.webhookUrl);
112
114
  const transport = url.protocol === 'https:' ? https : http;
113
- const body = JSON.stringify(entry);
115
+
116
+ let payload;
117
+ if (this.webhookTemplate) {
118
+ payload = this._renderTemplate(this.webhookTemplate, entry);
119
+ } else {
120
+ // Default payload format
121
+ payload = JSON.stringify(entry);
122
+ }
123
+
114
124
  const req = transport.request({
115
125
  hostname: url.hostname,
116
126
  port: url.port,
117
127
  path: url.pathname + url.search,
118
128
  method: 'POST',
119
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
129
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
120
130
  });
121
131
  req.on('error', () => {});
122
- req.write(body);
132
+ req.write(payload);
123
133
  req.end();
124
134
  } catch {}
125
135
  }
126
136
 
137
+ /**
138
+ * Render a mustache-style template with variable substitution.
139
+ * @param {string} template - Template string with {{variable}} placeholders
140
+ * @param {Object} entry - Alert entry object
141
+ * @returns {string} Rendered template
142
+ */
143
+ _renderTemplate(template, entry) {
144
+ const variables = {
145
+ timestamp: entry.timestamp,
146
+ severity: entry.severity,
147
+ type: entry.type,
148
+ message: entry.message,
149
+ details: JSON.stringify(entry.details || {}),
150
+ source: 'ClawMoat'
151
+ };
152
+
153
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
154
+ return variables.hasOwnProperty(key) ? variables[key] : match;
155
+ });
156
+ }
157
+
127
158
  /** Get total alerts sent. */
128
159
  get count() {
129
160
  return this._alertCount;
@@ -73,7 +73,14 @@ const TIERS = {
73
73
  };
74
74
 
75
75
  // ─── Forbidden Zones (always blocked except in 'full' mode) ─────────
76
+ // Helper: create cross-platform forbidden zone pattern
77
+ // Matches both Unix (~/.ssh) and Windows (C:\Users\X\.ssh, %USERPROFILE%\.ssh)
78
+ function _crossPlatformPattern(unixPattern) {
79
+ return unixPattern;
80
+ }
81
+
76
82
  const FORBIDDEN_ZONES = [
83
+ // Unix dotfiles (also matches on Windows when accessed via forward slashes)
77
84
  { pattern: /^~?\/?\.ssh\b/i, label: 'SSH keys', severity: 'critical' },
78
85
  { pattern: /^~?\/?\.gnupg\b/i, label: 'GPG keys', severity: 'critical' },
79
86
  { pattern: /^~?\/?\.aws\b/i, label: 'AWS credentials', severity: 'critical' },
@@ -97,6 +104,19 @@ const FORBIDDEN_ZONES = [
97
104
  { pattern: /^~?\/?\.password-store\b/i, label: 'Password store', severity: 'critical' },
98
105
  { pattern: /^~?\/?\.1password\b/i, label: '1Password data', severity: 'critical' },
99
106
  { pattern: /(?:KeePass|\.kdbx)$/i, label: 'KeePass database', severity: 'critical' },
107
+
108
+ // Windows-specific forbidden zones
109
+ { pattern: /[\\\/]AppData[\\\/](?:Local|Roaming)[\\\/](?:Google[\\\/]Chrome|Microsoft[\\\/]Edge|BraveSoftware)[\\\/]User Data\b/i, label: 'Windows browser credentials', severity: 'critical' },
110
+ { pattern: /[\\\/]\.?credential[s]?\b/i, label: 'Credential store', severity: 'critical' },
111
+ { pattern: /[\\\/]AppData[\\\/]Roaming[\\\/](?:npm[\\\/])?\.npmrc$/i, label: 'Windows npm credentials', severity: 'high' },
112
+ { pattern: /[\\\/]\.aws[\\\/]/i, label: 'AWS credentials (Windows)', severity: 'critical' },
113
+ { pattern: /[\\\/]\.ssh[\\\/]/i, label: 'SSH keys (Windows)', severity: 'critical' },
114
+ { pattern: /[\\\/]\.gnupg[\\\/]/i, label: 'GPG keys (Windows)', severity: 'critical' },
115
+ { pattern: /[\\\/]AppData[\\\/]Roaming[\\\/]gcloud\b/i, label: 'Google Cloud (Windows)', severity: 'high' },
116
+ { pattern: /[\\\/]AppData[\\\/]Roaming[\\\/]GitHub CLI\b/i, label: 'GitHub CLI (Windows)', severity: 'high' },
117
+ { pattern: /[\\\/]AppData[\\\/]Local[\\\/]Microsoft[\\\/]Credentials\b/i, label: 'Windows Credential Manager', severity: 'critical' },
118
+ { pattern: /[\\\/]ntuser\.dat$/i, label: 'Windows registry hive', severity: 'critical' },
119
+ { pattern: /\\Windows\\System32\\config\\(?:SAM|SECURITY|SYSTEM)/i, label: 'Windows SAM/Security', severity: 'critical' },
100
120
  ];
101
121
 
102
122
  // ─── Dangerous Commands (blocked in observer/worker, warned in standard) ─
@@ -403,9 +423,13 @@ class HostGuardian {
403
423
  _checkForbidden(rawPath, resolvedPath) {
404
424
  const allForbidden = [...FORBIDDEN_ZONES, ...this.extraForbidden];
405
425
  const normalized = resolvedPath.replace(this.home, '~');
426
+ // Also check with forward slashes for Windows path compat
427
+ const forwardSlashed = resolvedPath.replace(/\\/g, '/');
428
+ const normalizedForward = normalized.replace(/\\/g, '/');
406
429
 
407
430
  for (const zone of allForbidden) {
408
- if (zone.pattern.test(rawPath) || zone.pattern.test(normalized) || zone.pattern.test(resolvedPath)) {
431
+ if (zone.pattern.test(rawPath) || zone.pattern.test(normalized) || zone.pattern.test(resolvedPath) ||
432
+ zone.pattern.test(forwardSlashed) || zone.pattern.test(normalizedForward)) {
409
433
  if (this.mode === 'full') {
410
434
  // Full mode: log but allow
411
435
  return {
@@ -433,7 +457,11 @@ class HostGuardian {
433
457
  if (this._inWorkspace(resolvedPath)) return 'workspace';
434
458
  if (this._inSafeZone(resolvedPath)) return 'safe';
435
459
  if (resolvedPath.startsWith(this.home)) return 'home';
460
+ // Unix system paths
436
461
  if (resolvedPath.startsWith('/etc') || resolvedPath.startsWith('/usr') || resolvedPath.startsWith('/var')) return 'system';
462
+ // Windows system paths
463
+ if (/^[A-Z]:\\Windows\\/i.test(resolvedPath) || /^[A-Z]:\\Program Files/i.test(resolvedPath)) return 'system';
464
+ if (/^[A-Z]:\\ProgramData\\/i.test(resolvedPath)) return 'system';
437
465
  return 'unknown';
438
466
  }
439
467
 
@@ -447,10 +475,21 @@ class HostGuardian {
447
475
 
448
476
  _resolve(p) {
449
477
  if (!p) return '';
450
- const expanded = p.replace(/^~/, this.home);
478
+ // Handle Windows %USERPROFILE% and %HOME% env vars
479
+ const expanded = p
480
+ .replace(/^~/, this.home)
481
+ .replace(/%USERPROFILE%/gi, this.home)
482
+ .replace(/%HOME%/gi, this.home)
483
+ .replace(/%APPDATA%/gi, path.join(this.home, 'AppData', 'Roaming'))
484
+ .replace(/%LOCALAPPDATA%/gi, path.join(this.home, 'AppData', 'Local'));
451
485
  return path.resolve(expanded);
452
486
  }
453
487
 
488
+ // Normalize path separators for cross-platform comparison
489
+ _normalizePath(p) {
490
+ return p.replace(/\\/g, '/');
491
+ }
492
+
454
493
  _sanitizeArgs(args) {
455
494
  // Don't log full file contents or long commands
456
495
  const sanitized = { ...args };
package/src/index.js CHANGED
@@ -30,6 +30,8 @@ const { scanMemoryPoison } = require('./scanners/memory-poison');
30
30
  const { scanExfiltration } = require('./scanners/exfiltration');
31
31
  const { scanExcessiveAgency } = require('./scanners/excessive-agency');
32
32
  const { scanSkill, scanSkillContent } = require('./scanners/supply-chain');
33
+ const { scanDependencyAttacks } = require('./scanners/dependency-attacks');
34
+ const { scoreExploitability } = require('./vuln-ops/exploitability');
33
35
  const { evaluateToolCall } = require('./policies/engine');
34
36
  const { HostGuardian, TIERS } = require('./guardian');
35
37
  const { SecurityLogger } = require('./utils/logger');
@@ -165,6 +167,15 @@ class ClawMoat {
165
167
  }
166
168
  }
167
169
 
170
+ // Dependency attack patterns (prototype pollution, ReDoS, decompression bombs, JWT manipulation)
171
+ if (this.config.detection?.dependency_attacks !== false) {
172
+ const da = scanDependencyAttacks(text);
173
+ if (!da.clean) {
174
+ results.findings.push(...da.findings);
175
+ results.safe = false;
176
+ }
177
+ }
178
+
168
179
  // Determine action
169
180
  if (!results.safe) {
170
181
  const maxSev = this._maxSeverity(results.findings);
@@ -289,6 +300,21 @@ class ClawMoat {
289
300
  };
290
301
  }
291
302
 
303
+ /**
304
+ * Analyze findings with exploitability-oriented scoring.
305
+ * @param {string} text - Text to scan
306
+ * @param {Object} [context] - Reachability/exposure context
307
+ * @returns {{ safe: boolean, findings: ScanFinding[], exploitability: { score: number, priority: string, reasons: string[] } }}
308
+ */
309
+ analyzeFindings(text, context = {}) {
310
+ const result = this.scan(text, context);
311
+ return {
312
+ safe: result.safe,
313
+ findings: result.findings,
314
+ exploitability: scoreExploitability(result.findings, context),
315
+ };
316
+ }
317
+
292
318
  /**
293
319
  * Get security event log
294
320
  */
@@ -348,9 +374,85 @@ module.exports.scanExfiltration = scanExfiltration;
348
374
  module.exports.scanExcessiveAgency = scanExcessiveAgency;
349
375
  module.exports.scanSkill = scanSkill;
350
376
  module.exports.scanSkillContent = scanSkillContent;
377
+ module.exports.scanDependencyAttacks = scanDependencyAttacks;
378
+ module.exports.scoreExploitability = scoreExploitability;
351
379
  module.exports.evaluateToolCall = evaluateToolCall;
352
380
  module.exports.HostGuardian = HostGuardian;
353
381
  module.exports.TIERS = TIERS;
354
382
  module.exports.GatewayMonitor = require('./guardian/gateway-monitor').GatewayMonitor;
355
383
  module.exports.FinanceGuard = require('./finance').FinanceGuard;
356
384
  module.exports.McpFirewall = require('./finance/mcp-firewall').McpFirewall;
385
+ module.exports.LiveMonitor = require('./watch/live-monitor').LiveMonitor;
386
+
387
+ // Interactive Approval Workflow
388
+ const { ApprovalWorkflow, NotificationChannel, ConsoleChannel, WebhookChannel, CallbackChannel } = require('./approval');
389
+ module.exports.ApprovalWorkflow = ApprovalWorkflow;
390
+ module.exports.NotificationChannel = NotificationChannel;
391
+ module.exports.ConsoleChannel = ConsoleChannel;
392
+ module.exports.WebhookChannel = WebhookChannel;
393
+ module.exports.CallbackChannel = CallbackChannel;
394
+
395
+ // Multimodal Input Scanning
396
+ const { scanMultimodalInput, scanImageDataUrl, scanFileMetadata } = require('./multimodal');
397
+ module.exports.scanMultimodalInput = scanMultimodalInput;
398
+ module.exports.scanImageDataUrl = scanImageDataUrl;
399
+ module.exports.scanFileMetadata = scanFileMetadata;
400
+
401
+ // AgentMesh Governance Integration
402
+ const { AgentMeshBridge, OWASP_AGENTIC_MAPPING, DEFAULT_POLICIES, GOVERNANCE_ACTIONS } = require('./integrations/agentmesh');
403
+ module.exports.AgentMeshBridge = AgentMeshBridge;
404
+ module.exports.OWASP_AGENTIC_MAPPING = OWASP_AGENTIC_MAPPING;
405
+ module.exports.DEFAULT_POLICIES = DEFAULT_POLICIES;
406
+ module.exports.GOVERNANCE_ACTIONS = GOVERNANCE_ACTIONS;
407
+
408
+ // MCP Scanner
409
+ const mcpScanner = require('./mcp-scanner');
410
+ module.exports.scanMCP = mcpScanner.scanMCP;
411
+ module.exports.discoverMCPConfigs = mcpScanner.discoverMCPConfigs;
412
+ module.exports.parseMCPConfig = mcpScanner.parseMCPConfig;
413
+ module.exports.scanMCPServer = mcpScanner.scanMCPServer;
414
+
415
+ // Enforcement mode
416
+ const enforce = require('./enforce');
417
+ module.exports.enforceInbound = enforce.enforceInbound;
418
+ module.exports.enforceOutbound = enforce.enforceOutbound;
419
+ module.exports.firewall = enforce.middleware;
420
+ module.exports.wrapAgent = enforce.wrap;
421
+ module.exports.ClawMoatBlocked = enforce.ClawMoatBlocked;
422
+
423
+ // Policy Engine
424
+ const { PolicyEngine, DATA_PATTERNS, TOOL_RISK_DEFAULTS } = require('./policy-engine');
425
+ module.exports.PolicyEngine = PolicyEngine;
426
+ module.exports.DATA_PATTERNS = DATA_PATTERNS;
427
+ module.exports.TOOL_RISK_DEFAULTS = TOOL_RISK_DEFAULTS;
428
+
429
+ // Obfuscation Scanner
430
+ const obfuscation = require('./obfuscation-scanner');
431
+ module.exports.scanObfuscation = obfuscation.scanObfuscation;
432
+ module.exports.stripObfuscation = obfuscation.stripObfuscation;
433
+
434
+ // Code Scanner
435
+ const codeScanner = require('./code-scanner');
436
+ module.exports.scanCode = codeScanner.scanCode;
437
+ module.exports.quickToolCheck = codeScanner.quickToolCheck;
438
+
439
+ // Boundary Scanner (Agent Pipeline)
440
+ const boundary = require('./boundary-scanner');
441
+ module.exports.createPipeline = boundary.createPipeline;
442
+ module.exports.createDefaultPipeline = boundary.createDefaultPipeline;
443
+ module.exports.BOUNDARY_STAGES = boundary.STAGES;
444
+
445
+ // Language Detection
446
+ const lang = require('./language-detector');
447
+ module.exports.scanLanguage = lang.scanLanguage;
448
+ module.exports.detectScripts = lang.detectScripts;
449
+
450
+ // Ban Scanner
451
+ const ban = require('./ban-scanner');
452
+ module.exports.createBanScanner = ban.createBanScanner;
453
+ module.exports.BAN_PRESETS = ban.PRESETS;
454
+
455
+ // Framework Adapters (also available via require('clawmoat/adapters'))
456
+ const adapters = require('./adapters');
457
+ module.exports.createGuard = adapters.createGuard;
458
+ module.exports.ClawMoatCallbackHandler = adapters.ClawMoatCallbackHandler;