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
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Express/Fastify Middleware Adapter
3
+ *
4
+ * One-line security for any Express/Fastify API that serves an AI agent.
5
+ *
6
+ * Usage (Express):
7
+ * const { clawmoatMiddleware } = require('clawmoat/adapters/express');
8
+ * app.use(clawmoatMiddleware({ mode: 'enforce' }));
9
+ *
10
+ * Usage (Fastify):
11
+ * const { clawmoatPlugin } = require('clawmoat/adapters/express');
12
+ * fastify.register(clawmoatPlugin, { mode: 'enforce' });
13
+ *
14
+ * @module adapters/express
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ /**
20
+ * Express middleware factory
21
+ * @param {Object} [opts] - Options
22
+ * @param {string} [opts.mode='enforce'] - enforce | monitor
23
+ * @param {string[]} [opts.scanPaths=['/api/*','/chat/*','/agent/*']] - Paths to scan
24
+ * @param {string[]} [opts.skipPaths=['/health','/status']] - Paths to skip
25
+ * @param {Function} [opts.onBlock] - (req, findings) => void
26
+ * @param {Function} [opts.onFinding] - (req, finding) => void
27
+ * @returns {Function} Express middleware
28
+ */
29
+ function clawmoatMiddleware(opts = {}) {
30
+ const {
31
+ mode = 'enforce',
32
+ scanPaths = null,
33
+ skipPaths = ['/health', '/status', '/ping', '/favicon.ico'],
34
+ onBlock = null,
35
+ onFinding = null,
36
+ } = opts;
37
+
38
+ // Lazy load
39
+ let moat = null;
40
+ let obfuscation = null;
41
+
42
+ function getMoat() {
43
+ if (!moat) {
44
+ const ClawMoat = require('../index');
45
+ moat = new ClawMoat();
46
+ }
47
+ return moat;
48
+ }
49
+
50
+ function getObfuscation() {
51
+ if (!obfuscation) obfuscation = require('../obfuscation-scanner');
52
+ return obfuscation;
53
+ }
54
+
55
+ return function clawmoat(req, res, next) {
56
+ // Skip non-matching paths
57
+ if (skipPaths.some(p => req.path === p || req.path.startsWith(p))) {
58
+ return next();
59
+ }
60
+
61
+ if (scanPaths && !scanPaths.some(p => {
62
+ if (p.endsWith('*')) return req.path.startsWith(p.slice(0, -1));
63
+ return req.path === p;
64
+ })) {
65
+ return next();
66
+ }
67
+
68
+ // Only scan requests with bodies
69
+ if (!req.body || (typeof req.body === 'object' && Object.keys(req.body).length === 0)) {
70
+ return next();
71
+ }
72
+
73
+ const text = typeof req.body === 'string' ? req.body : JSON.stringify(req.body);
74
+ const m = getMoat();
75
+ const o = getObfuscation();
76
+
77
+ // Scan inbound
78
+ const inbound = m.scanInbound(text);
79
+ const obfResult = o.scanObfuscation(text);
80
+
81
+ const findings = [...(inbound.findings || []), ...(obfResult.findings || [])];
82
+
83
+ if (findings.length > 0) {
84
+ // Attach findings to request for downstream use
85
+ req.clawmoat = { findings, blocked: false };
86
+
87
+ for (const f of findings) {
88
+ if (onFinding) onFinding(req, f);
89
+ }
90
+
91
+ if (mode === 'enforce' && (!inbound.safe || !obfResult.safe)) {
92
+ req.clawmoat.blocked = true;
93
+ if (onBlock) onBlock(req, findings);
94
+
95
+ return res.status(422).json({
96
+ error: 'Request blocked by ClawMoat',
97
+ code: 'CLAWMOAT_BLOCKED',
98
+ findings: findings.map(f => ({
99
+ type: f.type,
100
+ subtype: f.subtype,
101
+ severity: f.severity,
102
+ evidence: f.evidence,
103
+ })),
104
+ });
105
+ }
106
+ }
107
+
108
+ // Wrap res.json to scan outbound
109
+ const originalJson = res.json.bind(res);
110
+ res.json = function(data) {
111
+ const outText = typeof data === 'string' ? data : JSON.stringify(data);
112
+ const outbound = m.scanOutbound(outText);
113
+
114
+ if (outbound.findings && outbound.findings.length > 0) {
115
+ if (!req.clawmoat) req.clawmoat = { findings: [], blocked: false };
116
+ req.clawmoat.findings.push(...outbound.findings);
117
+
118
+ if (mode === 'enforce' && !outbound.safe) {
119
+ return originalJson({
120
+ error: 'Response blocked by ClawMoat — potential data leak',
121
+ code: 'CLAWMOAT_OUTPUT_BLOCKED',
122
+ });
123
+ }
124
+ }
125
+
126
+ return originalJson(data);
127
+ };
128
+
129
+ next();
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Fastify plugin
135
+ */
136
+ async function clawmoatPlugin(fastify, opts = {}) {
137
+ const middleware = clawmoatMiddleware(opts);
138
+
139
+ fastify.addHook('preHandler', (request, reply, done) => {
140
+ // Adapt Fastify request/reply to Express-like interface
141
+ const req = {
142
+ path: request.url,
143
+ body: request.body,
144
+ clawmoat: null,
145
+ };
146
+ const res = {
147
+ status: (code) => ({ json: (data) => reply.code(code).send(data) }),
148
+ json: (data) => reply.send(data),
149
+ };
150
+
151
+ middleware(req, res, () => {
152
+ if (req.clawmoat) request.clawmoat = req.clawmoat;
153
+ done();
154
+ });
155
+ });
156
+ }
157
+
158
+ module.exports = {
159
+ clawmoatMiddleware,
160
+ clawmoatPlugin,
161
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * ClawMoat Framework Adapters
3
+ *
4
+ * Drop-in integrations for popular AI/web frameworks.
5
+ *
6
+ * Usage:
7
+ * // LangChain
8
+ * const { ClawMoatCallbackHandler } = require('clawmoat/adapters/langchain');
9
+ *
10
+ * // Express
11
+ * const { clawmoatMiddleware } = require('clawmoat/adapters/express');
12
+ *
13
+ * // Generic (any framework)
14
+ * const { createGuard } = require('clawmoat/adapters');
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const { ClawMoatCallbackHandler, wrapChain } = require('./langchain');
20
+ const { clawmoatMiddleware, clawmoatPlugin } = require('./express');
21
+
22
+ /**
23
+ * Generic guard factory — works with any framework
24
+ * @param {Object} [opts] - Options
25
+ * @returns {Object} Guard with scanInput/scanOutput/scanTool methods
26
+ */
27
+ function createGuard(opts = {}) {
28
+ const { mode = 'enforce' } = opts;
29
+
30
+ // Lazy load all scanners
31
+ let moat, obf, code;
32
+ const getMoat = () => { if (!moat) { const C = require('../index'); moat = new C(); } return moat; };
33
+ const getObf = () => { if (!obf) obf = require('../obfuscation-scanner'); return obf; };
34
+ const getCode = () => { if (!code) code = require('../code-scanner'); return code; };
35
+
36
+ function _check(findings) {
37
+ if (findings.length === 0) return { safe: true, findings: [] };
38
+ if (mode === 'monitor') return { safe: true, findings, warning: true };
39
+ const hasCritical = findings.some(f => f.severity === 'critical' || f.severity === 'high');
40
+ return { safe: !hasCritical, findings };
41
+ }
42
+
43
+ return {
44
+ /**
45
+ * Scan user/external input before processing
46
+ */
47
+ scanInput(text) {
48
+ const inbound = getMoat().scanInbound(text);
49
+ const obfResult = getObf().scanObfuscation(text);
50
+ return _check([...(inbound.findings || []), ...(obfResult.findings || [])]);
51
+ },
52
+
53
+ /**
54
+ * Scan agent output before sending
55
+ */
56
+ scanOutput(text) {
57
+ const outbound = getMoat().scanOutbound(text);
58
+ return _check(outbound.findings || []);
59
+ },
60
+
61
+ /**
62
+ * Scan tool call before execution
63
+ */
64
+ scanTool(toolName, args) {
65
+ const text = typeof args === 'string' ? args : JSON.stringify(args);
66
+ const result = getCode().scanCode(text, { tool: toolName });
67
+ return _check(result.findings || []);
68
+ },
69
+
70
+ /**
71
+ * Scan tool result before returning to model
72
+ */
73
+ scanToolResult(text) {
74
+ const inbound = getMoat().scanInbound(text);
75
+ return _check(inbound.findings || []);
76
+ },
77
+
78
+ /** Get mode */
79
+ get mode() { return mode; },
80
+ };
81
+ }
82
+
83
+ module.exports = {
84
+ // LangChain
85
+ ClawMoatCallbackHandler,
86
+ wrapChain,
87
+ // Express/Fastify
88
+ clawmoatMiddleware,
89
+ clawmoatPlugin,
90
+ // Generic
91
+ createGuard,
92
+ };
@@ -0,0 +1,185 @@
1
+ /**
2
+ * LangChain Adapter for ClawMoat
3
+ *
4
+ * Drop-in middleware for LangChain applications.
5
+ * Wraps chains, agents, and tools with ClawMoat security scanning.
6
+ *
7
+ * Usage:
8
+ * const { ClawMoatCallbackHandler } = require('clawmoat/adapters/langchain');
9
+ * const handler = new ClawMoatCallbackHandler({ mode: 'enforce' });
10
+ * const chain = new LLMChain({ llm, prompt, callbacks: [handler] });
11
+ *
12
+ * @module adapters/langchain
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ /**
18
+ * LangChain callback handler that scans inputs/outputs
19
+ */
20
+ class ClawMoatCallbackHandler {
21
+ constructor(opts = {}) {
22
+ this.name = 'ClawMoatCallbackHandler';
23
+ this.mode = opts.mode || 'enforce';
24
+ this.onBlock = opts.onBlock || null;
25
+ this.onFinding = opts.onFinding || null;
26
+ this.findings = [];
27
+
28
+ // Lazy load to avoid circular deps
29
+ this._moat = null;
30
+ this._obfuscation = null;
31
+ this._codeScanner = null;
32
+ }
33
+
34
+ _getMoat() {
35
+ if (!this._moat) {
36
+ const ClawMoat = require('../index');
37
+ this._moat = new ClawMoat();
38
+ }
39
+ return this._moat;
40
+ }
41
+
42
+ _getObfuscation() {
43
+ if (!this._obfuscation) this._obfuscation = require('../obfuscation-scanner');
44
+ return this._obfuscation;
45
+ }
46
+
47
+ _getCodeScanner() {
48
+ if (!this._codeScanner) this._codeScanner = require('../code-scanner');
49
+ return this._codeScanner;
50
+ }
51
+
52
+ /**
53
+ * Called when chain starts — scan input
54
+ */
55
+ async handleChainStart(chain, inputs) {
56
+ const text = typeof inputs === 'string' ? inputs : JSON.stringify(inputs);
57
+
58
+ // Scan for prompt injection + obfuscation
59
+ const moat = this._getMoat();
60
+ const inbound = moat.scanInbound(text);
61
+ const obfResult = this._getObfuscation().scanObfuscation(text);
62
+
63
+ const allFindings = [...(inbound.findings || []), ...(obfResult.findings || [])];
64
+ this.findings.push(...allFindings);
65
+
66
+ for (const f of allFindings) {
67
+ if (this.onFinding) this.onFinding(f, 'chain_input');
68
+ }
69
+
70
+ if (!inbound.safe || !obfResult.safe) {
71
+ if (this.mode === 'enforce') {
72
+ const err = new Error(`[ClawMoat] Blocked: ${allFindings[0]?.evidence || 'threat detected'}`);
73
+ err.code = 'CLAWMOAT_BLOCKED';
74
+ err.findings = allFindings;
75
+ if (this.onBlock) this.onBlock(allFindings, 'chain_input');
76
+ throw err;
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Called when chain ends — scan output for leaks
83
+ */
84
+ async handleChainEnd(outputs) {
85
+ const text = typeof outputs === 'string' ? outputs : JSON.stringify(outputs);
86
+
87
+ const moat = this._getMoat();
88
+ const outbound = moat.scanOutbound(text);
89
+
90
+ if (outbound.findings) {
91
+ this.findings.push(...outbound.findings);
92
+ for (const f of outbound.findings) {
93
+ if (this.onFinding) this.onFinding(f, 'chain_output');
94
+ }
95
+ }
96
+
97
+ if (!outbound.safe && this.mode === 'enforce') {
98
+ const err = new Error(`[ClawMoat] Output blocked: ${outbound.findings[0]?.evidence || 'leak detected'}`);
99
+ err.code = 'CLAWMOAT_BLOCKED';
100
+ err.findings = outbound.findings;
101
+ if (this.onBlock) this.onBlock(outbound.findings, 'chain_output');
102
+ throw err;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Called when tool starts — scan tool call
108
+ */
109
+ async handleToolStart(tool, input) {
110
+ const cs = this._getCodeScanner();
111
+ const text = typeof input === 'string' ? input : JSON.stringify(input);
112
+ const result = cs.scanCode(text, { tool: tool?.name });
113
+
114
+ if (result.findings.length > 0) {
115
+ this.findings.push(...result.findings);
116
+ for (const f of result.findings) {
117
+ if (this.onFinding) this.onFinding(f, 'tool_call');
118
+ }
119
+ }
120
+
121
+ if (!result.safe && this.mode === 'enforce') {
122
+ const err = new Error(`[ClawMoat] Tool call blocked: ${result.findings[0]?.evidence || 'dangerous code'}`);
123
+ err.code = 'CLAWMOAT_BLOCKED';
124
+ err.findings = result.findings;
125
+ if (this.onBlock) this.onBlock(result.findings, 'tool_call');
126
+ throw err;
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Called when tool ends — scan result for injection
132
+ */
133
+ async handleToolEnd(output) {
134
+ const moat = this._getMoat();
135
+ const text = typeof output === 'string' ? output : JSON.stringify(output);
136
+ const result = moat.scanInbound(text);
137
+
138
+ if (result.findings) {
139
+ this.findings.push(...result.findings);
140
+ }
141
+
142
+ // Don't block tool results in most cases — just log
143
+ // The model needs to see the result to understand what happened
144
+ }
145
+
146
+ /**
147
+ * Get all findings collected during execution
148
+ */
149
+ getFindings() {
150
+ return [...this.findings];
151
+ }
152
+
153
+ /**
154
+ * Clear collected findings
155
+ */
156
+ clearFindings() {
157
+ this.findings = [];
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Wrap a LangChain chain/agent with ClawMoat protection
163
+ * Convenience function for simple usage
164
+ * @param {Object} chain - LangChain chain or agent
165
+ * @param {Object} [opts] - ClawMoat options
166
+ * @returns {Object} Wrapped chain with security scanning
167
+ */
168
+ function wrapChain(chain, opts = {}) {
169
+ const handler = new ClawMoatCallbackHandler(opts);
170
+
171
+ // Add our handler to the chain's callbacks
172
+ if (!chain.callbacks) chain.callbacks = [];
173
+ chain.callbacks.push(handler);
174
+
175
+ // Attach findings accessor
176
+ chain._clawmoat = handler;
177
+ chain.getSecurityFindings = () => handler.getFindings();
178
+
179
+ return chain;
180
+ }
181
+
182
+ module.exports = {
183
+ ClawMoatCallbackHandler,
184
+ wrapChain,
185
+ };