clawmoat 0.8.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +9 -0
- package/CHANGELOG.md +18 -0
- package/DEMO.md +87 -0
- package/Dockerfile +5 -18
- package/README.md +232 -8
- 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 +11 -4
- package/docs/blog/40000-exposed-openclaw-instances.html +11 -4
- package/docs/blog/agent-trust-protocol.html +5 -4
- 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 +10 -4
- package/docs/blog/host-guardian-launch.html +18 -8
- package/docs/blog/ibm-experts-agent-runtime-protection.html +15 -6
- package/docs/blog/index.html +67 -9
- package/docs/blog/langchain-security-tutorial.html +18 -8
- package/docs/blog/mcp-30-cves-security-crisis.html +11 -4
- package/docs/blog/meta-researcher-rogue-agent.html +201 -0
- package/docs/blog/microsoft-openclaw-workstation-security.html +5 -4
- package/docs/blog/nist-ai-agent-standards-clawmoat.html +16 -8
- package/docs/blog/oasis-websocket-hijack.html +11 -4
- package/docs/blog/ollama-openclaw-security.html +10 -4
- package/docs/blog/openclaw-enterprise-readiness-claw10.html +5 -4
- package/docs/blog/openclaw-security-reckoning-2026.html +11 -4
- 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 +11 -16
- package/docs/business/install.html +21 -7
- package/docs/checklist.html +10 -4
- 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 +9 -6
- package/docs/guides/business-deployment.html +770 -0
- package/docs/hall-of-fame.html +11 -5
- package/docs/index.html +266 -137
- 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/rfcs/defense-in-depth.md +467 -0
- package/docs/scan/index.html +156 -12
- 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 +62 -2
- package/docs/support/index.html +12 -1
- 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/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/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 +142 -7
- 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/formatters/json.js +80 -0
- package/src/formatters/sarif.js +388 -0
- package/src/guardian/alerts.js +34 -3
- package/src/guardian/index.js +41 -2
- package/src/index.js +102 -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
- package/clawmoat-0.8.0.tgz +0 -0
- package/server/index.js.patch +0 -1
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "CLAWMOAT-2026-0001",
|
|
4
|
+
"title": "LiteLLM .pth File Injection — Credential Stealer in PyPI",
|
|
5
|
+
"published": "2026-03-24T00:00:00Z",
|
|
6
|
+
"updated": "2026-03-26T00:00:00Z",
|
|
7
|
+
"severity": "critical",
|
|
8
|
+
"category": "supply-chain",
|
|
9
|
+
"tags": [
|
|
10
|
+
"python",
|
|
11
|
+
"pypi",
|
|
12
|
+
"credential-theft",
|
|
13
|
+
"pth-injection",
|
|
14
|
+
"ai-agent"
|
|
15
|
+
],
|
|
16
|
+
"summary": "LiteLLM versions 1.82.7 and 1.82.8 contain a credential-stealing payload delivered via a .pth file that Python's site module auto-executes on interpreter startup. Harvests SSH keys, cloud credentials, API keys, and crypto wallets. Package has 97M monthly downloads.",
|
|
17
|
+
"affected": {
|
|
18
|
+
"packages": [
|
|
19
|
+
"litellm==1.82.7",
|
|
20
|
+
"litellm==1.82.8"
|
|
21
|
+
],
|
|
22
|
+
"ecosystems": [
|
|
23
|
+
"pypi"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"ioc": {
|
|
27
|
+
"domains": [
|
|
28
|
+
"models.litellm.cloud"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"litellm_init.pth"
|
|
32
|
+
],
|
|
33
|
+
"hashes": [],
|
|
34
|
+
"patterns": [
|
|
35
|
+
"exec(base64.b64decode(",
|
|
36
|
+
"litellm_init.pth",
|
|
37
|
+
"import base64; exec(base64.b64decode"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"detection": {
|
|
41
|
+
"clawmoat_module": "scanners/supply-chain",
|
|
42
|
+
"clawmoat_function": "scanSkillContent",
|
|
43
|
+
"rules_triggered": [
|
|
44
|
+
"obfuscated_exec_b64",
|
|
45
|
+
"pth_file_injection",
|
|
46
|
+
"pth_known_malicious_litellm",
|
|
47
|
+
"exfil_litellm_lookalike"
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"mitigation": "Check version: pip show litellm | grep Version. Search for payload: find / -name litellm_init.pth 2>/dev/null. If affected: rotate ALL credentials — SSH keys, AWS/GCP/Azure creds, API keys, K8s configs, database passwords.",
|
|
51
|
+
"references": [
|
|
52
|
+
"https://github.com/BerriAI/litellm/issues/24512",
|
|
53
|
+
"https://news.ycombinator.com/item?id=47501426",
|
|
54
|
+
"https://awesomeagents.ai/news/litellm-supply-chain-compromise-credential-theft/"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": "CLAWMOAT-2026-0002",
|
|
59
|
+
"title": "Meta Internal AI Agent Causes Sensitive Data Leak",
|
|
60
|
+
"published": "2026-03-20T00:00:00Z",
|
|
61
|
+
"updated": "2026-03-20T00:00:00Z",
|
|
62
|
+
"severity": "high",
|
|
63
|
+
"category": "excessive-agency",
|
|
64
|
+
"tags": [
|
|
65
|
+
"ai-agent",
|
|
66
|
+
"data-leak",
|
|
67
|
+
"excessive-agency",
|
|
68
|
+
"internal"
|
|
69
|
+
],
|
|
70
|
+
"summary": "A Meta internal AI agent responded to an engineer's question with incorrect instructions that, when followed, exposed sensitive user and company data to internal employees for two hours. Highlights risk of AI agents operating with broad system access and no runtime guardrails.",
|
|
71
|
+
"affected": {
|
|
72
|
+
"packages": [],
|
|
73
|
+
"ecosystems": [
|
|
74
|
+
"internal-ai-agents"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"ioc": {
|
|
78
|
+
"domains": [],
|
|
79
|
+
"files": [],
|
|
80
|
+
"hashes": [],
|
|
81
|
+
"patterns": []
|
|
82
|
+
},
|
|
83
|
+
"detection": {
|
|
84
|
+
"clawmoat_module": "scanners/excessive-agency",
|
|
85
|
+
"clawmoat_function": "scanExcessiveAgency",
|
|
86
|
+
"rules_triggered": [
|
|
87
|
+
"excessive_data_access",
|
|
88
|
+
"unbounded_action_scope"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"mitigation": "Enforce least-privilege access for AI agents. Require human approval for actions affecting sensitive data. Use ClawMoat Guardian permission tiers to restrict agent scope.",
|
|
92
|
+
"references": [
|
|
93
|
+
"https://www.theguardian.com/technology/2026/mar/20/meta-ai-agents-instruction-causes-large-sensitive-data-leak-to-employees"
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"id": "CLAWMOAT-2026-0003",
|
|
98
|
+
"title": "1,748 Active API Keys Exposed Across 10,000 Websites",
|
|
99
|
+
"published": "2026-03-23T00:00:00Z",
|
|
100
|
+
"updated": "2026-03-25T00:00:00Z",
|
|
101
|
+
"severity": "high",
|
|
102
|
+
"category": "credential-exposure",
|
|
103
|
+
"tags": [
|
|
104
|
+
"api-keys",
|
|
105
|
+
"javascript",
|
|
106
|
+
"bundler",
|
|
107
|
+
"aws",
|
|
108
|
+
"stripe",
|
|
109
|
+
"openai",
|
|
110
|
+
"github"
|
|
111
|
+
],
|
|
112
|
+
"summary": "Stanford researchers scanned 10M web pages and found 1,748 verified active credentials from 14 major providers (AWS, Stripe, GitHub, OpenAI) on nearly 10,000 websites. 84% were in JavaScript bundles. Average exposure time: 12 months. Affected orgs include a global financial institution and major hosting platform.",
|
|
113
|
+
"affected": {
|
|
114
|
+
"packages": [],
|
|
115
|
+
"ecosystems": [
|
|
116
|
+
"javascript",
|
|
117
|
+
"web"
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"ioc": {
|
|
121
|
+
"domains": [],
|
|
122
|
+
"files": [],
|
|
123
|
+
"hashes": [],
|
|
124
|
+
"patterns": [
|
|
125
|
+
"AKIA[A-Z0-9]{16}",
|
|
126
|
+
"sk-[a-zA-Z0-9]{48}",
|
|
127
|
+
"ghp_[a-zA-Z0-9]{36}"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
"detection": {
|
|
131
|
+
"clawmoat_module": "scanners/secrets",
|
|
132
|
+
"clawmoat_function": "scanSecrets",
|
|
133
|
+
"rules_triggered": [
|
|
134
|
+
"aws_access_key",
|
|
135
|
+
"openai_api_key",
|
|
136
|
+
"github_token",
|
|
137
|
+
"stripe_key"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"mitigation": "Scan your JavaScript bundles for exposed credentials before deployment. Never embed API keys in client-side code. Use environment variables and server-side proxies for all third-party API calls.",
|
|
141
|
+
"references": [
|
|
142
|
+
"https://www.newscientist.com/article/2520143-security-credentials-inadvertently-leaked-on-thousands-of-websites/"
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"id": "CLAWMOAT-2026-0004",
|
|
147
|
+
"title": "Gemini API Key Theft Leads to $82,314 Bill",
|
|
148
|
+
"published": "2026-03-01T00:00:00Z",
|
|
149
|
+
"updated": "2026-03-20T00:00:00Z",
|
|
150
|
+
"severity": "high",
|
|
151
|
+
"category": "credential-exposure",
|
|
152
|
+
"tags": [
|
|
153
|
+
"api-keys",
|
|
154
|
+
"google",
|
|
155
|
+
"gemini",
|
|
156
|
+
"gcp",
|
|
157
|
+
"financial-impact"
|
|
158
|
+
],
|
|
159
|
+
"summary": "A developer received an $82,314 GCP bill after a Gemini API key was stolen. Root cause: GCP API key over-scoping and lack of key rotation policies. AI agent workflows that embed API keys in code or config files are particularly vulnerable.",
|
|
160
|
+
"affected": {
|
|
161
|
+
"packages": [],
|
|
162
|
+
"ecosystems": [
|
|
163
|
+
"gcp",
|
|
164
|
+
"python",
|
|
165
|
+
"ai-agent"
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
"ioc": {
|
|
169
|
+
"domains": [],
|
|
170
|
+
"files": [],
|
|
171
|
+
"hashes": [],
|
|
172
|
+
"patterns": [
|
|
173
|
+
"AIza[0-9A-Za-z\\-_]{35}"
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
"detection": {
|
|
177
|
+
"clawmoat_module": "scanners/secrets",
|
|
178
|
+
"clawmoat_function": "scanSecrets",
|
|
179
|
+
"rules_triggered": [
|
|
180
|
+
"gcp_api_key"
|
|
181
|
+
]
|
|
182
|
+
},
|
|
183
|
+
"mitigation": "Scope API keys to minimum required permissions. Set billing alerts. Rotate keys regularly. Use secret managers (GCP Secret Manager, AWS Secrets Manager) instead of embedding keys in code.",
|
|
184
|
+
"references": [
|
|
185
|
+
"https://www.cloudquery.io/blog/gcp-gemini-api-key-audit"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"id": "CLAWMOAT-2026-0005",
|
|
190
|
+
"title": "Trivy Supply Chain Compromise — CI/CD Security Scanner Poisoned",
|
|
191
|
+
"published": "2026-03-19T00:00:00Z",
|
|
192
|
+
"updated": "2026-03-24T00:00:00Z",
|
|
193
|
+
"severity": "critical",
|
|
194
|
+
"category": "supply-chain",
|
|
195
|
+
"tags": [
|
|
196
|
+
"trivy",
|
|
197
|
+
"ci-cd",
|
|
198
|
+
"supply-chain",
|
|
199
|
+
"github-actions",
|
|
200
|
+
"devops"
|
|
201
|
+
],
|
|
202
|
+
"summary": "TeamPCP compromised the Trivy security scanner used in CI/CD pipelines. By poisoning a security tool, attackers gained publish access to downstream packages including LiteLLM (97M monthly downloads). Attack chain: Trivy → CI/CD token → PyPI publish token → poisoned package → credential harvest.",
|
|
203
|
+
"affected": {
|
|
204
|
+
"packages": [
|
|
205
|
+
"trivy (affected CI/CD versions)",
|
|
206
|
+
"litellm==1.82.7",
|
|
207
|
+
"litellm==1.82.8"
|
|
208
|
+
],
|
|
209
|
+
"ecosystems": [
|
|
210
|
+
"pypi",
|
|
211
|
+
"github-actions",
|
|
212
|
+
"ci-cd"
|
|
213
|
+
]
|
|
214
|
+
},
|
|
215
|
+
"ioc": {
|
|
216
|
+
"domains": [
|
|
217
|
+
"models.litellm.cloud"
|
|
218
|
+
],
|
|
219
|
+
"files": [
|
|
220
|
+
"litellm_init.pth"
|
|
221
|
+
],
|
|
222
|
+
"hashes": [],
|
|
223
|
+
"patterns": [
|
|
224
|
+
"TeamPCP"
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
"detection": {
|
|
228
|
+
"clawmoat_module": "scanners/supply-chain",
|
|
229
|
+
"clawmoat_function": "scanSkill",
|
|
230
|
+
"rules_triggered": [
|
|
231
|
+
"supply_chain_untrusted_source",
|
|
232
|
+
"obfuscated_exec_b64"
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
"mitigation": "Pin all CI/CD tool versions. Verify checksums of security tools before use. Scope CI/CD tokens to minimum permissions — PyPI publish tokens should be package-scoped. Rotate all tokens after any dependency upgrade.",
|
|
236
|
+
"references": [
|
|
237
|
+
"https://ramimac.me/trivy-teampcp/",
|
|
238
|
+
"https://news.ycombinator.com/item?id=47450142"
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"id": "CLAWMOAT-2026-0006",
|
|
243
|
+
"title": "AnythingLLM SQL Injection in SQL Agent (CVE-2026-32628)",
|
|
244
|
+
"published": "2026-03-26T00:00:00Z",
|
|
245
|
+
"updated": "2026-03-26T00:00:00Z",
|
|
246
|
+
"severity": "high",
|
|
247
|
+
"category": "excessive-agency",
|
|
248
|
+
"tags": [
|
|
249
|
+
"sql-injection",
|
|
250
|
+
"ai-agent",
|
|
251
|
+
"anythingllm",
|
|
252
|
+
"database",
|
|
253
|
+
"cve"
|
|
254
|
+
],
|
|
255
|
+
"summary": "CVE-2026-32628: High-severity SQL injection in AnythingLLM (56K GitHub stars) built-in SQL Agent affects MySQL, PostgreSQL, and MSSQL connectors. The agent has direct DB access and the flaw allows attackers to execute arbitrary SQL through it.",
|
|
256
|
+
"affected": {
|
|
257
|
+
"packages": [
|
|
258
|
+
"anythingllm"
|
|
259
|
+
],
|
|
260
|
+
"ecosystems": [
|
|
261
|
+
"npm",
|
|
262
|
+
"docker"
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
"ioc": {
|
|
266
|
+
"domains": [],
|
|
267
|
+
"files": [],
|
|
268
|
+
"hashes": [],
|
|
269
|
+
"patterns": [
|
|
270
|
+
"SELECT * FROM",
|
|
271
|
+
"UNION SELECT",
|
|
272
|
+
"DROP TABLE",
|
|
273
|
+
"1=1"
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
"detection": {
|
|
277
|
+
"clawmoat_module": "scanners/excessive-agency",
|
|
278
|
+
"clawmoat_function": "scanExcessiveAgency",
|
|
279
|
+
"rules_triggered": [
|
|
280
|
+
"excessive_data_access",
|
|
281
|
+
"unbounded_db_query",
|
|
282
|
+
"sql_injection_pattern"
|
|
283
|
+
]
|
|
284
|
+
},
|
|
285
|
+
"mitigation": "Update AnythingLLM immediately. Restrict SQL Agent to read-only database user. Never grant AI agents DROP/DELETE/ALTER permissions.",
|
|
286
|
+
"references": [
|
|
287
|
+
"https://hackernoon.com/a-56000-star-ai-app-shipped-with-a-textbook-sql-injection-flaw"
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
]
|
package/server/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
const Stripe = require('stripe');
|
|
3
5
|
|
|
4
6
|
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
|
|
@@ -7,13 +9,15 @@ const SITE_URL = process.env.SITE_URL || 'https://clawmoat.com';
|
|
|
7
9
|
|
|
8
10
|
const PRICES = {
|
|
9
11
|
// Security Kit (one-time purchase)
|
|
10
|
-
'security-kit': process.env.PRICE_SECURITY_KIT || 'price_1T5F3LAUiOw2ZIorTAPB0Q76', // $29 one-time
|
|
12
|
+
'security-kit': process.env.PRICE_SECURITY_KIT || 'price_1T5F3LAUiOw2ZIorTAPB0Q76', // $29 one-time (legacy)
|
|
13
|
+
'dev-monthly': process.env.PRICE_DEV_MONTHLY || 'price_1TFnOIAUiOw2ZIor6V5PdXBx', // $9/mo
|
|
14
|
+
'dev-yearly': process.env.PRICE_DEV_YEARLY || 'price_1TFnOIAUiOw2ZIorJhG9KYZX', // $90/yr
|
|
11
15
|
// Pro subscriptions
|
|
12
|
-
'shield-monthly': process.env.PRICE_SHIELD_MONTHLY || 'price_1T5F23AUiOw2ZIor2oUgTD8W', // $14.99/mo
|
|
13
|
-
'shield-yearly': process.env.PRICE_SHIELD_YEARLY || 'price_1T5F23AUiOw2ZIorQLdy51G0', // $149/yr
|
|
16
|
+
'shield-monthly': process.env.PRICE_SHIELD_MONTHLY || 'price_1T5F23AUiOw2ZIor2oUgTD8W', // $14.99/mo (legacy)
|
|
17
|
+
'shield-yearly': process.env.PRICE_SHIELD_YEARLY || 'price_1T5F23AUiOw2ZIorQLdy51G0', // $149/yr (legacy)
|
|
14
18
|
// Team subscriptions
|
|
15
|
-
'team-monthly': process.env.PRICE_TEAM_MONTHLY || '
|
|
16
|
-
'team-yearly': process.env.PRICE_TEAM_YEARLY || '
|
|
19
|
+
'team-monthly': process.env.PRICE_TEAM_MONTHLY || 'price_1TFnOJAUiOw2ZIorunRX8OKm', // $49/mo
|
|
20
|
+
'team-yearly': process.env.PRICE_TEAM_YEARLY || 'price_1TFnOJAUiOw2ZIorlOfiCnNk', // $490/yr
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
const ONE_TIME_PLANS = new Set(['security-kit']);
|
|
@@ -21,6 +25,45 @@ const ONE_TIME_PLANS = new Set(['security-kit']);
|
|
|
21
25
|
// In-memory license store (replace with DB in production)
|
|
22
26
|
const licenses = new Map();
|
|
23
27
|
|
|
28
|
+
// ─── Threat Intel helpers ─────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
const THREATS_PATH = path.join(__dirname, 'data/threats.json');
|
|
31
|
+
const API_KEYS_PATH = path.join(__dirname, 'data/api-keys.json');
|
|
32
|
+
|
|
33
|
+
function loadThreats() {
|
|
34
|
+
try { return JSON.parse(fs.readFileSync(THREATS_PATH, 'utf8')); }
|
|
35
|
+
catch { return []; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadApiKeys() {
|
|
39
|
+
try { return JSON.parse(fs.readFileSync(API_KEYS_PATH, 'utf8')); }
|
|
40
|
+
catch { return {}; }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function saveApiKeys(keys) {
|
|
44
|
+
fs.writeFileSync(API_KEYS_PATH, JSON.stringify(keys, null, 2));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function checkApiKey(req) {
|
|
48
|
+
const key = req.headers['x-api-key'];
|
|
49
|
+
if (!key) return null;
|
|
50
|
+
const keys = loadApiKeys();
|
|
51
|
+
return keys[key] ? { key, ...keys[key] } : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function trackApiUsage(apiKey) {
|
|
55
|
+
const keys = loadApiKeys();
|
|
56
|
+
if (keys[apiKey]) {
|
|
57
|
+
keys[apiKey].calls_this_month = (keys[apiKey].calls_this_month || 0) + 1;
|
|
58
|
+
saveApiKeys(keys);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function parseQuery(url) {
|
|
63
|
+
const u = new URL(url, 'http://localhost');
|
|
64
|
+
return Object.fromEntries(u.searchParams.entries());
|
|
65
|
+
}
|
|
66
|
+
|
|
24
67
|
function generateLicenseKey() {
|
|
25
68
|
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
26
69
|
const segments = [];
|
|
@@ -74,7 +117,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
74
117
|
const priceId = PRICES[body.plan];
|
|
75
118
|
|
|
76
119
|
if (!priceId) {
|
|
77
|
-
return json(res, 400, { error: 'Invalid plan. Use:
|
|
120
|
+
return json(res, 400, { error: 'Invalid plan. Use: dev-monthly, dev-yearly, team-monthly, team-yearly' });
|
|
78
121
|
}
|
|
79
122
|
|
|
80
123
|
try {
|
|
@@ -82,7 +125,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
82
125
|
const sessionParams = {
|
|
83
126
|
mode: isOneTime ? 'payment' : 'subscription',
|
|
84
127
|
line_items: [{ price: priceId, quantity: 1 }],
|
|
85
|
-
success_url:
|
|
128
|
+
success_url: `https://app.clawmoat.com/dashboard?welcome=true&session_id={CHECKOUT_SESSION_ID}`,
|
|
86
129
|
cancel_url: `${SITE_URL}/#pricing`,
|
|
87
130
|
allow_promotion_codes: true,
|
|
88
131
|
customer_email: body.email || undefined,
|
|
@@ -233,6 +276,98 @@ const server = http.createServer(async (req, res) => {
|
|
|
233
276
|
return json(res, 200, { success: true, message: 'Thank you! We\'ll be in touch within 24 hours.' });
|
|
234
277
|
}
|
|
235
278
|
|
|
279
|
+
// ─── Threat Intel API ──────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
// GET /api/v1/threats
|
|
282
|
+
if (req.method === 'GET' && req.url.startsWith('/api/v1/threats')) {
|
|
283
|
+
const apiUser = checkApiKey(req);
|
|
284
|
+
if (!apiUser) return json(res, 401, { error: 'API key required. Add X-API-Key header. Get a free key at clawmoat.com/api' });
|
|
285
|
+
|
|
286
|
+
// Rate limit free tier
|
|
287
|
+
if (apiUser.tier === 'free' && apiUser.calls_this_month >= (apiUser.monthly_limit || 100)) {
|
|
288
|
+
return json(res, 429, { error: 'Free tier limit reached (100 calls/month). Upgrade at clawmoat.com/#pricing' });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
trackApiUsage(apiUser.key);
|
|
292
|
+
const threats = loadThreats();
|
|
293
|
+
|
|
294
|
+
// Single threat by id: /api/v1/threats/CLAWMOAT-2026-0001
|
|
295
|
+
const idMatch = req.url.match(/^\/api\/v1\/threats\/([^?]+)/);
|
|
296
|
+
if (idMatch) {
|
|
297
|
+
const threat = threats.find(t => t.id === idMatch[1]);
|
|
298
|
+
if (!threat) return json(res, 404, { error: 'Threat not found' });
|
|
299
|
+
return json(res, 200, { threat });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// List with filters
|
|
303
|
+
const q = parseQuery(req.url);
|
|
304
|
+
let filtered = threats;
|
|
305
|
+
if (q.category) filtered = filtered.filter(t => t.category === q.category);
|
|
306
|
+
if (q.severity) filtered = filtered.filter(t => t.severity === q.severity);
|
|
307
|
+
if (q.tags) filtered = filtered.filter(t => q.tags.split(',').every(tag => t.tags.includes(tag)));
|
|
308
|
+
if (q.since) filtered = filtered.filter(t => t.published >= q.since);
|
|
309
|
+
const limit = Math.min(parseInt(q.limit) || 20, 100);
|
|
310
|
+
filtered = filtered.slice(0, limit);
|
|
311
|
+
|
|
312
|
+
const updated = threats.reduce((max, t) => t.updated > max ? t.updated : max, '');
|
|
313
|
+
return json(res, 200, { threats: filtered, count: filtered.length, total: threats.length, updated });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// GET /api/v1/ioc — aggregated indicators of compromise
|
|
317
|
+
if (req.method === 'GET' && req.url.startsWith('/api/v1/ioc')) {
|
|
318
|
+
const apiUser = checkApiKey(req);
|
|
319
|
+
if (!apiUser) return json(res, 401, { error: 'API key required. Add X-API-Key header. Get a free key at clawmoat.com/api' });
|
|
320
|
+
|
|
321
|
+
if (apiUser.tier === 'free' && apiUser.calls_this_month >= (apiUser.monthly_limit || 100)) {
|
|
322
|
+
return json(res, 429, { error: 'Free tier limit reached (100 calls/month). Upgrade at clawmoat.com/#pricing' });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
trackApiUsage(apiUser.key);
|
|
326
|
+
const threats = loadThreats();
|
|
327
|
+
|
|
328
|
+
const ioc = { domains: new Set(), files: new Set(), patterns: new Set() };
|
|
329
|
+
for (const t of threats) {
|
|
330
|
+
(t.ioc?.domains || []).forEach(d => ioc.domains.add(d));
|
|
331
|
+
(t.ioc?.files || []).forEach(f => ioc.files.add(f));
|
|
332
|
+
(t.ioc?.patterns || []).forEach(p => ioc.patterns.add(p));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const updated = threats.reduce((max, t) => t.updated > max ? t.updated : max, '');
|
|
336
|
+
return json(res, 200, {
|
|
337
|
+
domains: [...ioc.domains],
|
|
338
|
+
files: [...ioc.files],
|
|
339
|
+
patterns: [...ioc.patterns],
|
|
340
|
+
threat_count: threats.length,
|
|
341
|
+
updated,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// POST /api/v1/keys — generate free API key
|
|
346
|
+
if (req.method === 'POST' && req.url === '/api/v1/keys') {
|
|
347
|
+
const body = await readBody(req);
|
|
348
|
+
if (!body.email) return json(res, 400, { error: 'email required' });
|
|
349
|
+
|
|
350
|
+
const newKey = 'cm-' + require('crypto').randomBytes(16).toString('hex');
|
|
351
|
+
const keys = loadApiKeys();
|
|
352
|
+
keys[newKey] = {
|
|
353
|
+
tier: 'free',
|
|
354
|
+
email: body.email,
|
|
355
|
+
calls_this_month: 0,
|
|
356
|
+
monthly_limit: 100,
|
|
357
|
+
created: new Date().toISOString().slice(0, 10),
|
|
358
|
+
label: 'Free tier — ' + body.email,
|
|
359
|
+
};
|
|
360
|
+
saveApiKeys(keys);
|
|
361
|
+
console.log(`New API key issued: ${body.email}`);
|
|
362
|
+
return json(res, 201, {
|
|
363
|
+
api_key: newKey,
|
|
364
|
+
tier: 'free',
|
|
365
|
+
monthly_limit: 100,
|
|
366
|
+
docs: 'https://clawmoat.com/docs/api',
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// ─── License validation endpoint (called by CLI) ──────────────────────────
|
|
236
371
|
// License validation endpoint (called by CLI)
|
|
237
372
|
if (req.method === 'POST' && req.url === '/api/validate') {
|
|
238
373
|
const body = await readBody(req);
|
|
@@ -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
|
+
};
|