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.
Files changed (178) hide show
  1. package/.dockerignore +9 -0
  2. package/CHANGELOG.md +18 -0
  3. package/CONTRIBUTING.md +4 -2
  4. package/DEMO.md +87 -0
  5. package/Dockerfile +5 -18
  6. package/README.md +294 -8
  7. package/SECURITY.md +58 -10
  8. package/THREAT_MODEL.md +129 -0
  9. package/agent/README.md +131 -0
  10. package/agent/index.js +471 -0
  11. package/agent/install-service.sh +94 -0
  12. package/agent/openclaw-hook.js +453 -0
  13. package/agent/provider-setup.js +649 -0
  14. package/agent/setup.js +274 -0
  15. package/assets/BADGE-USAGE.md +20 -0
  16. package/assets/clawmoat-badge.svg +21 -0
  17. package/bin/clawmoat.js +468 -111
  18. package/docs/affiliates/dashboard.html +124 -0
  19. package/docs/affiliates/index.html +236 -0
  20. package/docs/agent-install.html +183 -0
  21. package/docs/ai-agent-security-scanner.html +10 -6
  22. package/docs/badge/index.html +149 -0
  23. package/docs/badge/scanning.svg +23 -0
  24. package/docs/blog/386-malicious-skills.html +262 -0
  25. package/docs/blog/40000-exposed-openclaw-instances.html +201 -0
  26. package/docs/blog/agent-trust-protocol.html +198 -0
  27. package/docs/blog/ai-agent-earns-commissions.html +230 -0
  28. package/docs/blog/bugmageddon-agent-firewall.html +174 -0
  29. package/docs/blog/calculator-math.html +180 -0
  30. package/docs/blog/clawmoat-vs-llamafirewall-nemo-guardrails.html +229 -0
  31. package/docs/blog/host-guardian-launch.html +18 -8
  32. package/docs/blog/ibm-experts-agent-runtime-protection.html +247 -0
  33. package/docs/blog/index.html +211 -9
  34. package/docs/blog/langchain-security-tutorial.html +18 -8
  35. package/docs/blog/mcp-30-cves-security-crisis.html +286 -0
  36. package/docs/blog/meta-researcher-rogue-agent.html +201 -0
  37. package/docs/blog/microsoft-openclaw-workstation-security.html +235 -0
  38. package/docs/blog/nist-ai-agent-standards-clawmoat.html +377 -0
  39. package/docs/blog/oasis-websocket-hijack.html +212 -0
  40. package/docs/blog/ollama-openclaw-security.html +160 -0
  41. package/docs/blog/openclaw-enterprise-readiness-claw10.html +199 -0
  42. package/docs/blog/openclaw-security-reckoning-2026.html +368 -0
  43. package/docs/blog/owasp-agentic-ai-top10.html +18 -8
  44. package/docs/blog/securing-ai-agents.html +18 -8
  45. package/docs/blog/supply-chain-agents.html +18 -8
  46. package/docs/business/index.html +525 -0
  47. package/docs/business/install.html +261 -0
  48. package/docs/checklist.html +174 -0
  49. package/docs/compare/index.html +122 -0
  50. package/docs/compare/lakera/index.html +62 -0
  51. package/docs/compare/llm-guard/index.html +49 -0
  52. package/docs/compare/snyk-agent-scan/index.html +63 -0
  53. package/docs/compare.html +10 -6
  54. package/docs/dashboard/index.html +520 -0
  55. package/docs/finance/index.html +220 -0
  56. package/docs/guides/business-deployment.html +770 -0
  57. package/docs/hall-of-fame.html +174 -0
  58. package/docs/index.html +447 -154
  59. package/docs/install.sh +557 -0
  60. package/docs/integrations/langchain.html +14 -6
  61. package/docs/integrations/openai.html +14 -6
  62. package/docs/integrations/openclaw.html +55 -7
  63. package/docs/plans/2026-03-26-threat-intel-api.md +255 -0
  64. package/docs/plans/2026-04-14-bugmageddon-marketing-pack.md +329 -0
  65. package/docs/plans/2026-04-14-clawmoat-v1-bugmageddon.md +248 -0
  66. package/docs/plans/2026-04-14-v1-release-update.md +91 -0
  67. package/docs/plans/2026-04-19-supabase-audit.md +68 -0
  68. package/docs/plans/2026-05-12-sales-push.md +303 -0
  69. package/docs/playground/index.html +893 -0
  70. package/docs/playground.html +4 -7
  71. package/docs/privacy-policy/index.html +122 -0
  72. package/docs/rfcs/defense-in-depth.md +467 -0
  73. package/docs/scan/index.html +358 -0
  74. package/docs/services/case-study.html +255 -0
  75. package/docs/services/downloads/install-openclaw.bat +45 -0
  76. package/docs/services/downloads/install-openclaw.command +38 -0
  77. package/docs/services/downloads/install-openclaw.sh +38 -0
  78. package/docs/services/get-started.html +165 -0
  79. package/docs/services/index.html +598 -0
  80. package/docs/services/multi-agent-security.html +284 -0
  81. package/docs/services/one-pager.html +99 -0
  82. package/docs/services/pitch-deck.html +229 -0
  83. package/docs/services/roi-calculator.html +258 -0
  84. package/docs/sitemap.xml +192 -2
  85. package/docs/support/index.html +135 -0
  86. package/docs/templates/customer-service/HEARTBEAT.md +61 -0
  87. package/docs/templates/customer-service/MEMORY.md +89 -0
  88. package/docs/templates/customer-service/SOUL.md +41 -0
  89. package/docs/templates/customer-service/USER.md +56 -0
  90. package/docs/templates/executive/HEARTBEAT.md +86 -0
  91. package/docs/templates/executive/MEMORY.md +92 -0
  92. package/docs/templates/executive/SOUL.md +44 -0
  93. package/docs/templates/executive/USER.md +62 -0
  94. package/docs/templates/finance/HEARTBEAT.md +58 -0
  95. package/docs/templates/finance/MEMORY.md +87 -0
  96. package/docs/templates/finance/SOUL.md +38 -0
  97. package/docs/templates/finance/USER.md +53 -0
  98. package/docs/templates/index.html +115 -0
  99. package/docs/templates/operations/HEARTBEAT.md +63 -0
  100. package/docs/templates/operations/MEMORY.md +68 -0
  101. package/docs/templates/operations/SOUL.md +38 -0
  102. package/docs/templates/operations/USER.md +49 -0
  103. package/docs/templates/sales/HEARTBEAT.md +55 -0
  104. package/docs/templates/sales/MEMORY.md +89 -0
  105. package/docs/templates/sales/SOUL.md +34 -0
  106. package/docs/templates/sales/USER.md +54 -0
  107. package/docs/terms-of-service/index.html +122 -0
  108. package/eslint.config.js +32 -0
  109. package/evals/README.md +29 -0
  110. package/evals/cases.json +390 -0
  111. package/evals/results.md +68 -0
  112. package/evals/run.js +180 -0
  113. package/examples/basic-usage.js +38 -0
  114. package/examples/demo-attack/demo.js +186 -0
  115. package/examples/python-quickstart/README.md +54 -0
  116. package/examples/python-quickstart/clawmoat_client.py +167 -0
  117. package/examples/video-demo/README.md +14 -0
  118. package/examples/video-demo/scene-a-normal.js +29 -0
  119. package/examples/video-demo/scene-b-attack-arrives.js +31 -0
  120. package/examples/video-demo/scene-c-hijack.js +44 -0
  121. package/examples/video-demo/scene-d-clawmoat.js +46 -0
  122. package/integrations/crewai/README.md +32 -0
  123. package/integrations/crewai/clawmoat_crewai/__init__.py +17 -0
  124. package/integrations/crewai/clawmoat_crewai/guard.py +103 -0
  125. package/integrations/crewai/pyproject.toml +21 -0
  126. package/integrations/langchain/README.md +91 -0
  127. package/integrations/langchain/clawmoat_langchain/__init__.py +17 -0
  128. package/integrations/langchain/clawmoat_langchain/callback.py +489 -0
  129. package/integrations/langchain/pyproject.toml +32 -0
  130. package/integrations/litellm/README.md +324 -0
  131. package/integrations/litellm/clawmoat_litellm/__init__.py +21 -0
  132. package/integrations/litellm/clawmoat_litellm/callback.py +329 -0
  133. package/integrations/litellm/clawmoat_litellm/proxy_middleware.py +224 -0
  134. package/integrations/litellm/pyproject.toml +74 -0
  135. package/integrations/openai-agents/README.md +392 -0
  136. package/integrations/openai-agents/clawmoat_openai_agents/__init__.py +20 -0
  137. package/integrations/openai-agents/clawmoat_openai_agents/guardrail.py +431 -0
  138. package/integrations/openai-agents/clawmoat_openai_agents/middleware.py +311 -0
  139. package/integrations/openai-agents/pyproject.toml +76 -0
  140. package/package.json +6 -5
  141. package/plugins/openclaw-adapter/PHASE1.md +439 -0
  142. package/plugins/openclaw-adapter/README.md +103 -0
  143. package/plugins/openclaw-adapter/SPEC.md +1644 -0
  144. package/plugins/openclaw-adapter/package.json +31 -0
  145. package/plugins/openclaw-adapter/src/index.test.ts +226 -0
  146. package/plugins/openclaw-adapter/src/index.ts +140 -0
  147. package/plugins/openclaw-adapter/tsconfig.json +14 -0
  148. package/server/data/threats.json +290 -0
  149. package/server/index.js +224 -10
  150. package/src/adapters/express.js +161 -0
  151. package/src/adapters/index.js +92 -0
  152. package/src/adapters/langchain.js +185 -0
  153. package/src/approval/index.js +456 -0
  154. package/src/ban-scanner.js +200 -0
  155. package/src/boundary-scanner.js +296 -0
  156. package/src/ci-scanner.js +279 -0
  157. package/src/code-scanner.js +245 -0
  158. package/src/enforce.js +166 -0
  159. package/src/finance/index.js +585 -0
  160. package/src/finance/mcp-firewall.js +486 -0
  161. package/src/formatters/json.js +80 -0
  162. package/src/formatters/sarif.js +388 -0
  163. package/src/guardian/alerts.js +34 -3
  164. package/src/guardian/gateway-monitor.js +590 -0
  165. package/src/guardian/index.js +41 -2
  166. package/src/index.js +105 -0
  167. package/src/integrations/agentmesh.js +501 -0
  168. package/src/language-detector.js +201 -0
  169. package/src/mcp-scanner.js +253 -0
  170. package/src/multimodal/index.js +579 -0
  171. package/src/obfuscation-scanner.js +457 -0
  172. package/src/policy-engine.js +402 -0
  173. package/src/scanners/dependency-attacks.js +128 -0
  174. package/src/scanners/prompt-injection.js +18 -0
  175. package/src/scanners/supply-chain.js +14 -0
  176. package/src/templates/default-config.yml +90 -0
  177. package/src/vuln-ops/exploitability.js +46 -0
  178. package/src/watch/live-monitor.js +720 -0
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,6 +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;
382
+ module.exports.GatewayMonitor = require('./guardian/gateway-monitor').GatewayMonitor;
383
+ module.exports.FinanceGuard = require('./finance').FinanceGuard;
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;
@@ -0,0 +1,501 @@
1
+ /**
2
+ * AgentMesh Governance Integration
3
+ * Bridges ClawMoat security scanning with governance policy engines
4
+ * Maps threat detections to governance actions based on OWASP Agentic Top 10
5
+ *
6
+ * @module integrations/agentmesh
7
+ * @example
8
+ * const { AgentMeshBridge } = require('./integrations/agentmesh');
9
+ *
10
+ * const bridge = new AgentMeshBridge({
11
+ * policies: {
12
+ * prompt_injection: { action: 'block', severity: 'high' },
13
+ * secret_detected: { action: 'alert', notify: true }
14
+ * }
15
+ * });
16
+ *
17
+ * const decision = await bridge.enforcePolicy({
18
+ * action: 'send_message',
19
+ * agent: 'chatbot',
20
+ * context: { findings: clawMoatScanResults }
21
+ * });
22
+ */
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ /**
28
+ * OWASP Agentic Top 10 threat categories mapped to ClawMoat findings
29
+ */
30
+ const OWASP_AGENTIC_MAPPING = {
31
+ // LLM01: Prompt Injection
32
+ 'prompt_injection': 'LLM01',
33
+ 'jailbreak': 'LLM01',
34
+ 'embedded_injection': 'LLM01',
35
+
36
+ // LLM02: Insecure Output Handling
37
+ 'secret_detected': 'LLM02',
38
+ 'pii_detected': 'LLM02',
39
+ 'credential_leak': 'LLM02',
40
+
41
+ // LLM03: Training Data Poisoning
42
+ 'memory_poison': 'LLM03',
43
+ 'context_poison': 'LLM03',
44
+
45
+ // LLM04: Model Denial of Service
46
+ 'size_anomaly': 'LLM04',
47
+ 'excessive_tokens': 'LLM04',
48
+
49
+ // LLM06: Sensitive Information Disclosure
50
+ 'data_exfiltration': 'LLM06',
51
+ 'unauthorized_access': 'LLM06',
52
+
53
+ // LLM07: Insecure Plugin Design
54
+ 'supply_chain_threat': 'LLM07',
55
+ 'malicious_plugin': 'LLM07',
56
+
57
+ // LLM08: Excessive Agency
58
+ 'excessive_agency': 'LLM08',
59
+ 'privilege_escalation': 'LLM08',
60
+
61
+ // LLM09: Overreliance
62
+ 'confidence_manipulation': 'LLM09',
63
+
64
+ // LLM10: Model Theft
65
+ 'model_extraction': 'LLM10',
66
+ 'api_abuse': 'LLM10'
67
+ };
68
+
69
+ /**
70
+ * Default governance policies for different threat types
71
+ */
72
+ const DEFAULT_POLICIES = {
73
+ // High-risk threats - immediate action
74
+ 'prompt_injection': {
75
+ action: 'block',
76
+ severity: 'high',
77
+ notify: true,
78
+ log: true
79
+ },
80
+ 'jailbreak': {
81
+ action: 'block',
82
+ severity: 'high',
83
+ notify: true,
84
+ log: true
85
+ },
86
+ 'secret_detected': {
87
+ action: 'block',
88
+ severity: 'critical',
89
+ notify: true,
90
+ log: true,
91
+ redact: true
92
+ },
93
+
94
+ // Medium-risk threats - alert and log
95
+ 'memory_poison': {
96
+ action: 'alert',
97
+ severity: 'medium',
98
+ notify: true,
99
+ log: true
100
+ },
101
+ 'excessive_agency': {
102
+ action: 'alert',
103
+ severity: 'medium',
104
+ notify: true,
105
+ log: true
106
+ },
107
+ 'supply_chain_threat': {
108
+ action: 'alert',
109
+ severity: 'high',
110
+ notify: true,
111
+ log: true,
112
+ quarantine: true
113
+ },
114
+
115
+ // Low-risk threats - log only
116
+ 'steganographic_pattern': {
117
+ action: 'log',
118
+ severity: 'low',
119
+ notify: false,
120
+ log: true
121
+ },
122
+ 'suspicious_file_extension': {
123
+ action: 'log',
124
+ severity: 'medium',
125
+ notify: false,
126
+ log: true
127
+ }
128
+ };
129
+
130
+ /**
131
+ * Governance actions available for policy enforcement
132
+ */
133
+ const GOVERNANCE_ACTIONS = {
134
+ ALLOW: 'allow',
135
+ BLOCK: 'block',
136
+ ALERT: 'alert',
137
+ LOG: 'log',
138
+ QUARANTINE: 'quarantine',
139
+ REDACT: 'redact'
140
+ };
141
+
142
+ /**
143
+ * @typedef {Object} PolicyRule
144
+ * @property {string} action - Governance action to take (allow/block/alert/log)
145
+ * @property {string} severity - Severity level (low/medium/high/critical)
146
+ * @property {boolean} notify - Whether to send notifications
147
+ * @property {boolean} log - Whether to log the event
148
+ * @property {boolean} [redact] - Whether to redact sensitive content
149
+ * @property {boolean} [quarantine] - Whether to quarantine the content/agent
150
+ * @property {string[]} [exceptions] - List of exceptions that override this rule
151
+ */
152
+
153
+ /**
154
+ * @typedef {Object} EnforcementContext
155
+ * @property {string} action - The action being attempted (e.g., 'send_message', 'execute_tool')
156
+ * @property {string} agent - Agent ID or name
157
+ * @property {Object} [findings] - ClawMoat scan findings
158
+ * @property {string} [content] - Content being processed
159
+ * @property {Object} [metadata] - Additional context metadata
160
+ */
161
+
162
+ /**
163
+ * @typedef {Object} PolicyDecision
164
+ * @property {string} decision - Final governance decision (allow/block/alert/log)
165
+ * @property {string} reason - Human-readable explanation
166
+ * @property {string[]} triggeredRules - List of policy rules that matched
167
+ * @property {string} owaspCategory - OWASP Agentic Top 10 category
168
+ * @property {Object} actions - Specific actions to take
169
+ * @property {number} timestamp - Unix timestamp of decision
170
+ */
171
+
172
+ /**
173
+ * Agent governance policy engine that bridges ClawMoat findings with policy decisions
174
+ */
175
+ class AgentMeshBridge {
176
+ /**
177
+ * @param {Object} options - Configuration options
178
+ * @param {Object} [options.policies] - Custom policy rules
179
+ * @param {string} [options.policyFile] - Path to YAML/JSON policy file
180
+ * @param {string} [options.logFile] - Path to governance log file
181
+ * @param {boolean} [options.strict] - Strict mode (deny by default)
182
+ * @param {Function} [options.onDecision] - Callback for policy decisions
183
+ */
184
+ constructor(options = {}) {
185
+ this.policies = { ...DEFAULT_POLICIES };
186
+ this.strict = options.strict || false;
187
+ this.logFile = options.logFile || null;
188
+ this.onDecision = options.onDecision || null;
189
+
190
+ // Load custom policies
191
+ if (options.policies) {
192
+ this.policies = { ...this.policies, ...options.policies };
193
+ }
194
+
195
+ // Load policies from file
196
+ if (options.policyFile) {
197
+ this.loadPoliciesFromFile(options.policyFile);
198
+ }
199
+
200
+ this.stats = {
201
+ decisions: 0,
202
+ blocked: 0,
203
+ alerted: 0,
204
+ allowed: 0
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Load governance policies from a file
210
+ * @param {string} filePath - Path to policy file (JSON or YAML)
211
+ */
212
+ loadPoliciesFromFile(filePath) {
213
+ try {
214
+ if (!fs.existsSync(filePath)) {
215
+ throw new Error(`Policy file not found: ${filePath}`);
216
+ }
217
+
218
+ const content = fs.readFileSync(filePath, 'utf8');
219
+ const ext = path.extname(filePath).toLowerCase();
220
+
221
+ let policies;
222
+ if (ext === '.json') {
223
+ policies = JSON.parse(content);
224
+ } else if (ext === '.yml' || ext === '.yaml') {
225
+ // Simple YAML parser for basic structures
226
+ policies = this.parseSimpleYaml(content);
227
+ } else {
228
+ throw new Error(`Unsupported policy file format: ${ext}`);
229
+ }
230
+
231
+ this.policies = { ...this.policies, ...policies };
232
+ } catch (error) {
233
+ throw new Error(`Failed to load policies from ${filePath}: ${error.message}`);
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Simple YAML parser for policy files (no external dependencies)
239
+ * @param {string} yaml - YAML content
240
+ * @returns {Object} Parsed policies
241
+ */
242
+ parseSimpleYaml(yaml) {
243
+ const policies = {};
244
+ const lines = yaml.split('\n');
245
+ let currentKey = null;
246
+ let currentPolicy = {};
247
+
248
+ for (const line of lines) {
249
+ const trimmed = line.trim();
250
+ if (!trimmed || trimmed.startsWith('#')) continue;
251
+
252
+ if (trimmed.endsWith(':') && !trimmed.startsWith(' ')) {
253
+ // New policy rule
254
+ if (currentKey && Object.keys(currentPolicy).length > 0) {
255
+ policies[currentKey] = currentPolicy;
256
+ }
257
+ currentKey = trimmed.slice(0, -1);
258
+ currentPolicy = {};
259
+ } else if (trimmed.includes(':') && currentKey) {
260
+ // Policy property
261
+ const [key, ...valueParts] = trimmed.split(':');
262
+ let value = valueParts.join(':').trim();
263
+
264
+ // Parse boolean and number values
265
+ if (value === 'true') value = true;
266
+ else if (value === 'false') value = false;
267
+ else if (!isNaN(value)) value = Number(value);
268
+
269
+ currentPolicy[key.trim()] = value;
270
+ }
271
+ }
272
+
273
+ // Add last policy
274
+ if (currentKey && Object.keys(currentPolicy).length > 0) {
275
+ policies[currentKey] = currentPolicy;
276
+ }
277
+
278
+ return policies;
279
+ }
280
+
281
+ /**
282
+ * Enforce governance policy for an agent action
283
+ * @param {EnforcementContext} context - Action context
284
+ * @returns {Promise<PolicyDecision>} Policy decision
285
+ */
286
+ async enforcePolicy(context) {
287
+ this.stats.decisions++;
288
+
289
+ const decision = {
290
+ decision: 'allow',
291
+ reason: 'No policy violations detected',
292
+ triggeredRules: [],
293
+ owaspCategory: null,
294
+ actions: {
295
+ block: false,
296
+ alert: false,
297
+ log: false,
298
+ notify: false,
299
+ redact: false,
300
+ quarantine: false
301
+ },
302
+ timestamp: Date.now()
303
+ };
304
+
305
+ // Extract findings from context
306
+ const findings = context.findings || [];
307
+ if (findings.length === 0) {
308
+ decision.reason = 'No threats detected';
309
+ this.stats.allowed++;
310
+ await this.logDecision(context, decision);
311
+ return decision;
312
+ }
313
+
314
+ // Evaluate each finding against policies
315
+ let maxSeverityRank = 0;
316
+ const severityRank = { low: 1, medium: 2, high: 3, critical: 4 };
317
+
318
+ for (const finding of findings) {
319
+ const findingType = finding.type || finding.category;
320
+ const policy = this.policies[findingType];
321
+
322
+ if (policy) {
323
+ decision.triggeredRules.push(findingType);
324
+
325
+ // Map to OWASP category
326
+ const owaspCategory = OWASP_AGENTIC_MAPPING[findingType];
327
+ if (owaspCategory && !decision.owaspCategory) {
328
+ decision.owaspCategory = owaspCategory;
329
+ }
330
+
331
+ // Update actions based on policy
332
+ if (policy.action === 'block') {
333
+ decision.decision = 'block';
334
+ decision.actions.block = true;
335
+ } else if (policy.action === 'alert' && decision.decision !== 'block') {
336
+ decision.decision = 'alert';
337
+ decision.actions.alert = true;
338
+ } else if (policy.action === 'log') {
339
+ decision.actions.log = true;
340
+ }
341
+
342
+ // Set notification and other flags
343
+ if (policy.notify) decision.actions.notify = true;
344
+ if (policy.redact) decision.actions.redact = true;
345
+ if (policy.quarantine) decision.actions.quarantine = true;
346
+ if (policy.log) decision.actions.log = true;
347
+
348
+ // Track maximum severity
349
+ const rank = severityRank[policy.severity] || 0;
350
+ if (rank > maxSeverityRank) {
351
+ maxSeverityRank = rank;
352
+ }
353
+ } else if (this.strict) {
354
+ // In strict mode, unknown threat types trigger alerts
355
+ decision.decision = 'alert';
356
+ decision.actions.alert = true;
357
+ decision.actions.log = true;
358
+ decision.triggeredRules.push(`unknown_threat:${findingType}`);
359
+ }
360
+ }
361
+
362
+ // Generate human-readable reason
363
+ if (decision.triggeredRules.length > 0) {
364
+ const severityMap = { 1: 'low', 2: 'medium', 3: 'high', 4: 'critical' };
365
+ const maxSeverity = severityMap[maxSeverityRank] || 'unknown';
366
+
367
+ decision.reason = `${decision.triggeredRules.length} policy violation(s) detected ` +
368
+ `(${decision.triggeredRules.join(', ')}) with ${maxSeverity} severity`;
369
+
370
+ if (decision.owaspCategory) {
371
+ decision.reason += ` - maps to OWASP ${decision.owaspCategory}`;
372
+ }
373
+ }
374
+
375
+ // Update stats
376
+ if (decision.decision === 'block') this.stats.blocked++;
377
+ else if (decision.decision === 'alert') this.stats.alerted++;
378
+ else this.stats.allowed++;
379
+
380
+ // Log decision
381
+ await this.logDecision(context, decision);
382
+
383
+ // Execute callback if provided
384
+ if (this.onDecision) {
385
+ try {
386
+ await this.onDecision(context, decision);
387
+ } catch (error) {
388
+ console.error('Policy decision callback failed:', error.message);
389
+ }
390
+ }
391
+
392
+ return decision;
393
+ }
394
+
395
+ /**
396
+ * Get governance statistics
397
+ * @returns {Object} Stats summary
398
+ */
399
+ getStats() {
400
+ return { ...this.stats };
401
+ }
402
+
403
+ /**
404
+ * Add or update a policy rule
405
+ * @param {string} threatType - Threat type to add policy for
406
+ * @param {PolicyRule} policy - Policy rule configuration
407
+ */
408
+ setPolicy(threatType, policy) {
409
+ this.policies[threatType] = policy;
410
+ }
411
+
412
+ /**
413
+ * Remove a policy rule
414
+ * @param {string} threatType - Threat type to remove policy for
415
+ */
416
+ removePolicy(threatType) {
417
+ delete this.policies[threatType];
418
+ }
419
+
420
+ /**
421
+ * Get all current policies
422
+ * @returns {Object} Current policy rules
423
+ */
424
+ getPolicies() {
425
+ return { ...this.policies };
426
+ }
427
+
428
+ /**
429
+ * Map ClawMoat finding to OWASP Agentic Top 10 category
430
+ * @param {string} findingType - ClawMoat finding type
431
+ * @returns {string|null} OWASP category or null if not mapped
432
+ */
433
+ getOwaspCategory(findingType) {
434
+ return OWASP_AGENTIC_MAPPING[findingType] || null;
435
+ }
436
+
437
+ /**
438
+ * Log governance decision
439
+ * @private
440
+ */
441
+ async logDecision(context, decision) {
442
+ if (!this.logFile) return;
443
+
444
+ const logEntry = {
445
+ timestamp: decision.timestamp,
446
+ agent: context.agent,
447
+ action: context.action,
448
+ decision: decision.decision,
449
+ reason: decision.reason,
450
+ owaspCategory: decision.owaspCategory,
451
+ triggeredRules: decision.triggeredRules,
452
+ findings: context.findings ? context.findings.length : 0
453
+ };
454
+
455
+ try {
456
+ const logLine = JSON.stringify(logEntry) + '\n';
457
+ fs.appendFileSync(this.logFile, logLine);
458
+ } catch (error) {
459
+ console.error('Failed to write governance log:', error.message);
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Read governance decision log
465
+ * @param {Object} [filter] - Optional filter criteria
466
+ * @returns {Object[]} Array of log entries
467
+ */
468
+ getDecisionLog(filter = {}) {
469
+ if (!this.logFile || !fs.existsSync(this.logFile)) {
470
+ return [];
471
+ }
472
+
473
+ try {
474
+ const content = fs.readFileSync(this.logFile, 'utf8');
475
+ const entries = content
476
+ .split('\n')
477
+ .filter(line => line.trim())
478
+ .map(line => JSON.parse(line));
479
+
480
+ // Apply filters
481
+ return entries.filter(entry => {
482
+ if (filter.agent && entry.agent !== filter.agent) return false;
483
+ if (filter.decision && entry.decision !== filter.decision) return false;
484
+ if (filter.owaspCategory && entry.owaspCategory !== filter.owaspCategory) return false;
485
+ if (filter.since && entry.timestamp < filter.since) return false;
486
+ if (filter.until && entry.timestamp > filter.until) return false;
487
+ return true;
488
+ });
489
+ } catch (error) {
490
+ console.error('Failed to read governance log:', error.message);
491
+ return [];
492
+ }
493
+ }
494
+ }
495
+
496
+ module.exports = {
497
+ AgentMeshBridge,
498
+ OWASP_AGENTIC_MAPPING,
499
+ DEFAULT_POLICIES,
500
+ GOVERNANCE_ACTIONS
501
+ };