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,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
|
+
};
|
package/src/guardian/alerts.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
129
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
120
130
|
});
|
|
121
131
|
req.on('error', () => {});
|
|
122
|
-
req.write(
|
|
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;
|