clawmoat 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +9 -0
- package/CHANGELOG.md +18 -0
- package/CONTRIBUTING.md +4 -2
- package/DEMO.md +87 -0
- package/Dockerfile +5 -18
- package/README.md +294 -8
- package/SECURITY.md +58 -10
- package/THREAT_MODEL.md +129 -0
- package/agent/README.md +131 -0
- package/agent/index.js +471 -0
- package/agent/install-service.sh +94 -0
- package/agent/openclaw-hook.js +453 -0
- package/agent/provider-setup.js +649 -0
- package/agent/setup.js +274 -0
- package/assets/BADGE-USAGE.md +20 -0
- package/assets/clawmoat-badge.svg +21 -0
- package/bin/clawmoat.js +468 -111
- package/docs/affiliates/dashboard.html +124 -0
- package/docs/affiliates/index.html +236 -0
- package/docs/agent-install.html +183 -0
- package/docs/ai-agent-security-scanner.html +10 -6
- package/docs/badge/index.html +149 -0
- package/docs/badge/scanning.svg +23 -0
- package/docs/blog/386-malicious-skills.html +262 -0
- package/docs/blog/40000-exposed-openclaw-instances.html +201 -0
- package/docs/blog/agent-trust-protocol.html +198 -0
- package/docs/blog/ai-agent-earns-commissions.html +230 -0
- package/docs/blog/bugmageddon-agent-firewall.html +174 -0
- package/docs/blog/calculator-math.html +180 -0
- package/docs/blog/clawmoat-vs-llamafirewall-nemo-guardrails.html +229 -0
- package/docs/blog/host-guardian-launch.html +18 -8
- package/docs/blog/ibm-experts-agent-runtime-protection.html +247 -0
- package/docs/blog/index.html +211 -9
- package/docs/blog/langchain-security-tutorial.html +18 -8
- package/docs/blog/mcp-30-cves-security-crisis.html +286 -0
- package/docs/blog/meta-researcher-rogue-agent.html +201 -0
- package/docs/blog/microsoft-openclaw-workstation-security.html +235 -0
- package/docs/blog/nist-ai-agent-standards-clawmoat.html +377 -0
- package/docs/blog/oasis-websocket-hijack.html +212 -0
- package/docs/blog/ollama-openclaw-security.html +160 -0
- package/docs/blog/openclaw-enterprise-readiness-claw10.html +199 -0
- package/docs/blog/openclaw-security-reckoning-2026.html +368 -0
- package/docs/blog/owasp-agentic-ai-top10.html +18 -8
- package/docs/blog/securing-ai-agents.html +18 -8
- package/docs/blog/supply-chain-agents.html +18 -8
- package/docs/business/index.html +525 -0
- package/docs/business/install.html +261 -0
- package/docs/checklist.html +174 -0
- package/docs/compare/index.html +122 -0
- package/docs/compare/lakera/index.html +62 -0
- package/docs/compare/llm-guard/index.html +49 -0
- package/docs/compare/snyk-agent-scan/index.html +63 -0
- package/docs/compare.html +10 -6
- package/docs/dashboard/index.html +520 -0
- package/docs/finance/index.html +220 -0
- package/docs/guides/business-deployment.html +770 -0
- package/docs/hall-of-fame.html +174 -0
- package/docs/index.html +447 -154
- package/docs/install.sh +557 -0
- package/docs/integrations/langchain.html +14 -6
- package/docs/integrations/openai.html +14 -6
- package/docs/integrations/openclaw.html +55 -7
- package/docs/plans/2026-03-26-threat-intel-api.md +255 -0
- package/docs/plans/2026-04-14-bugmageddon-marketing-pack.md +329 -0
- package/docs/plans/2026-04-14-clawmoat-v1-bugmageddon.md +248 -0
- package/docs/plans/2026-04-14-v1-release-update.md +91 -0
- package/docs/plans/2026-04-19-supabase-audit.md +68 -0
- package/docs/plans/2026-05-12-sales-push.md +303 -0
- package/docs/playground/index.html +893 -0
- package/docs/playground.html +4 -7
- package/docs/privacy-policy/index.html +122 -0
- package/docs/rfcs/defense-in-depth.md +467 -0
- package/docs/scan/index.html +358 -0
- package/docs/services/case-study.html +255 -0
- package/docs/services/downloads/install-openclaw.bat +45 -0
- package/docs/services/downloads/install-openclaw.command +38 -0
- package/docs/services/downloads/install-openclaw.sh +38 -0
- package/docs/services/get-started.html +165 -0
- package/docs/services/index.html +598 -0
- package/docs/services/multi-agent-security.html +284 -0
- package/docs/services/one-pager.html +99 -0
- package/docs/services/pitch-deck.html +229 -0
- package/docs/services/roi-calculator.html +258 -0
- package/docs/sitemap.xml +192 -2
- package/docs/support/index.html +135 -0
- package/docs/templates/customer-service/HEARTBEAT.md +61 -0
- package/docs/templates/customer-service/MEMORY.md +89 -0
- package/docs/templates/customer-service/SOUL.md +41 -0
- package/docs/templates/customer-service/USER.md +56 -0
- package/docs/templates/executive/HEARTBEAT.md +86 -0
- package/docs/templates/executive/MEMORY.md +92 -0
- package/docs/templates/executive/SOUL.md +44 -0
- package/docs/templates/executive/USER.md +62 -0
- package/docs/templates/finance/HEARTBEAT.md +58 -0
- package/docs/templates/finance/MEMORY.md +87 -0
- package/docs/templates/finance/SOUL.md +38 -0
- package/docs/templates/finance/USER.md +53 -0
- package/docs/templates/index.html +115 -0
- package/docs/templates/operations/HEARTBEAT.md +63 -0
- package/docs/templates/operations/MEMORY.md +68 -0
- package/docs/templates/operations/SOUL.md +38 -0
- package/docs/templates/operations/USER.md +49 -0
- package/docs/templates/sales/HEARTBEAT.md +55 -0
- package/docs/templates/sales/MEMORY.md +89 -0
- package/docs/templates/sales/SOUL.md +34 -0
- package/docs/templates/sales/USER.md +54 -0
- package/docs/terms-of-service/index.html +122 -0
- package/eslint.config.js +32 -0
- package/evals/README.md +29 -0
- package/evals/cases.json +390 -0
- package/evals/results.md +68 -0
- package/evals/run.js +180 -0
- package/examples/basic-usage.js +38 -0
- package/examples/demo-attack/demo.js +186 -0
- package/examples/python-quickstart/README.md +54 -0
- package/examples/python-quickstart/clawmoat_client.py +167 -0
- package/examples/video-demo/README.md +14 -0
- package/examples/video-demo/scene-a-normal.js +29 -0
- package/examples/video-demo/scene-b-attack-arrives.js +31 -0
- package/examples/video-demo/scene-c-hijack.js +44 -0
- package/examples/video-demo/scene-d-clawmoat.js +46 -0
- package/integrations/crewai/README.md +32 -0
- package/integrations/crewai/clawmoat_crewai/__init__.py +17 -0
- package/integrations/crewai/clawmoat_crewai/guard.py +103 -0
- package/integrations/crewai/pyproject.toml +21 -0
- package/integrations/langchain/README.md +91 -0
- package/integrations/langchain/clawmoat_langchain/__init__.py +17 -0
- package/integrations/langchain/clawmoat_langchain/callback.py +489 -0
- package/integrations/langchain/pyproject.toml +32 -0
- package/integrations/litellm/README.md +324 -0
- package/integrations/litellm/clawmoat_litellm/__init__.py +21 -0
- package/integrations/litellm/clawmoat_litellm/callback.py +329 -0
- package/integrations/litellm/clawmoat_litellm/proxy_middleware.py +224 -0
- package/integrations/litellm/pyproject.toml +74 -0
- package/integrations/openai-agents/README.md +392 -0
- package/integrations/openai-agents/clawmoat_openai_agents/__init__.py +20 -0
- package/integrations/openai-agents/clawmoat_openai_agents/guardrail.py +431 -0
- package/integrations/openai-agents/clawmoat_openai_agents/middleware.py +311 -0
- package/integrations/openai-agents/pyproject.toml +76 -0
- package/package.json +6 -5
- package/plugins/openclaw-adapter/PHASE1.md +439 -0
- package/plugins/openclaw-adapter/README.md +103 -0
- package/plugins/openclaw-adapter/SPEC.md +1644 -0
- package/plugins/openclaw-adapter/package.json +31 -0
- package/plugins/openclaw-adapter/src/index.test.ts +226 -0
- package/plugins/openclaw-adapter/src/index.ts +140 -0
- package/plugins/openclaw-adapter/tsconfig.json +14 -0
- package/server/data/threats.json +290 -0
- package/server/index.js +224 -10
- package/src/adapters/express.js +161 -0
- package/src/adapters/index.js +92 -0
- package/src/adapters/langchain.js +185 -0
- package/src/approval/index.js +456 -0
- package/src/ban-scanner.js +200 -0
- package/src/boundary-scanner.js +296 -0
- package/src/ci-scanner.js +279 -0
- package/src/code-scanner.js +245 -0
- package/src/enforce.js +166 -0
- package/src/finance/index.js +585 -0
- package/src/finance/mcp-firewall.js +486 -0
- package/src/formatters/json.js +80 -0
- package/src/formatters/sarif.js +388 -0
- package/src/guardian/alerts.js +34 -3
- package/src/guardian/gateway-monitor.js +590 -0
- package/src/guardian/index.js +41 -2
- package/src/index.js +105 -0
- package/src/integrations/agentmesh.js +501 -0
- package/src/language-detector.js +201 -0
- package/src/mcp-scanner.js +253 -0
- package/src/multimodal/index.js +579 -0
- package/src/obfuscation-scanner.js +457 -0
- package/src/policy-engine.js +402 -0
- package/src/scanners/dependency-attacks.js +128 -0
- package/src/scanners/prompt-injection.js +18 -0
- package/src/scanners/supply-chain.js +14 -0
- package/src/templates/default-config.yml +90 -0
- package/src/vuln-ops/exploitability.js +46 -0
- package/src/watch/live-monitor.js +720 -0
|
@@ -0,0 +1,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
|
+
};
|