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,720 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawMoat Live Monitor
|
|
3
|
+
* Real-time terminal dashboard for AI agent security monitoring
|
|
4
|
+
* Like htop but for AI agents - visual, impressive, demo-worthy
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const { EventEmitter } = require('events');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ANSI color codes for terminal output
|
|
14
|
+
*/
|
|
15
|
+
const COLORS = {
|
|
16
|
+
RESET: '\x1b[0m',
|
|
17
|
+
BOLD: '\x1b[1m',
|
|
18
|
+
DIM: '\x1b[2m',
|
|
19
|
+
RED: '\x1b[31m',
|
|
20
|
+
GREEN: '\x1b[32m',
|
|
21
|
+
YELLOW: '\x1b[33m',
|
|
22
|
+
BLUE: '\x1b[34m',
|
|
23
|
+
MAGENTA: '\x1b[35m',
|
|
24
|
+
CYAN: '\x1b[36m',
|
|
25
|
+
WHITE: '\x1b[37m',
|
|
26
|
+
BG_RED: '\x1b[41m',
|
|
27
|
+
BG_GREEN: '\x1b[42m',
|
|
28
|
+
BG_YELLOW: '\x1b[43m'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Unicode characters for better visual display
|
|
33
|
+
*/
|
|
34
|
+
const CHARS = {
|
|
35
|
+
BLOCK_FULL: '█',
|
|
36
|
+
BLOCK_THREE_QUARTERS: '▊',
|
|
37
|
+
BLOCK_HALF: '▌',
|
|
38
|
+
BLOCK_QUARTER: '▎',
|
|
39
|
+
BOX_VERTICAL: '│',
|
|
40
|
+
BOX_HORIZONTAL: '─',
|
|
41
|
+
BOX_TOP_LEFT: '┌',
|
|
42
|
+
BOX_TOP_RIGHT: '┐',
|
|
43
|
+
BOX_BOTTOM_LEFT: '└',
|
|
44
|
+
BOX_BOTTOM_RIGHT: '┘',
|
|
45
|
+
BOX_CROSS: '┼',
|
|
46
|
+
BOX_T_DOWN: '┬',
|
|
47
|
+
BOX_T_UP: '┴',
|
|
48
|
+
BOX_T_RIGHT: '├',
|
|
49
|
+
BOX_T_LEFT: '┤',
|
|
50
|
+
ARROW_UP: '↑',
|
|
51
|
+
ARROW_DOWN: '↓',
|
|
52
|
+
SHIELD: '🛡️',
|
|
53
|
+
WARNING: '⚠️',
|
|
54
|
+
BLOCKED: '🚫',
|
|
55
|
+
CHECK: '✓',
|
|
56
|
+
CROSS: '✗'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
class LiveMonitor extends EventEmitter {
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
super();
|
|
62
|
+
|
|
63
|
+
this.options = {
|
|
64
|
+
refreshRate: options.refreshRate || 1000, // milliseconds
|
|
65
|
+
watchDir: options.watchDir || path.join(os.homedir(), '.openclaw'),
|
|
66
|
+
showNetworkGraph: options.showNetworkGraph !== false,
|
|
67
|
+
showThreatMap: options.showThreatMap !== false,
|
|
68
|
+
maxHistoryItems: options.maxHistoryItems || 100,
|
|
69
|
+
animateCharts: options.animateCharts !== false,
|
|
70
|
+
...options
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
this.stats = {
|
|
74
|
+
uptime: 0,
|
|
75
|
+
totalScans: 0,
|
|
76
|
+
threatsBlocked: 0,
|
|
77
|
+
filesAccessed: 0,
|
|
78
|
+
networkCalls: 0,
|
|
79
|
+
agentsActive: 0,
|
|
80
|
+
lastThreat: null,
|
|
81
|
+
threatHistory: [],
|
|
82
|
+
fileAccess: [],
|
|
83
|
+
networkActivity: [],
|
|
84
|
+
threatsByType: {},
|
|
85
|
+
scanRate: 0,
|
|
86
|
+
threatRate: 0
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.isRunning = false;
|
|
90
|
+
this.startTime = Date.now();
|
|
91
|
+
this.lastUpdate = Date.now();
|
|
92
|
+
this.intervals = [];
|
|
93
|
+
|
|
94
|
+
// Terminal state
|
|
95
|
+
this.terminalWidth = process.stdout.columns || 80;
|
|
96
|
+
this.terminalHeight = process.stdout.rows || 24;
|
|
97
|
+
|
|
98
|
+
// Bind methods
|
|
99
|
+
this.handleResize = this.handleResize.bind(this);
|
|
100
|
+
this.handleKeypress = this.handleKeypress.bind(this);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Start the live monitoring dashboard
|
|
105
|
+
*/
|
|
106
|
+
async start() {
|
|
107
|
+
if (this.isRunning) return;
|
|
108
|
+
|
|
109
|
+
this.isRunning = true;
|
|
110
|
+
this.startTime = Date.now();
|
|
111
|
+
|
|
112
|
+
// Setup terminal
|
|
113
|
+
this.setupTerminal();
|
|
114
|
+
|
|
115
|
+
// Start monitoring loops
|
|
116
|
+
this.startFileWatcher();
|
|
117
|
+
this.startSessionMonitor();
|
|
118
|
+
this.startNetworkMonitor();
|
|
119
|
+
|
|
120
|
+
// Main display loop
|
|
121
|
+
const displayInterval = setInterval(() => {
|
|
122
|
+
this.updateDisplay();
|
|
123
|
+
}, this.options.refreshRate);
|
|
124
|
+
this.intervals.push(displayInterval);
|
|
125
|
+
|
|
126
|
+
// Stats calculation loop
|
|
127
|
+
const statsInterval = setInterval(() => {
|
|
128
|
+
this.updateStats();
|
|
129
|
+
}, 5000);
|
|
130
|
+
this.intervals.push(statsInterval);
|
|
131
|
+
|
|
132
|
+
this.emit('started');
|
|
133
|
+
console.log(`${COLORS.GREEN}${CHARS.SHIELD} ClawMoat Live Monitor started${COLORS.RESET}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Stop the monitoring dashboard
|
|
138
|
+
*/
|
|
139
|
+
stop() {
|
|
140
|
+
if (!this.isRunning) return;
|
|
141
|
+
|
|
142
|
+
this.isRunning = false;
|
|
143
|
+
|
|
144
|
+
// Clear intervals
|
|
145
|
+
this.intervals.forEach(interval => clearInterval(interval));
|
|
146
|
+
this.intervals = [];
|
|
147
|
+
|
|
148
|
+
// Cleanup terminal
|
|
149
|
+
this.cleanupTerminal();
|
|
150
|
+
|
|
151
|
+
this.emit('stopped');
|
|
152
|
+
console.log(`${COLORS.YELLOW}${CHARS.WARNING} ClawMoat Live Monitor stopped${COLORS.RESET}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Setup terminal for live monitoring
|
|
157
|
+
*/
|
|
158
|
+
setupTerminal() {
|
|
159
|
+
// Hide cursor
|
|
160
|
+
process.stdout.write('\x1b[?25l');
|
|
161
|
+
|
|
162
|
+
// Clear screen
|
|
163
|
+
process.stdout.write('\x1b[2J');
|
|
164
|
+
|
|
165
|
+
// Handle terminal resize
|
|
166
|
+
process.stdout.on('resize', this.handleResize);
|
|
167
|
+
|
|
168
|
+
// Setup keyboard input
|
|
169
|
+
if (process.stdin.isTTY) {
|
|
170
|
+
process.stdin.setRawMode(true);
|
|
171
|
+
process.stdin.on('keypress', this.handleKeypress);
|
|
172
|
+
process.stdin.resume();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Cleanup terminal state
|
|
178
|
+
*/
|
|
179
|
+
cleanupTerminal() {
|
|
180
|
+
// Show cursor
|
|
181
|
+
process.stdout.write('\x1b[?25h');
|
|
182
|
+
|
|
183
|
+
// Reset terminal
|
|
184
|
+
process.stdout.write('\x1b[0m\x1b[2J\x1b[H');
|
|
185
|
+
|
|
186
|
+
// Cleanup stdin
|
|
187
|
+
if (process.stdin.isTTY) {
|
|
188
|
+
process.stdin.setRawMode(false);
|
|
189
|
+
process.stdin.removeListener('keypress', this.handleKeypress);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Handle terminal resize
|
|
195
|
+
*/
|
|
196
|
+
handleResize() {
|
|
197
|
+
this.terminalWidth = process.stdout.columns || 80;
|
|
198
|
+
this.terminalHeight = process.stdout.rows || 24;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Handle keypress events
|
|
203
|
+
*/
|
|
204
|
+
handleKeypress(key, data) {
|
|
205
|
+
if (key === 'q' || key === '\u0003') { // 'q' or Ctrl+C
|
|
206
|
+
this.stop();
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Start file system watcher
|
|
213
|
+
*/
|
|
214
|
+
startFileWatcher() {
|
|
215
|
+
const credentialsPath = path.join(this.options.watchDir, 'credentials');
|
|
216
|
+
const sessionsPath = path.join(this.options.watchDir, 'agents', 'main', 'sessions');
|
|
217
|
+
|
|
218
|
+
[credentialsPath, sessionsPath].forEach(dir => {
|
|
219
|
+
if (fs.existsSync(dir)) {
|
|
220
|
+
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
221
|
+
if (filename && eventType === 'change') {
|
|
222
|
+
this.recordFileAccess(filename, path.join(dir, filename));
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Start session log monitor
|
|
231
|
+
*/
|
|
232
|
+
startSessionMonitor() {
|
|
233
|
+
const sessionInterval = setInterval(() => {
|
|
234
|
+
this.scanRecentSessions();
|
|
235
|
+
}, 2000);
|
|
236
|
+
this.intervals.push(sessionInterval);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Start network activity monitor
|
|
241
|
+
*/
|
|
242
|
+
startNetworkMonitor() {
|
|
243
|
+
// Monitor for new network calls in session logs
|
|
244
|
+
const networkInterval = setInterval(() => {
|
|
245
|
+
this.scanNetworkActivity();
|
|
246
|
+
}, 3000);
|
|
247
|
+
this.intervals.push(networkInterval);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Record file access event
|
|
252
|
+
*/
|
|
253
|
+
recordFileAccess(filename, fullPath) {
|
|
254
|
+
this.stats.filesAccessed++;
|
|
255
|
+
|
|
256
|
+
const event = {
|
|
257
|
+
timestamp: Date.now(),
|
|
258
|
+
filename,
|
|
259
|
+
fullPath,
|
|
260
|
+
type: this.classifyFileType(filename)
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
this.stats.fileAccess.unshift(event);
|
|
264
|
+
if (this.stats.fileAccess.length > this.options.maxHistoryItems) {
|
|
265
|
+
this.stats.fileAccess.pop();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Classify file type for display
|
|
271
|
+
*/
|
|
272
|
+
classifyFileType(filename) {
|
|
273
|
+
if (filename.includes('credential')) return 'credential';
|
|
274
|
+
if (filename.includes('session')) return 'session';
|
|
275
|
+
if (filename.includes('skill')) return 'skill';
|
|
276
|
+
if (filename.includes('memory')) return 'memory';
|
|
277
|
+
return 'other';
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Scan recent session files for threats
|
|
282
|
+
*/
|
|
283
|
+
scanRecentSessions() {
|
|
284
|
+
const sessionsDir = path.join(this.options.watchDir, 'agents', 'main', 'sessions');
|
|
285
|
+
|
|
286
|
+
if (!fs.existsSync(sessionsDir)) return;
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const files = fs.readdirSync(sessionsDir)
|
|
290
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
291
|
+
.sort((a, b) => {
|
|
292
|
+
const statA = fs.statSync(path.join(sessionsDir, a));
|
|
293
|
+
const statB = fs.statSync(path.join(sessionsDir, b));
|
|
294
|
+
return statB.mtime - statA.mtime;
|
|
295
|
+
})
|
|
296
|
+
.slice(0, 3); // Check 3 most recent files
|
|
297
|
+
|
|
298
|
+
files.forEach(file => {
|
|
299
|
+
this.procesSessionFile(path.join(sessionsDir, file));
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
// Silent fail - directory might not exist yet
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Process session file for threats
|
|
308
|
+
*/
|
|
309
|
+
procesSessionFile(filepath) {
|
|
310
|
+
try {
|
|
311
|
+
const content = fs.readFileSync(filepath, 'utf8');
|
|
312
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
313
|
+
|
|
314
|
+
lines.forEach(line => {
|
|
315
|
+
try {
|
|
316
|
+
const entry = JSON.parse(line);
|
|
317
|
+
if (entry.type === 'tool_call' || entry.type === 'message') {
|
|
318
|
+
this.stats.totalScans++;
|
|
319
|
+
this.simulateThreatDetection(entry);
|
|
320
|
+
}
|
|
321
|
+
} catch (e) {
|
|
322
|
+
// Skip invalid JSON lines
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// Silent fail - file might be locked
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Simulate threat detection (replace with real ClawMoat scanning)
|
|
332
|
+
*/
|
|
333
|
+
simulateThreatDetection(entry) {
|
|
334
|
+
const ClawMoat = require('../index');
|
|
335
|
+
const moat = new ClawMoat();
|
|
336
|
+
|
|
337
|
+
const text = entry.content || entry.data || '';
|
|
338
|
+
if (typeof text === 'string' && text.length > 0) {
|
|
339
|
+
const result = moat.scan(text);
|
|
340
|
+
|
|
341
|
+
if (!result.safe && result.findings.length > 0) {
|
|
342
|
+
this.recordThreat(result.findings[0]);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Record threat detection
|
|
349
|
+
*/
|
|
350
|
+
recordThreat(finding) {
|
|
351
|
+
this.stats.threatsBlocked++;
|
|
352
|
+
this.stats.lastThreat = finding;
|
|
353
|
+
|
|
354
|
+
const threat = {
|
|
355
|
+
timestamp: Date.now(),
|
|
356
|
+
type: finding.type,
|
|
357
|
+
subtype: finding.subtype,
|
|
358
|
+
severity: finding.severity
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
this.stats.threatHistory.unshift(threat);
|
|
362
|
+
if (this.stats.threatHistory.length > this.options.maxHistoryItems) {
|
|
363
|
+
this.stats.threatHistory.pop();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Update threat type counts
|
|
367
|
+
this.stats.threatsByType[finding.type] = (this.stats.threatsByType[finding.type] || 0) + 1;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Scan for network activity
|
|
372
|
+
*/
|
|
373
|
+
scanNetworkActivity() {
|
|
374
|
+
// Simulate network monitoring
|
|
375
|
+
if (Math.random() < 0.3) { // 30% chance of network activity
|
|
376
|
+
const activity = {
|
|
377
|
+
timestamp: Date.now(),
|
|
378
|
+
url: this.generateRandomUrl(),
|
|
379
|
+
method: Math.random() < 0.8 ? 'GET' : 'POST',
|
|
380
|
+
status: Math.random() < 0.95 ? 'allowed' : 'blocked'
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
this.stats.networkActivity.unshift(activity);
|
|
384
|
+
if (this.stats.networkActivity.length > this.options.maxHistoryItems) {
|
|
385
|
+
this.stats.networkActivity.pop();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
this.stats.networkCalls++;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Generate random URL for simulation
|
|
394
|
+
*/
|
|
395
|
+
generateRandomUrl() {
|
|
396
|
+
const domains = ['github.com', 'api.openai.com', 'stackoverflow.com', 'google.com', 'npmjs.com'];
|
|
397
|
+
const paths = ['api/v1/models', 'search', 'issues', 'releases', 'packages'];
|
|
398
|
+
|
|
399
|
+
const domain = domains[Math.floor(Math.random() * domains.length)];
|
|
400
|
+
const pathPart = paths[Math.floor(Math.random() * paths.length)];
|
|
401
|
+
|
|
402
|
+
return `https://${domain}/${pathPart}`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Update calculated statistics
|
|
407
|
+
*/
|
|
408
|
+
updateStats() {
|
|
409
|
+
const now = Date.now();
|
|
410
|
+
this.stats.uptime = Math.floor((now - this.startTime) / 1000);
|
|
411
|
+
|
|
412
|
+
// Calculate rates
|
|
413
|
+
if (this.stats.uptime > 0) {
|
|
414
|
+
this.stats.scanRate = this.stats.totalScans / this.stats.uptime;
|
|
415
|
+
this.stats.threatRate = this.stats.threatsBlocked / this.stats.uptime;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this.lastUpdate = now;
|
|
419
|
+
|
|
420
|
+
// Simulate active agents count
|
|
421
|
+
this.stats.agentsActive = Math.floor(Math.random() * 3) + 1;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Main display update function
|
|
426
|
+
*/
|
|
427
|
+
updateDisplay() {
|
|
428
|
+
// Clear screen and move to top
|
|
429
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
430
|
+
|
|
431
|
+
const output = this.buildDisplay();
|
|
432
|
+
process.stdout.write(output);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Build the complete display output
|
|
437
|
+
*/
|
|
438
|
+
buildDisplay() {
|
|
439
|
+
const sections = [];
|
|
440
|
+
|
|
441
|
+
sections.push(this.buildHeader());
|
|
442
|
+
sections.push(this.buildStatsOverview());
|
|
443
|
+
sections.push(this.buildThreatMap());
|
|
444
|
+
sections.push(this.buildActivityFeed());
|
|
445
|
+
sections.push(this.buildNetworkGraph());
|
|
446
|
+
sections.push(this.buildFooter());
|
|
447
|
+
|
|
448
|
+
return sections.join('\n');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Build header section
|
|
453
|
+
*/
|
|
454
|
+
buildHeader() {
|
|
455
|
+
const title = `${CHARS.SHIELD} ClawMoat Live Monitor`;
|
|
456
|
+
const uptime = this.formatUptime(this.stats.uptime);
|
|
457
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
458
|
+
|
|
459
|
+
const headerLine = this.createBoxLine(
|
|
460
|
+
`${COLORS.BOLD}${COLORS.CYAN}${title}${COLORS.RESET}`,
|
|
461
|
+
`${COLORS.DIM}Uptime: ${uptime} | ${timestamp}${COLORS.RESET}`,
|
|
462
|
+
this.terminalWidth
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
return `${this.createHorizontalLine('top')}\n${headerLine}\n${this.createHorizontalLine('middle')}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Build statistics overview
|
|
470
|
+
*/
|
|
471
|
+
buildStatsOverview() {
|
|
472
|
+
const stats = [
|
|
473
|
+
{ label: 'Agents Active', value: this.stats.agentsActive, color: COLORS.GREEN },
|
|
474
|
+
{ label: 'Total Scans', value: this.formatNumber(this.stats.totalScans), color: COLORS.BLUE },
|
|
475
|
+
{ label: 'Threats Blocked', value: this.formatNumber(this.stats.threatsBlocked), color: COLORS.RED },
|
|
476
|
+
{ label: 'Files Accessed', value: this.formatNumber(this.stats.filesAccessed), color: COLORS.YELLOW },
|
|
477
|
+
{ label: 'Network Calls', value: this.formatNumber(this.stats.networkCalls), color: COLORS.MAGENTA }
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
const statLines = this.createStatsGrid(stats);
|
|
481
|
+
return statLines.join('\n');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Build threat map visualization
|
|
486
|
+
*/
|
|
487
|
+
buildThreatMap() {
|
|
488
|
+
if (!this.options.showThreatMap) return '';
|
|
489
|
+
|
|
490
|
+
const title = `${COLORS.BOLD}Threat Detection Map${COLORS.RESET}`;
|
|
491
|
+
const recentThreats = this.stats.threatHistory.slice(0, 10);
|
|
492
|
+
|
|
493
|
+
const lines = [this.createSectionHeader(title)];
|
|
494
|
+
|
|
495
|
+
if (recentThreats.length === 0) {
|
|
496
|
+
lines.push(`${CHARS.BOX_VERTICAL} ${COLORS.GREEN}${CHARS.CHECK} No recent threats detected${COLORS.RESET}`);
|
|
497
|
+
} else {
|
|
498
|
+
recentThreats.forEach((threat, i) => {
|
|
499
|
+
const age = this.formatAge(Date.now() - threat.timestamp);
|
|
500
|
+
const severity = this.getSeverityIndicator(threat.severity);
|
|
501
|
+
const line = `${CHARS.BOX_VERTICAL} ${severity} ${threat.type}/${threat.subtype} ${COLORS.DIM}(${age})${COLORS.RESET}`;
|
|
502
|
+
lines.push(line);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return lines.join('\n');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Build activity feed
|
|
511
|
+
*/
|
|
512
|
+
buildActivityFeed() {
|
|
513
|
+
const title = `${COLORS.BOLD}Recent Activity${COLORS.RESET}`;
|
|
514
|
+
const lines = [this.createSectionHeader(title)];
|
|
515
|
+
|
|
516
|
+
const recentActivity = [
|
|
517
|
+
...this.stats.fileAccess.slice(0, 3).map(f => ({ ...f, activityType: 'file' })),
|
|
518
|
+
...this.stats.networkActivity.slice(0, 3).map(n => ({ ...n, activityType: 'network' })),
|
|
519
|
+
...this.stats.threatHistory.slice(0, 2).map(t => ({ ...t, activityType: 'threat' }))
|
|
520
|
+
]
|
|
521
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
522
|
+
.slice(0, 6);
|
|
523
|
+
|
|
524
|
+
if (recentActivity.length === 0) {
|
|
525
|
+
lines.push(`${CHARS.BOX_VERTICAL} ${COLORS.DIM}No recent activity${COLORS.RESET}`);
|
|
526
|
+
} else {
|
|
527
|
+
recentActivity.forEach(activity => {
|
|
528
|
+
const age = this.formatAge(Date.now() - activity.timestamp);
|
|
529
|
+
let line = `${CHARS.BOX_VERTICAL} `;
|
|
530
|
+
|
|
531
|
+
switch (activity.activityType) {
|
|
532
|
+
case 'file':
|
|
533
|
+
line += `${COLORS.BLUE}📁${COLORS.RESET} ${activity.filename} ${COLORS.DIM}(${age})${COLORS.RESET}`;
|
|
534
|
+
break;
|
|
535
|
+
case 'network':
|
|
536
|
+
line += `${COLORS.MAGENTA}🌐${COLORS.RESET} ${activity.url} ${COLORS.DIM}(${age})${COLORS.RESET}`;
|
|
537
|
+
break;
|
|
538
|
+
case 'threat':
|
|
539
|
+
line += `${COLORS.RED}${CHARS.BLOCKED}${COLORS.RESET} ${activity.type} blocked ${COLORS.DIM}(${age})${COLORS.RESET}`;
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
lines.push(line);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return lines.join('\n');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Build network graph
|
|
552
|
+
*/
|
|
553
|
+
buildNetworkGraph() {
|
|
554
|
+
if (!this.options.showNetworkGraph) return '';
|
|
555
|
+
|
|
556
|
+
const title = `${COLORS.BOLD}Network Activity Graph${COLORS.RESET}`;
|
|
557
|
+
const lines = [this.createSectionHeader(title)];
|
|
558
|
+
|
|
559
|
+
// Create a simple bar chart of recent network activity
|
|
560
|
+
const buckets = this.createTimeBuckets(this.stats.networkActivity, 10);
|
|
561
|
+
const maxCount = Math.max(...buckets.map(b => b.count), 1);
|
|
562
|
+
|
|
563
|
+
buckets.forEach(bucket => {
|
|
564
|
+
const barLength = Math.floor((bucket.count / maxCount) * 40);
|
|
565
|
+
const bar = CHARS.BLOCK_FULL.repeat(barLength);
|
|
566
|
+
const line = `${CHARS.BOX_VERTICAL} ${bucket.label} ${COLORS.GREEN}${bar}${COLORS.RESET} ${bucket.count}`;
|
|
567
|
+
lines.push(line);
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
return lines.join('\n');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Build footer
|
|
575
|
+
*/
|
|
576
|
+
buildFooter() {
|
|
577
|
+
const rateInfo = `Scan Rate: ${this.stats.scanRate.toFixed(1)}/s | Threat Rate: ${this.stats.threatRate.toFixed(2)}/s`;
|
|
578
|
+
const controlInfo = "Press 'q' to quit";
|
|
579
|
+
|
|
580
|
+
const footerLine = this.createBoxLine(
|
|
581
|
+
`${COLORS.DIM}${rateInfo}${COLORS.RESET}`,
|
|
582
|
+
`${COLORS.DIM}${controlInfo}${COLORS.RESET}`,
|
|
583
|
+
this.terminalWidth
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
return `${this.createHorizontalLine('middle')}\n${footerLine}\n${this.createHorizontalLine('bottom')}`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Utility functions for display formatting
|
|
591
|
+
*/
|
|
592
|
+
|
|
593
|
+
formatUptime(seconds) {
|
|
594
|
+
const hours = Math.floor(seconds / 3600);
|
|
595
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
596
|
+
const secs = seconds % 60;
|
|
597
|
+
|
|
598
|
+
if (hours > 0) {
|
|
599
|
+
return `${hours}h ${minutes}m ${secs}s`;
|
|
600
|
+
} else if (minutes > 0) {
|
|
601
|
+
return `${minutes}m ${secs}s`;
|
|
602
|
+
} else {
|
|
603
|
+
return `${secs}s`;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
formatNumber(num) {
|
|
608
|
+
if (num >= 1000000) {
|
|
609
|
+
return `${(num / 1000000).toFixed(1)}M`;
|
|
610
|
+
} else if (num >= 1000) {
|
|
611
|
+
return `${(num / 1000).toFixed(1)}K`;
|
|
612
|
+
}
|
|
613
|
+
return num.toString();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
formatAge(milliseconds) {
|
|
617
|
+
const seconds = Math.floor(milliseconds / 1000);
|
|
618
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
619
|
+
const minutes = Math.floor(seconds / 60);
|
|
620
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
621
|
+
const hours = Math.floor(minutes / 60);
|
|
622
|
+
return `${hours}h ago`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
getSeverityIndicator(severity) {
|
|
626
|
+
switch (severity) {
|
|
627
|
+
case 'critical': return `${COLORS.BG_RED}${COLORS.WHITE} CRIT ${COLORS.RESET}`;
|
|
628
|
+
case 'high': return `${COLORS.RED}${CHARS.WARNING}${COLORS.RESET}`;
|
|
629
|
+
case 'medium': return `${COLORS.YELLOW}${CHARS.WARNING}${COLORS.RESET}`;
|
|
630
|
+
case 'low': return `${COLORS.BLUE}ℹ${COLORS.RESET}`;
|
|
631
|
+
default: return `${COLORS.DIM}?${COLORS.RESET}`;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
createHorizontalLine(type) {
|
|
636
|
+
let left, middle, right, fill;
|
|
637
|
+
|
|
638
|
+
switch (type) {
|
|
639
|
+
case 'top':
|
|
640
|
+
left = CHARS.BOX_TOP_LEFT;
|
|
641
|
+
right = CHARS.BOX_TOP_RIGHT;
|
|
642
|
+
fill = CHARS.BOX_HORIZONTAL;
|
|
643
|
+
break;
|
|
644
|
+
case 'bottom':
|
|
645
|
+
left = CHARS.BOX_BOTTOM_LEFT;
|
|
646
|
+
right = CHARS.BOX_BOTTOM_RIGHT;
|
|
647
|
+
fill = CHARS.BOX_HORIZONTAL;
|
|
648
|
+
break;
|
|
649
|
+
case 'middle':
|
|
650
|
+
left = CHARS.BOX_T_RIGHT;
|
|
651
|
+
right = CHARS.BOX_T_LEFT;
|
|
652
|
+
fill = CHARS.BOX_HORIZONTAL;
|
|
653
|
+
break;
|
|
654
|
+
default:
|
|
655
|
+
return '';
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return left + fill.repeat(this.terminalWidth - 2) + right;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
createBoxLine(leftText, rightText, width) {
|
|
662
|
+
const leftClean = this.stripAnsi(leftText);
|
|
663
|
+
const rightClean = this.stripAnsi(rightText);
|
|
664
|
+
const padding = width - leftClean.length - rightClean.length - 2;
|
|
665
|
+
|
|
666
|
+
return `${CHARS.BOX_VERTICAL}${leftText}${' '.repeat(Math.max(0, padding))}${rightText}${CHARS.BOX_VERTICAL}`;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
createSectionHeader(title) {
|
|
670
|
+
const padding = Math.max(0, this.terminalWidth - this.stripAnsi(title).length - 4);
|
|
671
|
+
return `${CHARS.BOX_T_RIGHT}${CHARS.BOX_HORIZONTAL} ${title} ${CHARS.BOX_HORIZONTAL.repeat(padding)}${CHARS.BOX_T_LEFT}`;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
createStatsGrid(stats) {
|
|
675
|
+
const lines = [];
|
|
676
|
+
const itemsPerRow = Math.min(5, Math.floor(this.terminalWidth / 20));
|
|
677
|
+
|
|
678
|
+
for (let i = 0; i < stats.length; i += itemsPerRow) {
|
|
679
|
+
const rowStats = stats.slice(i, i + itemsPerRow);
|
|
680
|
+
const statTexts = rowStats.map(stat =>
|
|
681
|
+
`${stat.color}${stat.value}${COLORS.RESET} ${stat.label}`
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
const line = `${CHARS.BOX_VERTICAL} ${statTexts.join(' | ')}`;
|
|
685
|
+
lines.push(line);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return lines;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
createTimeBuckets(activities, bucketCount) {
|
|
692
|
+
const now = Date.now();
|
|
693
|
+
const bucketSize = 60000; // 1 minute buckets
|
|
694
|
+
const buckets = [];
|
|
695
|
+
|
|
696
|
+
for (let i = bucketCount - 1; i >= 0; i--) {
|
|
697
|
+
const bucketStart = now - (i + 1) * bucketSize;
|
|
698
|
+
const bucketEnd = now - i * bucketSize;
|
|
699
|
+
|
|
700
|
+
const count = activities.filter(a =>
|
|
701
|
+
a.timestamp >= bucketStart && a.timestamp < bucketEnd
|
|
702
|
+
).length;
|
|
703
|
+
|
|
704
|
+
buckets.push({
|
|
705
|
+
label: `${i + 1}m`,
|
|
706
|
+
count,
|
|
707
|
+
start: bucketStart,
|
|
708
|
+
end: bucketEnd
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return buckets;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
stripAnsi(text) {
|
|
716
|
+
return text.replace(/\x1b\[[0-9;]*m/g, '');
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
module.exports = { LiveMonitor };
|