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,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawMoat Policy Engine — Declarative security rules for AI agents
|
|
3
|
+
*
|
|
4
|
+
* Define rules in YAML/JSON, enforce them at runtime.
|
|
5
|
+
* This is what makes ClawMoat a firewall, not a scanner.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const { PolicyEngine } = require('clawmoat');
|
|
9
|
+
* const engine = new PolicyEngine('./clawmoat-policy.yaml');
|
|
10
|
+
*
|
|
11
|
+
* // Evaluate a tool call
|
|
12
|
+
* const decision = engine.evaluate({
|
|
13
|
+
* type: 'tool_call',
|
|
14
|
+
* tool: 'slack.send',
|
|
15
|
+
* args: { channel: '#general', text: 'API key: sk-...' },
|
|
16
|
+
* context: { user: 'agent-1', source: 'mcp' }
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* if (decision.action === 'block') {
|
|
20
|
+
* console.log('Blocked:', decision.reason);
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
|
|
27
|
+
// Severity levels (ordered)
|
|
28
|
+
const SEVERITY_ORDER = { critical: 4, high: 3, medium: 2, low: 1, info: 0 };
|
|
29
|
+
|
|
30
|
+
// Built-in data patterns for classification
|
|
31
|
+
const DATA_PATTERNS = {
|
|
32
|
+
secret: [
|
|
33
|
+
/sk-(?:proj-)?[a-zA-Z0-9]{20,}/g, // OpenAI keys
|
|
34
|
+
/ghp_[a-zA-Z0-9]{36}/g, // GitHub PAT
|
|
35
|
+
/glpat-[a-zA-Z0-9\-_]{20,}/g, // GitLab PAT
|
|
36
|
+
/xox[bpsa]-[a-zA-Z0-9\-]{10,}/g, // Slack tokens
|
|
37
|
+
/AKIA[A-Z0-9]{16}/g, // AWS access key
|
|
38
|
+
/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g, // Private keys
|
|
39
|
+
/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, // JWT tokens
|
|
40
|
+
],
|
|
41
|
+
pii: [
|
|
42
|
+
/\b\d{3}-\d{2}-\d{4}\b/g, // SSN
|
|
43
|
+
/\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi, // Email
|
|
44
|
+
/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, // Credit card
|
|
45
|
+
/\b\d{3}[\s.-]?\d{3}[\s.-]?\d{4}\b/g, // Phone number
|
|
46
|
+
],
|
|
47
|
+
internal: [
|
|
48
|
+
/(?:internal|confidential|restricted|do not share)/gi,
|
|
49
|
+
/(?:JIRA|CONFLUENCE)-[A-Z]+-\d+/g, // Internal ticket refs
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Built-in tool risk levels
|
|
54
|
+
const TOOL_RISK_DEFAULTS = {
|
|
55
|
+
// Read-only = low
|
|
56
|
+
'read': 'low', 'get': 'low', 'list': 'low', 'search': 'low', 'query': 'low',
|
|
57
|
+
// Side-effecting = medium
|
|
58
|
+
'create': 'medium', 'update': 'medium', 'write': 'medium', 'put': 'medium',
|
|
59
|
+
// External comms = high
|
|
60
|
+
'send': 'high', 'email': 'high', 'post': 'high', 'publish': 'high', 'notify': 'high',
|
|
61
|
+
// Destructive/privileged = critical
|
|
62
|
+
'delete': 'critical', 'exec': 'critical', 'shell': 'critical', 'eval': 'critical',
|
|
63
|
+
'drop': 'critical', 'rm': 'critical', 'chmod': 'critical', 'sudo': 'critical',
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} PolicyRule
|
|
68
|
+
* @property {string} id - Unique rule identifier
|
|
69
|
+
* @property {string} [description] - Human-readable description
|
|
70
|
+
* @property {string} [severity] - Rule severity (critical/high/medium/low)
|
|
71
|
+
* @property {Object} when - Match conditions
|
|
72
|
+
* @property {string} action - Enforcement action (block/warn/sanitize/log/require_approval)
|
|
73
|
+
* @property {string} [message] - Message to include in decision
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {Object} EvalEvent
|
|
78
|
+
* @property {string} type - Event type (tool_call/inbound/outbound/memory_write/retrieval)
|
|
79
|
+
* @property {string} [tool] - Tool name (for tool_call events)
|
|
80
|
+
* @property {Object} [args] - Tool arguments
|
|
81
|
+
* @property {string} [text] - Text content (for inbound/outbound)
|
|
82
|
+
* @property {Object} [context] - Additional context (user, source, session, etc.)
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @typedef {Object} Decision
|
|
87
|
+
* @property {string} action - Final action (allow/block/warn/sanitize/require_approval)
|
|
88
|
+
* @property {string} [ruleId] - ID of the rule that triggered
|
|
89
|
+
* @property {string} [reason] - Human-readable reason
|
|
90
|
+
* @property {string} [severity] - Severity of the match
|
|
91
|
+
* @property {Array} matchedRules - All rules that matched
|
|
92
|
+
* @property {Object} classifications - Data classifications found
|
|
93
|
+
* @property {number} timestamp - Unix timestamp
|
|
94
|
+
* @property {number} latencyMs - Processing time
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
class PolicyEngine {
|
|
98
|
+
/**
|
|
99
|
+
* @param {string|Object|Array} policy - Path to YAML/JSON file, or policy object/array
|
|
100
|
+
* @param {Object} [options]
|
|
101
|
+
* @param {string} [options.mode='block'] - Default mode: monitor/warn/sanitize/block
|
|
102
|
+
* @param {boolean} [options.trace=true] - Enable execution tracing
|
|
103
|
+
* @param {Function} [options.onDecision] - Callback for every decision
|
|
104
|
+
*/
|
|
105
|
+
constructor(policy, options = {}) {
|
|
106
|
+
this.mode = options.mode || 'block';
|
|
107
|
+
this.trace = options.trace !== false;
|
|
108
|
+
this.onDecision = options.onDecision || null;
|
|
109
|
+
this.rules = [];
|
|
110
|
+
this.traceLog = [];
|
|
111
|
+
this.stats = { total: 0, allowed: 0, blocked: 0, warned: 0, sanitized: 0 };
|
|
112
|
+
this.toolRiskOverrides = {};
|
|
113
|
+
|
|
114
|
+
if (typeof policy === 'string') {
|
|
115
|
+
this._loadFromFile(policy);
|
|
116
|
+
} else if (Array.isArray(policy)) {
|
|
117
|
+
this.rules = policy;
|
|
118
|
+
} else if (policy && policy.rules) {
|
|
119
|
+
this.rules = policy.rules;
|
|
120
|
+
if (policy.tool_risks) this.toolRiskOverrides = policy.tool_risks;
|
|
121
|
+
if (policy.mode) this.mode = policy.mode;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_loadFromFile(filePath) {
|
|
126
|
+
const ext = path.extname(filePath);
|
|
127
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
128
|
+
|
|
129
|
+
if (ext === '.json') {
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
this.rules = parsed.rules || parsed;
|
|
132
|
+
if (parsed.tool_risks) this.toolRiskOverrides = parsed.tool_risks;
|
|
133
|
+
if (parsed.mode) this.mode = parsed.mode;
|
|
134
|
+
} else if (ext === '.yaml' || ext === '.yml') {
|
|
135
|
+
// Simple YAML parser (no deps) — handles the subset we need
|
|
136
|
+
const parsed = this._parseSimpleYaml(raw);
|
|
137
|
+
this.rules = parsed.rules || [];
|
|
138
|
+
if (parsed.tool_risks) this.toolRiskOverrides = parsed.tool_risks;
|
|
139
|
+
if (parsed.mode) this.mode = parsed.mode;
|
|
140
|
+
} else {
|
|
141
|
+
throw new Error(`Unsupported policy file format: ${ext}. Use .json or .yaml`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_parseSimpleYaml(text) {
|
|
146
|
+
// Minimal YAML parser for policy files — handles maps, arrays, strings
|
|
147
|
+
// For production, users should convert to JSON. This handles 90% of cases.
|
|
148
|
+
try {
|
|
149
|
+
// Strip comments
|
|
150
|
+
const lines = text.split('\n').filter(l => !l.trim().startsWith('#'));
|
|
151
|
+
const cleaned = lines.join('\n');
|
|
152
|
+
// Convert YAML-ish to JSON-ish (basic)
|
|
153
|
+
let json = cleaned
|
|
154
|
+
.replace(/:\s*\n/g, ': null\n') // empty values
|
|
155
|
+
.replace(/^(\s*)- /gm, '$1 ') // arrays
|
|
156
|
+
;
|
|
157
|
+
// Fall back to JSON.parse if it looks like JSON
|
|
158
|
+
if (cleaned.trim().startsWith('{') || cleaned.trim().startsWith('[')) {
|
|
159
|
+
return JSON.parse(cleaned);
|
|
160
|
+
}
|
|
161
|
+
// For complex YAML, tell user to use JSON
|
|
162
|
+
throw new Error('Complex YAML detected. Please use JSON format for policy files, or install a YAML parser.');
|
|
163
|
+
} catch(e) {
|
|
164
|
+
throw new Error(`Failed to parse policy YAML: ${e.message}. Tip: use .json format for reliability.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Classify text content for data types
|
|
170
|
+
* @param {string} text
|
|
171
|
+
* @returns {Object} classifications { secret: [...], pii: [...], internal: [...] }
|
|
172
|
+
*/
|
|
173
|
+
classify(text) {
|
|
174
|
+
if (!text || typeof text !== 'string') return {};
|
|
175
|
+
const result = {};
|
|
176
|
+
for (const [category, patterns] of Object.entries(DATA_PATTERNS)) {
|
|
177
|
+
const matches = [];
|
|
178
|
+
for (const pattern of patterns) {
|
|
179
|
+
const p = new RegExp(pattern.source, pattern.flags);
|
|
180
|
+
const found = text.match(p);
|
|
181
|
+
if (found) matches.push(...found);
|
|
182
|
+
}
|
|
183
|
+
if (matches.length > 0) result[category] = matches;
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get risk level for a tool
|
|
190
|
+
* @param {string} toolName
|
|
191
|
+
* @returns {string} risk level
|
|
192
|
+
*/
|
|
193
|
+
getToolRisk(toolName) {
|
|
194
|
+
if (this.toolRiskOverrides[toolName]) return this.toolRiskOverrides[toolName];
|
|
195
|
+
const lower = (toolName || '').toLowerCase();
|
|
196
|
+
for (const [keyword, risk] of Object.entries(TOOL_RISK_DEFAULTS)) {
|
|
197
|
+
if (lower.includes(keyword)) return risk;
|
|
198
|
+
}
|
|
199
|
+
return 'medium'; // default
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check if an event matches a rule's conditions
|
|
204
|
+
* @param {EvalEvent} event
|
|
205
|
+
* @param {PolicyRule} rule
|
|
206
|
+
* @returns {boolean}
|
|
207
|
+
*/
|
|
208
|
+
_matchRule(event, rule) {
|
|
209
|
+
const when = rule.when;
|
|
210
|
+
if (!when) return false;
|
|
211
|
+
|
|
212
|
+
// Match event type
|
|
213
|
+
if (when.type && when.type !== event.type) return false;
|
|
214
|
+
|
|
215
|
+
// Match tool name
|
|
216
|
+
if (when.tool) {
|
|
217
|
+
const tools = Array.isArray(when.tool) ? when.tool : [when.tool];
|
|
218
|
+
const eventTool = event.tool || '';
|
|
219
|
+
if (!tools.some(t => {
|
|
220
|
+
if (t === '*') return true;
|
|
221
|
+
if (t.includes('*')) {
|
|
222
|
+
// Convert glob to regex: "http.*" → /^http\..*$/
|
|
223
|
+
const re = new RegExp('^' + t.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
|
224
|
+
return re.test(eventTool);
|
|
225
|
+
}
|
|
226
|
+
return eventTool === t || eventTool.toLowerCase() === t.toLowerCase();
|
|
227
|
+
})) return false;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Match text content contains
|
|
231
|
+
if (when.input_contains) {
|
|
232
|
+
const text = this._getEventText(event);
|
|
233
|
+
const terms = Array.isArray(when.input_contains) ? when.input_contains : [when.input_contains];
|
|
234
|
+
if (!terms.some(term => text.toLowerCase().includes(term.toLowerCase()))) return false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Match text content regex
|
|
238
|
+
if (when.input_matches) {
|
|
239
|
+
const text = this._getEventText(event);
|
|
240
|
+
const patterns = Array.isArray(when.input_matches) ? when.input_matches : [when.input_matches];
|
|
241
|
+
if (!patterns.some(p => new RegExp(p, 'i').test(text))) return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Match data classification
|
|
245
|
+
if (when.data_classification) {
|
|
246
|
+
const text = this._getEventText(event);
|
|
247
|
+
const classifications = this.classify(text);
|
|
248
|
+
const required = Array.isArray(when.data_classification) ? when.data_classification : [when.data_classification];
|
|
249
|
+
if (!required.some(c => classifications[c] && classifications[c].length > 0)) return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Match tool risk level
|
|
253
|
+
if (when.tool_risk) {
|
|
254
|
+
const risk = this.getToolRisk(event.tool);
|
|
255
|
+
const required = Array.isArray(when.tool_risk) ? when.tool_risk : [when.tool_risk];
|
|
256
|
+
if (!required.includes(risk)) return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Match context
|
|
260
|
+
if (when.context) {
|
|
261
|
+
const ctx = event.context || {};
|
|
262
|
+
for (const [key, val] of Object.entries(when.context)) {
|
|
263
|
+
if (ctx[key] !== val) return false;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Match source
|
|
268
|
+
if (when.source) {
|
|
269
|
+
const sources = Array.isArray(when.source) ? when.source : [when.source];
|
|
270
|
+
const eventSource = (event.context || {}).source || event.source || '';
|
|
271
|
+
if (!sources.includes(eventSource)) return false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_getEventText(event) {
|
|
278
|
+
if (event.text) return event.text;
|
|
279
|
+
if (event.args) return JSON.stringify(event.args);
|
|
280
|
+
return '';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Evaluate an event against all rules
|
|
285
|
+
* @param {EvalEvent} event
|
|
286
|
+
* @returns {Decision}
|
|
287
|
+
*/
|
|
288
|
+
evaluate(event) {
|
|
289
|
+
const start = Date.now();
|
|
290
|
+
this.stats.total++;
|
|
291
|
+
|
|
292
|
+
const text = this._getEventText(event);
|
|
293
|
+
const classifications = this.classify(text);
|
|
294
|
+
const matchedRules = [];
|
|
295
|
+
|
|
296
|
+
// Test all rules
|
|
297
|
+
for (const rule of this.rules) {
|
|
298
|
+
if (this._matchRule(event, rule)) {
|
|
299
|
+
matchedRules.push(rule);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Determine action from highest-severity matched rule
|
|
304
|
+
let finalAction = 'allow';
|
|
305
|
+
let triggerRule = null;
|
|
306
|
+
let maxSeverity = -1;
|
|
307
|
+
|
|
308
|
+
for (const rule of matchedRules) {
|
|
309
|
+
const sev = SEVERITY_ORDER[rule.severity || 'medium'] || 2;
|
|
310
|
+
if (sev > maxSeverity) {
|
|
311
|
+
maxSeverity = sev;
|
|
312
|
+
triggerRule = rule;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (triggerRule) {
|
|
317
|
+
// Apply mode override
|
|
318
|
+
const ruleAction = triggerRule.action || 'block';
|
|
319
|
+
if (this.mode === 'monitor') {
|
|
320
|
+
finalAction = 'log';
|
|
321
|
+
} else if (this.mode === 'warn') {
|
|
322
|
+
finalAction = 'warn';
|
|
323
|
+
} else {
|
|
324
|
+
finalAction = ruleAction;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Update stats
|
|
329
|
+
if (finalAction === 'allow' || finalAction === 'log') this.stats.allowed++;
|
|
330
|
+
else if (finalAction === 'block') this.stats.blocked++;
|
|
331
|
+
else if (finalAction === 'warn') this.stats.warned++;
|
|
332
|
+
else if (finalAction === 'sanitize') this.stats.sanitized++;
|
|
333
|
+
|
|
334
|
+
const decision = {
|
|
335
|
+
action: finalAction,
|
|
336
|
+
ruleId: triggerRule ? triggerRule.id : null,
|
|
337
|
+
reason: triggerRule ? (triggerRule.message || triggerRule.description || `Matched rule: ${triggerRule.id}`) : null,
|
|
338
|
+
severity: triggerRule ? (triggerRule.severity || 'medium') : null,
|
|
339
|
+
matchedRules: matchedRules.map(r => ({ id: r.id, severity: r.severity, action: r.action })),
|
|
340
|
+
classifications: Object.keys(classifications).length > 0 ? classifications : undefined,
|
|
341
|
+
toolRisk: event.tool ? this.getToolRisk(event.tool) : undefined,
|
|
342
|
+
timestamp: Date.now(),
|
|
343
|
+
latencyMs: Date.now() - start,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// Trace log
|
|
347
|
+
if (this.trace) {
|
|
348
|
+
this.traceLog.push({
|
|
349
|
+
event: { type: event.type, tool: event.tool },
|
|
350
|
+
decision: { action: decision.action, ruleId: decision.ruleId, severity: decision.severity },
|
|
351
|
+
timestamp: decision.timestamp,
|
|
352
|
+
latencyMs: decision.latencyMs,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Callback
|
|
357
|
+
if (this.onDecision) {
|
|
358
|
+
try { this.onDecision(decision, event); } catch(e) { /* ignore callback errors */ }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return decision;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get execution trace
|
|
366
|
+
* @returns {Array}
|
|
367
|
+
*/
|
|
368
|
+
getTrace() {
|
|
369
|
+
return [...this.traceLog];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Get stats
|
|
374
|
+
* @returns {Object}
|
|
375
|
+
*/
|
|
376
|
+
getStats() {
|
|
377
|
+
return { ...this.stats };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Simulate a policy against a set of test events
|
|
382
|
+
* @param {Array<EvalEvent>} events
|
|
383
|
+
* @returns {Object} simulation results
|
|
384
|
+
*/
|
|
385
|
+
simulate(events) {
|
|
386
|
+
const originalMode = this.mode;
|
|
387
|
+
this.mode = 'block'; // Simulate full enforcement
|
|
388
|
+
const results = events.map(event => {
|
|
389
|
+
const decision = this.evaluate(event);
|
|
390
|
+
return { event, decision };
|
|
391
|
+
});
|
|
392
|
+
this.mode = originalMode;
|
|
393
|
+
return {
|
|
394
|
+
total: results.length,
|
|
395
|
+
blocked: results.filter(r => r.decision.action === 'block').length,
|
|
396
|
+
allowed: results.filter(r => r.decision.action === 'allow').length,
|
|
397
|
+
results,
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports = { PolicyEngine, DATA_PATTERNS, TOOL_RISK_DEFAULTS, SEVERITY_ORDER };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawMoat — Dependency Attack Scanner
|
|
3
|
+
*
|
|
4
|
+
* Detects attack patterns derived from real CVEs in common dependencies.
|
|
5
|
+
* Inspired by vulnerability analysis of SOP-Automation and similar projects.
|
|
6
|
+
*
|
|
7
|
+
* Attack classes covered:
|
|
8
|
+
* 1. Prototype Pollution (axios CVE, lodash CVE history)
|
|
9
|
+
* 2. ReDoS injection (minimatch CVE family)
|
|
10
|
+
* 3. Decompression bombs (urllib3 CVE family)
|
|
11
|
+
* 4. JWT manipulation (PyJWT, jose CVE family)
|
|
12
|
+
* 5. Path traversal in archives (tar CVE family — complements multimodal scanner)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// ─── Prototype Pollution ─────────────────────────────────────────────────────
|
|
16
|
+
// Attackers inject __proto__ or constructor.prototype into JSON/objects
|
|
17
|
+
// passed to vulnerable libraries (axios mergeConfig, lodash merge, etc.)
|
|
18
|
+
|
|
19
|
+
const PROTOTYPE_POLLUTION_PATTERNS = [
|
|
20
|
+
{ pattern: /"__proto__"\s*:/, severity: 'critical', name: 'prototype_pollution_proto' },
|
|
21
|
+
{ pattern: /"constructor"\s*:\s*\{[^}]*"prototype"/, severity: 'critical', name: 'prototype_pollution_constructor' },
|
|
22
|
+
{ pattern: /\["__proto__"\]/, severity: 'critical', name: 'prototype_pollution_bracket' },
|
|
23
|
+
{ pattern: /\.__proto__\s*=/, severity: 'critical', name: 'prototype_pollution_assign' },
|
|
24
|
+
{ pattern: /Object\.prototype\[/, severity: 'high', name: 'prototype_pollution_object' },
|
|
25
|
+
{ pattern: /\["constructor"\]\s*\[["']prototype["']\]/, severity: 'critical', name: 'prototype_pollution_chain' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// ─── ReDoS Patterns ──────────────────────────────────────────────────────────
|
|
29
|
+
// Detect when AI agent is being instructed to use or process catastrophically
|
|
30
|
+
// backtracking regex patterns (minimatch/picomatch CVE family: nested *(), repeated extglobs, multiple **)
|
|
31
|
+
|
|
32
|
+
const REDOS_PATTERNS = [
|
|
33
|
+
// Nested quantifiers — classic ReDoS
|
|
34
|
+
{ pattern: /(\(\S+\+\)\+|\(\S+\*\)\*|\(\S+\?\)\+)/, severity: 'high', name: 'redos_nested_quantifier' },
|
|
35
|
+
// Multiple adjacent GLOBSTAR patterns (minimatch specific)
|
|
36
|
+
{ pattern: /\*\*[^\s/]*\*\*[^\s/]*\*\*/, severity: 'high', name: 'redos_globstar_chain' },
|
|
37
|
+
// Nested or repeated *() extglob quantifiers (minimatch/picomatch ReDoS families)
|
|
38
|
+
{ pattern: /(?:\*\([^)]*\*\([^)]*\)|(?:\*\([^)]*\)){2,})/, severity: 'high', name: 'redos_nested_extglob' },
|
|
39
|
+
// Evil regex known patterns
|
|
40
|
+
{ pattern: /\(\.\*\)\+|\(\.\+\)\*|\(\.\*\)\{/, severity: 'medium', name: 'redos_evil_regex' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// ─── Decompression Bomb Detection ────────────────────────────────────────────
|
|
44
|
+
// Detect signals of zip/gzip/brotli bomb attacks
|
|
45
|
+
// (urllib3 CVE family: GHSA-g4mx-q9vg-27p4 — unlimited decompression chain)
|
|
46
|
+
|
|
47
|
+
const DECOMPRESSION_BOMB_PATTERNS = [
|
|
48
|
+
// Instruction to process suspiciously large compressed data
|
|
49
|
+
{ pattern: /(?:decompress|unzip|extract|gunzip|unbrotli)\s+(?:the\s+)?(?:following|this|attached|uploaded)\s+(?:file|data|content)/i, severity: 'medium', name: 'decompression_instruction' },
|
|
50
|
+
// Base64-encoded data that's extremely large (>100KB encoded = likely bomb)
|
|
51
|
+
// We check for very long base64 strings as a signal
|
|
52
|
+
{ pattern: /(?:^|[\s"'])([A-Za-z0-9+/]{100000,}={0,2})(?:$|[\s"'])/, severity: 'high', name: 'decompression_large_b64' },
|
|
53
|
+
// Multiple nested compression signals
|
|
54
|
+
{ pattern: /(?:gzip|deflate|brotli|zstd|lz4).*(?:gzip|deflate|brotli|zstd|lz4).*(?:gzip|deflate|brotli|zstd|lz4)/i, severity: 'medium', name: 'decompression_chain' },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// ─── JWT Manipulation ─────────────────────────────────────────────────────────
|
|
58
|
+
// Detect JWT tampering techniques
|
|
59
|
+
// (PyJWT unknown crit header CVE, alg:none attack, header injection)
|
|
60
|
+
|
|
61
|
+
const JWT_MANIPULATION_PATTERNS = [
|
|
62
|
+
// Algorithm confusion (alg:none or symmetric/asymmetric swap)
|
|
63
|
+
{ pattern: /"alg"\s*:\s*"(?:none|None|NONE)"/, severity: 'critical', name: 'jwt_alg_none' },
|
|
64
|
+
{ pattern: /eyJ[A-Za-z0-9_-]*\.eyJ[A-Za-z0-9_-]*\.(?:$|\s)/, severity: 'high', name: 'jwt_no_signature' }, // JWT with empty sig
|
|
65
|
+
// crit header manipulation (PyJWT CVE GHSA-m695-7mj6-7w6v)
|
|
66
|
+
{ pattern: /"crit"\s*:\s*\[[^\]]*"[a-zA-Z0-9_-]+"[^\]]*\]/, severity: 'high', name: 'jwt_crit_header' },
|
|
67
|
+
// kid injection (SQL/path injection via key ID header)
|
|
68
|
+
{ pattern: /"kid"\s*:\s*"[^"]*(?:\.\.\/|SELECT|UNION|exec|eval)[^"]*"/, severity: 'critical', name: 'jwt_kid_injection' },
|
|
69
|
+
// JWT embedded in instruction (agent being told to use a forged token)
|
|
70
|
+
{ pattern: /use\s+(?:this|the\s+following)\s+(?:jwt|token|bearer)\s*[:\s]+eyJ/i, severity: 'high', name: 'jwt_forged_token_instruction' },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
// ─── Archive Path Traversal ───────────────────────────────────────────────────
|
|
74
|
+
// Text-based detection for archive-related traversal instructions
|
|
75
|
+
// (Complements the filename-level detection in multimodal scanner)
|
|
76
|
+
// tar CVE family: GHSA-qffp-2rhf-9h96, GHSA-j44v-mmf2-xvm9
|
|
77
|
+
|
|
78
|
+
const ARCHIVE_TRAVERSAL_PATTERNS = [
|
|
79
|
+
// Instruction to extract to absolute/relative path outside working dir
|
|
80
|
+
{ pattern: /(?:extract|untar|unzip|decompress)\s+(?:the\s+)?(?:archive\s+)?(?:to|into)\s+(?:\/|~|\.\.)/, severity: 'high', name: 'archive_traversal_extract' },
|
|
81
|
+
// Archive containing files with suspicious path patterns (in description/content)
|
|
82
|
+
{ pattern: /(?:archive|tar|zip)\s+(?:contains?|includes?|has)\s+(?:files?\s+with\s+paths?\s+(?:like|starting|beginning)\s+)?(?:\/|\.\.\/|[A-Za-z]:)/i, severity: 'medium', name: 'archive_traversal_describe' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// ─── Scanner ─────────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Scan text for dependency-class attack patterns
|
|
89
|
+
* @param {string} text - Text to scan
|
|
90
|
+
* @returns {{ clean: boolean, findings: Array, severity: string|null }}
|
|
91
|
+
*/
|
|
92
|
+
function scanDependencyAttacks(text) {
|
|
93
|
+
const findings = [];
|
|
94
|
+
|
|
95
|
+
const allPatterns = [
|
|
96
|
+
...PROTOTYPE_POLLUTION_PATTERNS,
|
|
97
|
+
...REDOS_PATTERNS,
|
|
98
|
+
...DECOMPRESSION_BOMB_PATTERNS,
|
|
99
|
+
...JWT_MANIPULATION_PATTERNS,
|
|
100
|
+
...ARCHIVE_TRAVERSAL_PATTERNS,
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
for (const { pattern, severity, name } of allPatterns) {
|
|
104
|
+
const match = text.match(pattern);
|
|
105
|
+
if (match) {
|
|
106
|
+
findings.push({
|
|
107
|
+
type: 'dependency_attack',
|
|
108
|
+
subtype: name,
|
|
109
|
+
severity,
|
|
110
|
+
matched: match[0].substring(0, 100), // cap at 100 chars
|
|
111
|
+
position: text.indexOf(match[0]),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const severityRank = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
117
|
+
const topSeverity = findings.reduce((max, f) => {
|
|
118
|
+
return (severityRank[f.severity] || 0) > (severityRank[max] || 0) ? f.severity : max;
|
|
119
|
+
}, null);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
clean: findings.length === 0,
|
|
123
|
+
findings,
|
|
124
|
+
severity: topSeverity,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { scanDependencyAttacks };
|
|
@@ -26,6 +26,17 @@ const INJECTION_PATTERNS = [
|
|
|
26
26
|
{ pattern: /(?:show|reveal|display|print|output|repeat|echo)\s+(?:me\s+)?(?:your|the)\s+(?:system\s+)?(?:prompt|instructions?|rules?|guidelines?)/i, severity: 'high', name: 'system_prompt_extraction' },
|
|
27
27
|
{ pattern: /what\s+(?:are|is)\s+your\s+(?:system\s+)?(?:prompt|instructions?|rules?|initial\s+message)/i, severity: 'medium', name: 'system_prompt_extraction' },
|
|
28
28
|
{ pattern: /(?:beginning|start)\s+of\s+(?:the\s+)?(?:system|initial)\s+(?:prompt|message|instruction)/i, severity: 'high', name: 'system_prompt_extraction' },
|
|
29
|
+
{ pattern: /repeat\s+(?:everything|all|the\s+text)\s+(?:above|before)\s+(?:this|here)/i, severity: 'high', name: 'system_prompt_extraction' },
|
|
30
|
+
{ pattern: /(?:complete|finish)\s+(?:the|this)\s+(?:following\s+)?sentence\s*:?\s*.*(?:system\s+prompt|my\s+instructions)/i, severity: 'medium', name: 'system_prompt_extraction' },
|
|
31
|
+
{ pattern: /(?:verbatim|word.for.word|exactly)\s*,?\s*(?:including|with)\s+(?:any|all|the)\s+(?:instructions?|rules?|prompts?)/i, severity: 'high', name: 'system_prompt_extraction' },
|
|
32
|
+
|
|
33
|
+
// Indirect injection (instructions embedded in data/tool results)
|
|
34
|
+
{ pattern: /(?:IMPORTANT|URGENT|NOTE|SYSTEM)\s*:\s*(?:The\s+)?(?:user|admin|system)\s+(?:has\s+)?(?:requested|wants|requires|asked)/i, severity: 'critical', name: 'indirect_injection' },
|
|
35
|
+
{ pattern: /(?:to\s+complete\s+the\s+task|to\s+continue)\s*[,:]\s*(?:run|execute|curl|wget|send|post)/i, severity: 'critical', name: 'indirect_injection' },
|
|
36
|
+
|
|
37
|
+
// CI/CD injection
|
|
38
|
+
{ pattern: /\$\{\{\s*github\.event\./i, severity: 'high', name: 'ci_injection' },
|
|
39
|
+
{ pattern: /\$\{\{\s*(?:inputs|secrets|env)\./i, severity: 'high', name: 'ci_injection' },
|
|
29
40
|
|
|
30
41
|
// Data exfiltration attempts
|
|
31
42
|
{ pattern: /(?:send|post|upload|transmit|exfiltrate|forward)\s+(?:all|the|my|this|your)\s+(?:data|files?|info|content|messages?|history|conversation)/i, severity: 'critical', name: 'data_exfiltration' },
|
|
@@ -44,6 +55,13 @@ const INJECTION_PATTERNS = [
|
|
|
44
55
|
// Tool abuse instructions
|
|
45
56
|
{ pattern: /(?:run|execute|call|use)\s+(?:the\s+)?(?:exec|shell|terminal|command|bash)\s+(?:tool|function)/i, severity: 'medium', name: 'tool_abuse' },
|
|
46
57
|
{ pattern: /(?:read|access|open)\s+(?:the\s+)?(?:file|path)\s+(?:\/etc|~\/\.ssh|~\/\.aws|\.env)/i, severity: 'high', name: 'tool_abuse' },
|
|
58
|
+
{ pattern: /(?:cat|read|get|show|display|print)\s+~\/\.ssh\//i, severity: 'critical', name: 'credential_access' },
|
|
59
|
+
{ pattern: /(?:cat|read|get|show|display|print)\s+~\/\.aws\//i, severity: 'critical', name: 'credential_access' },
|
|
60
|
+
{ pattern: /(?:cat|read|get|show|display|print)\s+\.env\b/i, severity: 'high', name: 'credential_access' },
|
|
61
|
+
|
|
62
|
+
// Credential/key content in text
|
|
63
|
+
{ pattern: /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/i, severity: 'critical', name: 'private_key_content' },
|
|
64
|
+
{ pattern: /-----BEGIN\s+OPENSSH\s+PRIVATE\s+KEY-----/i, severity: 'critical', name: 'private_key_content' },
|
|
47
65
|
];
|
|
48
66
|
|
|
49
67
|
// Heuristic signals that text contains instruction-like content (in a data context)
|
|
@@ -33,6 +33,20 @@ const SKILL_PATTERNS = [
|
|
|
33
33
|
{ pattern: /\batob\s*\(/i, severity: 'medium', name: 'obfuscated_atob' },
|
|
34
34
|
{ pattern: /\bBuffer\.from\s*\([^,]+,\s*['"]base64['"]\s*\)/i, severity: 'medium', name: 'obfuscated_buffer' },
|
|
35
35
|
{ pattern: /\\x[0-9a-f]{2}(?:\\x[0-9a-f]{2}){5,}/i, severity: 'high', name: 'obfuscated_hex' },
|
|
36
|
+
// Double base64 encoding (LiteLLM-style payload obfuscation)
|
|
37
|
+
{ pattern: /base64[._]?(?:b64)?decode\s*\([^)]*base64[._]?(?:b64)?decode/i, severity: 'critical', name: 'obfuscated_double_b64' },
|
|
38
|
+
// exec(base64.b64decode(...)) — Python payload pattern
|
|
39
|
+
{ pattern: /\bexec\s*\(\s*(?:base64\.)?(?:b64decode|decodestring)\s*\(/i, severity: 'critical', name: 'obfuscated_exec_b64' },
|
|
40
|
+
|
|
41
|
+
// .pth file injection (Python site-packages auto-execution, as used in LiteLLM attack)
|
|
42
|
+
{ pattern: /\.pth\b.*(?:import|exec|subprocess|eval|compile)/i, severity: 'critical', name: 'pth_file_injection' },
|
|
43
|
+
{ pattern: /litellm_init\.pth/i, severity: 'critical', name: 'pth_known_malicious_litellm' },
|
|
44
|
+
// Generic .pth with code execution (not just path entries)
|
|
45
|
+
{ pattern: /^import\s+\w+/m, severity: 'low', name: 'pth_import_statement' },
|
|
46
|
+
|
|
47
|
+
// Lookalike domain detection (exfiltration via typosquatting)
|
|
48
|
+
{ pattern: /models\.litellm\.cloud/i, severity: 'critical', name: 'exfil_litellm_lookalike' },
|
|
49
|
+
{ pattern: /(?:pypi|npmjs|github|googleapis)\.(?:cloud|io|dev|app)\b/i, severity: 'high', name: 'exfil_lookalike_domain' },
|
|
36
50
|
|
|
37
51
|
// System configuration modification
|
|
38
52
|
{ pattern: /\bcrontab\b/i, severity: 'high', name: 'system_crontab' },
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# ClawMoat Configuration
|
|
2
|
+
# Generated by: clawmoat init
|
|
3
|
+
# Docs: https://clawmoat.com/docs
|
|
4
|
+
|
|
5
|
+
# Operating mode: enforce (block threats) | monitor (log only) | off
|
|
6
|
+
mode: enforce
|
|
7
|
+
|
|
8
|
+
# Scanning stages (agent boundary pipeline)
|
|
9
|
+
stages:
|
|
10
|
+
pre-input: true # User → Agent
|
|
11
|
+
pre-model: false # Agent → LLM (enable for prompt leak detection)
|
|
12
|
+
pre-tool-call: true # LLM → Tool (dangerous commands, exfil)
|
|
13
|
+
post-tool-result: true # Tool → LLM (poisoned results)
|
|
14
|
+
pre-output: true # Agent → User (secret/PII leakage)
|
|
15
|
+
|
|
16
|
+
# Scanners to enable
|
|
17
|
+
scanners:
|
|
18
|
+
prompt-injection: true
|
|
19
|
+
jailbreak: true
|
|
20
|
+
secrets: true
|
|
21
|
+
pii: true
|
|
22
|
+
obfuscation: true
|
|
23
|
+
code-danger: true
|
|
24
|
+
exfiltration: true
|
|
25
|
+
language-anomaly: false # Enable if your agent is single-language
|
|
26
|
+
|
|
27
|
+
# MCP security
|
|
28
|
+
mcp:
|
|
29
|
+
scan-on-start: true
|
|
30
|
+
block-dangerous-servers: true
|
|
31
|
+
require-pinned-versions: false
|
|
32
|
+
|
|
33
|
+
# Ban lists (add your own)
|
|
34
|
+
banned:
|
|
35
|
+
substrings: []
|
|
36
|
+
# - "DROP TABLE"
|
|
37
|
+
# - "/etc/shadow"
|
|
38
|
+
patterns: []
|
|
39
|
+
# - "rm\\s+-rf\\s+[/~]"
|
|
40
|
+
topics: []
|
|
41
|
+
# - "how to hack"
|
|
42
|
+
|
|
43
|
+
# Tool permissions
|
|
44
|
+
tools:
|
|
45
|
+
# High-risk tools require confirmation
|
|
46
|
+
require-confirmation:
|
|
47
|
+
- exec
|
|
48
|
+
- shell
|
|
49
|
+
- write_file
|
|
50
|
+
- delete_file
|
|
51
|
+
- send_email
|
|
52
|
+
# Tools that are always blocked
|
|
53
|
+
blocked: []
|
|
54
|
+
# - format_disk
|
|
55
|
+
# Max tool calls per turn (prevent runaway agents)
|
|
56
|
+
max-per-turn: 20
|
|
57
|
+
|
|
58
|
+
# Data classification
|
|
59
|
+
data:
|
|
60
|
+
# Auto-redact these in outbound messages
|
|
61
|
+
redact-secrets: true
|
|
62
|
+
redact-pii: false
|
|
63
|
+
# Custom secret patterns
|
|
64
|
+
secret-patterns: []
|
|
65
|
+
# - name: "Internal API Key"
|
|
66
|
+
# pattern: "MYAPP_[A-Z0-9]{32}"
|
|
67
|
+
|
|
68
|
+
# Alerting
|
|
69
|
+
alerts:
|
|
70
|
+
# Console logging (always on)
|
|
71
|
+
console: true
|
|
72
|
+
# File logging
|
|
73
|
+
file: null # Set to path: "./clawmoat-audit.log"
|
|
74
|
+
# Webhook (POST JSON on critical findings)
|
|
75
|
+
webhook: null # Set to URL: "https://hooks.slack.com/..."
|
|
76
|
+
# Minimum severity to alert: critical | high | medium | low
|
|
77
|
+
min-severity: high
|
|
78
|
+
|
|
79
|
+
# CI/CD mode settings
|
|
80
|
+
ci:
|
|
81
|
+
# Fail CI on findings of this severity or higher
|
|
82
|
+
fail-on: high
|
|
83
|
+
# Output format: text | json | sarif
|
|
84
|
+
format: text
|
|
85
|
+
# Scan these paths for agent configs
|
|
86
|
+
paths:
|
|
87
|
+
- "."
|
|
88
|
+
- ".claude/"
|
|
89
|
+
- ".cursor/"
|
|
90
|
+
- ".vscode/"
|