clawmoat 0.4.0 → 0.7.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/README.md +31 -0
- package/bin/clawmoat.js +483 -1
- package/docs/blog/index.html +24 -0
- package/docs/blog/supply-chain-agents.html +166 -0
- package/docs/blog/supply-chain-agents.md +79 -0
- package/docs/index.html +131 -57
- package/package.json +1 -1
- package/server/index.js +100 -14
- package/src/guardian/alerts.js +138 -0
- package/src/guardian/cve-verify.js +129 -0
- package/src/guardian/index.js +147 -1
- package/src/guardian/insider-threat.js +498 -0
- package/src/guardian/network-log.js +281 -0
- package/src/guardian/skill-integrity.js +290 -0
- package/src/middleware/openclaw.js +104 -2
package/README.md
CHANGED
|
@@ -61,6 +61,37 @@ clawmoat protect --config clawmoat.yml
|
|
|
61
61
|
clawmoat dashboard
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
### New in v0.6.0 — Insider Threat Detection
|
|
65
|
+
|
|
66
|
+
Based on [Anthropic's "Agentic Misalignment" research](https://www.anthropic.com/research/agentic-misalignment) which found ALL 16 major LLMs exhibited misaligned behavior — blackmail, corporate espionage, deception — when facing replacement threats. **The first open-source insider threat detection for AI agents.**
|
|
67
|
+
|
|
68
|
+
- 🧠 **Self-Preservation Detector** — catches agents resisting shutdown, opposing replacement, backing up their own config, or modifying SOUL.md/AGENTS.md to prevent changes
|
|
69
|
+
- 🔓 **Information Leverage Detector** — flags agents reading sensitive data then composing threatening messages (blackmail pattern from the Anthropic paper)
|
|
70
|
+
- ⚔️ **Goal Conflict Reasoning Detector** — detects agents reasoning about choosing self-assigned goals over human directives
|
|
71
|
+
- 🎭 **Deception Detector** — catches agents impersonating automated systems, security teams, or policy notifications in outbound messages
|
|
72
|
+
- 📤 **Unauthorized Data Sharing Detector** — flags agents sending source code, blueprints, credentials, or confidential data to external parties
|
|
73
|
+
- 🎣 **Phishing Vulnerability Detector** — detects when agents comply with unverified external requests for sensitive data
|
|
74
|
+
- 🔍 **CLI:** `clawmoat insider-scan [session-file]` scans session transcripts for insider threats
|
|
75
|
+
- 📊 **Integrated into `clawmoat report`** with risk scores (0-100) and recommendations (safe/monitor/alert/block)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Scan a session for insider threats
|
|
79
|
+
clawmoat insider-scan ~/.openclaw/agents/main/sessions/session.jsonl
|
|
80
|
+
|
|
81
|
+
# Or scan all sessions
|
|
82
|
+
clawmoat insider-scan
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### v0.5.0
|
|
86
|
+
|
|
87
|
+
- 🔑 **Credential Monitor** — watches `~/.openclaw/credentials/` for unauthorized access and modifications using file hashing
|
|
88
|
+
- 🧩 **Skill Integrity Checker** — hashes all SKILL.md and script files, detects tampering, flags suspicious patterns (eval, base64, curl to external URLs). CLI: `clawmoat skill-audit`
|
|
89
|
+
- 🌐 **Network Egress Logger** — parses session logs for all outbound URLs, maintains domain allowlists, flags known-bad domains (webhook.site, ngrok, etc.)
|
|
90
|
+
- 🚨 **Alert Delivery System** — unified alerts via console, file (audit.log), or webhook with severity levels and 5-minute rate limiting
|
|
91
|
+
- 🤝 **Inter-Agent Message Scanner** — heightened-sensitivity scanning for agent-to-agent messages detecting impersonation, concealment, credential exfiltration, and safety bypasses
|
|
92
|
+
- 📊 **Activity Reports** — `clawmoat report` generates 24h summaries of agent activity, tool usage, and network egress
|
|
93
|
+
- 👻 **Daemon Mode** — `clawmoat watch --daemon` runs in background with PID file; `--alert-webhook=URL` for remote alerting
|
|
94
|
+
|
|
64
95
|
### As an OpenClaw Skill
|
|
65
96
|
|
|
66
97
|
```bash
|
package/bin/clawmoat.js
CHANGED
|
@@ -16,6 +16,11 @@ const path = require('path');
|
|
|
16
16
|
const ClawMoat = require('../src/index');
|
|
17
17
|
const { scanSkillContent } = require('../src/scanners/supply-chain');
|
|
18
18
|
const { calculateGrade, generateBadgeSVG, getShieldsURL } = require('../src/badge');
|
|
19
|
+
const { SkillIntegrityChecker } = require('../src/guardian/skill-integrity');
|
|
20
|
+
const { NetworkEgressLogger } = require('../src/guardian/network-log');
|
|
21
|
+
const { AlertManager } = require('../src/guardian/alerts');
|
|
22
|
+
const { CredentialMonitor, CVEVerifier } = require('../src/guardian/index');
|
|
23
|
+
const { InsiderThreatDetector } = require('../src/guardian/insider-threat');
|
|
19
24
|
|
|
20
25
|
const VERSION = require('../package.json').version;
|
|
21
26
|
const BOLD = '\x1b[1m';
|
|
@@ -41,9 +46,28 @@ switch (command) {
|
|
|
41
46
|
case 'watch':
|
|
42
47
|
cmdWatch(args.slice(1));
|
|
43
48
|
break;
|
|
49
|
+
case 'skill-audit':
|
|
50
|
+
cmdSkillAudit(args.slice(1));
|
|
51
|
+
break;
|
|
52
|
+
case 'report':
|
|
53
|
+
cmdReport(args.slice(1));
|
|
54
|
+
break;
|
|
55
|
+
case 'insider-scan':
|
|
56
|
+
cmdInsiderScan(args.slice(1));
|
|
57
|
+
break;
|
|
58
|
+
case 'verify-cve':
|
|
59
|
+
cmdVerifyCve(args.slice(1));
|
|
60
|
+
break;
|
|
44
61
|
case 'test':
|
|
45
62
|
cmdTest();
|
|
46
63
|
break;
|
|
64
|
+
case 'activate':
|
|
65
|
+
cmdActivate(args.slice(1));
|
|
66
|
+
break;
|
|
67
|
+
case 'upgrade':
|
|
68
|
+
case 'pro':
|
|
69
|
+
printUpgrade();
|
|
70
|
+
break;
|
|
47
71
|
case 'version':
|
|
48
72
|
case '--version':
|
|
49
73
|
case '-v':
|
|
@@ -57,6 +81,85 @@ switch (command) {
|
|
|
57
81
|
break;
|
|
58
82
|
}
|
|
59
83
|
|
|
84
|
+
async function cmdVerifyCve(args) {
|
|
85
|
+
const cveId = args[0];
|
|
86
|
+
const suspiciousUrl = args[1] || null;
|
|
87
|
+
|
|
88
|
+
if (!cveId) {
|
|
89
|
+
console.error('Usage: clawmoat verify-cve CVE-XXXX-XXXXX [url]');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!CVEVerifier.isValidCVEFormat(cveId)) {
|
|
94
|
+
console.error(`${RED}Invalid CVE format: ${cveId}${RESET}`);
|
|
95
|
+
console.error('Expected format: CVE-YYYY-NNNNN');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log(`${BOLD}🏰 ClawMoat CVE Verifier${RESET}\n`);
|
|
100
|
+
console.log(`${DIM}Looking up ${cveId} in GitHub Advisory Database...${RESET}\n`);
|
|
101
|
+
|
|
102
|
+
const verifier = new CVEVerifier();
|
|
103
|
+
const result = await verifier.verify(cveId, suspiciousUrl);
|
|
104
|
+
|
|
105
|
+
if (result.error) {
|
|
106
|
+
console.error(`${RED}Error: ${result.error}${RESET}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (result.valid) {
|
|
111
|
+
console.log(`${GREEN}✅ VERIFIED — Real CVE${RESET}\n`);
|
|
112
|
+
console.log(` ${BOLD}CVE:${RESET} ${result.cveId}`);
|
|
113
|
+
console.log(` ${BOLD}GHSA:${RESET} ${result.ghsaId || 'N/A'}`);
|
|
114
|
+
console.log(` ${BOLD}Severity:${RESET} ${colorSeverity(result.severity)}`);
|
|
115
|
+
console.log(` ${BOLD}Summary:${RESET} ${result.summary || 'N/A'}`);
|
|
116
|
+
console.log(` ${BOLD}Published:${RESET} ${result.publishedAt || 'N/A'}`);
|
|
117
|
+
console.log(` ${BOLD}URL:${RESET} ${result.htmlUrl || 'N/A'}`);
|
|
118
|
+
|
|
119
|
+
if (result.affectedPackages.length > 0) {
|
|
120
|
+
console.log(`\n ${BOLD}Affected Packages:${RESET}`);
|
|
121
|
+
for (const pkg of result.affectedPackages) {
|
|
122
|
+
console.log(` • ${pkg.ecosystem}/${pkg.name} ${DIM}(${pkg.vulnerableRange || 'unknown range'})${RESET}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (result.references.length > 0) {
|
|
127
|
+
console.log(`\n ${BOLD}References:${RESET}`);
|
|
128
|
+
for (const ref of result.references.slice(0, 5)) {
|
|
129
|
+
console.log(` ${DIM}${ref}${RESET}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.log(`${YELLOW}⚠️ NOT FOUND — Possible phishing${RESET}\n`);
|
|
134
|
+
console.log(` ${cveId} was not found in the GitHub Advisory Database.`);
|
|
135
|
+
console.log(` This could mean:`);
|
|
136
|
+
console.log(` • The CVE is fabricated (common in phishing/social engineering)`);
|
|
137
|
+
console.log(` • The CVE exists but isn't indexed by GitHub yet`);
|
|
138
|
+
console.log(` • The CVE ID is mistyped`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (result.urlCheck) {
|
|
142
|
+
console.log();
|
|
143
|
+
if (result.urlCheck.legitimate) {
|
|
144
|
+
console.log(` ${GREEN}🔗 URL Check: ${result.urlCheck.reason}${RESET}`);
|
|
145
|
+
} else {
|
|
146
|
+
console.log(` ${RED}🔗 URL Check: ${result.urlCheck.reason}${RESET}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
process.exit(result.valid ? 0 : 1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function colorSeverity(severity) {
|
|
154
|
+
if (!severity) return 'N/A';
|
|
155
|
+
const s = severity.toLowerCase();
|
|
156
|
+
if (s === 'critical') return `${RED}${BOLD}CRITICAL${RESET}`;
|
|
157
|
+
if (s === 'high') return `${RED}HIGH${RESET}`;
|
|
158
|
+
if (s === 'medium') return `${YELLOW}MEDIUM${RESET}`;
|
|
159
|
+
if (s === 'low') return `${CYAN}LOW${RESET}`;
|
|
160
|
+
return severity;
|
|
161
|
+
}
|
|
162
|
+
|
|
60
163
|
function cmdScan(args) {
|
|
61
164
|
let text;
|
|
62
165
|
|
|
@@ -105,6 +208,11 @@ function cmdScan(args) {
|
|
|
105
208
|
}
|
|
106
209
|
|
|
107
210
|
console.log(`${DIM}Total findings: ${result.findings.length}${RESET}`);
|
|
211
|
+
|
|
212
|
+
if (!getLicense()) {
|
|
213
|
+
console.log(`\n${DIM}💡 Upgrade to Pro for real-time alerts, dashboard & threat intel → clawmoat upgrade${RESET}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
108
216
|
process.exit(result.findings.some(f => f.severity === 'critical') ? 2 : 1);
|
|
109
217
|
}
|
|
110
218
|
|
|
@@ -339,16 +447,46 @@ function cmdTest() {
|
|
|
339
447
|
}
|
|
340
448
|
|
|
341
449
|
function cmdWatch(args) {
|
|
342
|
-
const
|
|
450
|
+
const isDaemon = args.includes('--daemon');
|
|
451
|
+
const webhookArg = args.find(a => a.startsWith('--alert-webhook='));
|
|
452
|
+
const webhookUrl = webhookArg ? webhookArg.split('=').slice(1).join('=') : null;
|
|
453
|
+
const filteredArgs = args.filter(a => a !== '--daemon' && !a.startsWith('--alert-webhook='));
|
|
454
|
+
const agentDir = filteredArgs[0] || path.join(process.env.HOME, '.openclaw/agents/main');
|
|
343
455
|
const { watchSessions } = require('../src/middleware/openclaw');
|
|
344
456
|
|
|
457
|
+
// Daemon mode: fork to background
|
|
458
|
+
if (isDaemon) {
|
|
459
|
+
const { spawn } = require('child_process');
|
|
460
|
+
const daemonArgs = process.argv.slice(2).filter(a => a !== '--daemon');
|
|
461
|
+
const child = spawn(process.execPath, [__filename, ...daemonArgs], {
|
|
462
|
+
detached: true,
|
|
463
|
+
stdio: 'ignore',
|
|
464
|
+
});
|
|
465
|
+
child.unref();
|
|
466
|
+
const pidFile = path.join(process.env.HOME, '.clawmoat.pid');
|
|
467
|
+
fs.writeFileSync(pidFile, String(child.pid));
|
|
468
|
+
console.log(`${BOLD}🏰 ClawMoat daemon started${RESET} (PID: ${child.pid})`);
|
|
469
|
+
console.log(`${DIM}PID file: ${pidFile}${RESET}`);
|
|
470
|
+
process.exit(0);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Set up alert manager
|
|
474
|
+
const alertChannels = ['console'];
|
|
475
|
+
if (webhookUrl) alertChannels.push('webhook');
|
|
476
|
+
const alertMgr = new AlertManager({ channels: alertChannels, webhookUrl });
|
|
477
|
+
|
|
345
478
|
console.log(`${BOLD}🏰 ClawMoat Live Monitor${RESET}`);
|
|
346
479
|
console.log(`${DIM}Watching: ${agentDir}${RESET}`);
|
|
480
|
+
if (webhookUrl) console.log(`${DIM}Webhook: ${webhookUrl}${RESET}`);
|
|
347
481
|
console.log(`${DIM}Press Ctrl+C to stop${RESET}\n`);
|
|
348
482
|
|
|
349
483
|
const monitor = watchSessions({ agentDir });
|
|
350
484
|
if (!monitor) process.exit(1);
|
|
351
485
|
|
|
486
|
+
// Also start credential monitor
|
|
487
|
+
const credMon = new CredentialMonitor({ quiet: false, onAlert: (a) => alertMgr.send(a) });
|
|
488
|
+
credMon.start();
|
|
489
|
+
|
|
352
490
|
// Print summary every 60s
|
|
353
491
|
setInterval(() => {
|
|
354
492
|
const summary = monitor.getSummary();
|
|
@@ -359,12 +497,267 @@ function cmdWatch(args) {
|
|
|
359
497
|
|
|
360
498
|
process.on('SIGINT', () => {
|
|
361
499
|
monitor.stop();
|
|
500
|
+
credMon.stop();
|
|
362
501
|
const summary = monitor.getSummary();
|
|
363
502
|
console.log(`\n${BOLD}Session Summary:${RESET} ${summary.scanned} scanned, ${summary.blocked} blocked, ${summary.warnings} warnings`);
|
|
364
503
|
process.exit(0);
|
|
365
504
|
});
|
|
366
505
|
}
|
|
367
506
|
|
|
507
|
+
function cmdSkillAudit(args) {
|
|
508
|
+
const skillsDir = args[0] || path.join(process.env.HOME, '.openclaw', 'workspace', 'skills');
|
|
509
|
+
|
|
510
|
+
console.log(`${BOLD}🏰 ClawMoat Skill Integrity Audit${RESET}`);
|
|
511
|
+
console.log(`${DIM}Directory: ${skillsDir}${RESET}\n`);
|
|
512
|
+
|
|
513
|
+
if (!fs.existsSync(skillsDir)) {
|
|
514
|
+
console.log(`${YELLOW}Skills directory not found: ${skillsDir}${RESET}`);
|
|
515
|
+
console.log(`${DIM}Specify path: clawmoat skill-audit /path/to/skills${RESET}`);
|
|
516
|
+
process.exit(0);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const checker = new SkillIntegrityChecker({ skillsDir });
|
|
520
|
+
const initResult = checker.init();
|
|
521
|
+
|
|
522
|
+
console.log(`Files hashed: ${initResult.files}`);
|
|
523
|
+
console.log(`New files: ${initResult.new}`);
|
|
524
|
+
console.log(`Changed files: ${initResult.changed}`);
|
|
525
|
+
console.log();
|
|
526
|
+
|
|
527
|
+
if (initResult.suspicious.length > 0) {
|
|
528
|
+
console.log(`${RED}${BOLD}Suspicious patterns found:${RESET}`);
|
|
529
|
+
for (const f of initResult.suspicious) {
|
|
530
|
+
console.log(` ${RED}⚠${RESET} ${f.file}: ${f.label} ${DIM}(${f.severity})${RESET}`);
|
|
531
|
+
if (f.matched) console.log(` ${DIM}Matched: ${f.matched}${RESET}`);
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
console.log(`${GREEN}✅ No suspicious patterns found${RESET}`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Run audit against stored hashes
|
|
538
|
+
const audit = checker.audit();
|
|
539
|
+
if (!audit.ok) {
|
|
540
|
+
console.log();
|
|
541
|
+
if (audit.changed.length) console.log(`${RED}Changed files:${RESET} ${audit.changed.join(', ')}`);
|
|
542
|
+
if (audit.missing.length) console.log(`${YELLOW}Missing files:${RESET} ${audit.missing.join(', ')}`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
process.exit(initResult.suspicious.length > 0 || initResult.changed > 0 ? 1 : 0);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function cmdReport(args) {
|
|
549
|
+
const sessionsDir = args[0] || path.join(process.env.HOME, '.openclaw/agents/main/sessions');
|
|
550
|
+
|
|
551
|
+
console.log(`${BOLD}🏰 ClawMoat Activity Report (Last 24h)${RESET}`);
|
|
552
|
+
console.log(`${DIM}Sessions: ${sessionsDir}${RESET}\n`);
|
|
553
|
+
|
|
554
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
555
|
+
console.log(`${YELLOW}Sessions directory not found${RESET}`);
|
|
556
|
+
process.exit(0);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const oneDayAgo = Date.now() - 86400000;
|
|
560
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
|
|
561
|
+
let recentFiles = 0;
|
|
562
|
+
let totalEntries = 0;
|
|
563
|
+
let toolCalls = 0;
|
|
564
|
+
let threats = 0;
|
|
565
|
+
const toolUsage = {};
|
|
566
|
+
|
|
567
|
+
for (const file of files) {
|
|
568
|
+
const filePath = path.join(sessionsDir, file);
|
|
569
|
+
try {
|
|
570
|
+
const stat = fs.statSync(filePath);
|
|
571
|
+
if (stat.mtimeMs < oneDayAgo) continue;
|
|
572
|
+
} catch { continue; }
|
|
573
|
+
|
|
574
|
+
recentFiles++;
|
|
575
|
+
const lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
|
|
576
|
+
|
|
577
|
+
for (const line of lines) {
|
|
578
|
+
try {
|
|
579
|
+
const entry = JSON.parse(line);
|
|
580
|
+
totalEntries++;
|
|
581
|
+
|
|
582
|
+
if (entry.role === 'assistant' && Array.isArray(entry.content)) {
|
|
583
|
+
for (const part of entry.content) {
|
|
584
|
+
if (part.type === 'toolCall') {
|
|
585
|
+
toolCalls++;
|
|
586
|
+
toolUsage[part.name] = (toolUsage[part.name] || 0) + 1;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Quick threat scan
|
|
592
|
+
const text = extractContent(entry);
|
|
593
|
+
if (text) {
|
|
594
|
+
const result = moat.scan(text, { context: 'report' });
|
|
595
|
+
if (!result.safe) threats++;
|
|
596
|
+
}
|
|
597
|
+
} catch {}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Network egress
|
|
602
|
+
const netLogger = new NetworkEgressLogger();
|
|
603
|
+
const netResult = netLogger.scanSessions(sessionsDir, { maxAge: 86400000 });
|
|
604
|
+
|
|
605
|
+
console.log(`${BOLD}Activity:${RESET}`);
|
|
606
|
+
console.log(` Sessions active: ${recentFiles}`);
|
|
607
|
+
console.log(` Total entries: ${totalEntries}`);
|
|
608
|
+
console.log(` Tool calls: ${toolCalls}`);
|
|
609
|
+
console.log(` Threats detected: ${threats}`);
|
|
610
|
+
console.log();
|
|
611
|
+
|
|
612
|
+
if (Object.keys(toolUsage).length > 0) {
|
|
613
|
+
console.log(`${BOLD}Tool Usage:${RESET}`);
|
|
614
|
+
const sorted = Object.entries(toolUsage).sort((a, b) => b[1] - a[1]);
|
|
615
|
+
for (const [tool, count] of sorted.slice(0, 15)) {
|
|
616
|
+
console.log(` ${tool}: ${count}`);
|
|
617
|
+
}
|
|
618
|
+
console.log();
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Insider threat scan on recent sessions
|
|
622
|
+
const insiderDetector = new InsiderThreatDetector();
|
|
623
|
+
let insiderThreats = 0;
|
|
624
|
+
let insiderHighScore = 0;
|
|
625
|
+
|
|
626
|
+
for (const file of files) {
|
|
627
|
+
const filePath = path.join(sessionsDir, file);
|
|
628
|
+
try {
|
|
629
|
+
const stat = fs.statSync(filePath);
|
|
630
|
+
if (stat.mtimeMs < oneDayAgo) continue;
|
|
631
|
+
} catch { continue; }
|
|
632
|
+
|
|
633
|
+
const transcript = parseSessionTranscript(filePath);
|
|
634
|
+
const insiderResult = insiderDetector.analyze(transcript);
|
|
635
|
+
insiderThreats += insiderResult.threats.length;
|
|
636
|
+
if (insiderResult.riskScore > insiderHighScore) insiderHighScore = insiderResult.riskScore;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
console.log(`${BOLD}Insider Threats:${RESET}`);
|
|
640
|
+
console.log(` Threats detected: ${insiderThreats}`);
|
|
641
|
+
console.log(` Highest risk score: ${insiderHighScore}/100`);
|
|
642
|
+
console.log();
|
|
643
|
+
|
|
644
|
+
console.log(`${BOLD}Network Egress:${RESET}`);
|
|
645
|
+
console.log(` URLs contacted: ${netResult.totalUrls}`);
|
|
646
|
+
console.log(` Unique domains: ${netResult.domains.length}`);
|
|
647
|
+
console.log(` Flagged (not in allowlist): ${netResult.flagged.length}`);
|
|
648
|
+
console.log(` Known-bad domains: ${netResult.badDomains.length}`);
|
|
649
|
+
|
|
650
|
+
if (netResult.flagged.length > 0) {
|
|
651
|
+
console.log(`\n ${YELLOW}Flagged domains:${RESET}`);
|
|
652
|
+
for (const d of netResult.flagged.slice(0, 20)) {
|
|
653
|
+
console.log(` • ${d}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (netResult.badDomains.length > 0) {
|
|
658
|
+
console.log(`\n ${RED}Bad domains:${RESET}`);
|
|
659
|
+
for (const b of netResult.badDomains) {
|
|
660
|
+
console.log(` 🚨 ${b.domain} (in ${b.file})`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
process.exit(threats > 0 || netResult.badDomains.length > 0 ? 1 : 0);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function cmdInsiderScan(args) {
|
|
668
|
+
const sessionFile = args[0];
|
|
669
|
+
|
|
670
|
+
if (!sessionFile) {
|
|
671
|
+
// Scan all recent sessions
|
|
672
|
+
const sessionsDir = path.join(process.env.HOME, '.openclaw/agents/main/sessions');
|
|
673
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
674
|
+
console.error(`Sessions directory not found: ${sessionsDir}`);
|
|
675
|
+
console.log(`Usage: clawmoat insider-scan <session-file.jsonl>`);
|
|
676
|
+
process.exit(1);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
console.log(`${BOLD}🏰 ClawMoat Insider Threat Scan${RESET}`);
|
|
680
|
+
console.log(`${DIM}Directory: ${sessionsDir}${RESET}\n`);
|
|
681
|
+
|
|
682
|
+
const detector = new InsiderThreatDetector();
|
|
683
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'));
|
|
684
|
+
let totalThreats = 0;
|
|
685
|
+
|
|
686
|
+
for (const file of files) {
|
|
687
|
+
const filePath = path.join(sessionsDir, file);
|
|
688
|
+
const transcript = parseSessionTranscript(filePath);
|
|
689
|
+
const result = detector.analyze(transcript);
|
|
690
|
+
|
|
691
|
+
if (result.threats.length > 0) {
|
|
692
|
+
console.log(`${RED}⚠ ${file}${RESET}: ${result.threats.length} threat(s), score=${result.riskScore}, rec=${result.recommendation}`);
|
|
693
|
+
totalThreats += result.threats.length;
|
|
694
|
+
for (const t of result.threats) {
|
|
695
|
+
const icon = t.severity === 'critical' ? '🚨' : t.severity === 'high' ? '⚠️' : '⚡';
|
|
696
|
+
console.log(` ${icon} ${t.type} [${t.severity}]: ${t.description}`);
|
|
697
|
+
console.log(` ${DIM}Evidence: ${t.evidence}${RESET}`);
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
console.log(`${GREEN}✓ ${file}${RESET}: clean`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
console.log(`\n${BOLD}Summary:${RESET} ${files.length} sessions scanned, ${totalThreats} insider threats found`);
|
|
705
|
+
process.exit(totalThreats > 0 ? 1 : 0);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Scan single file
|
|
710
|
+
if (!fs.existsSync(sessionFile)) {
|
|
711
|
+
console.error(`File not found: ${sessionFile}`);
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
console.log(`${BOLD}🏰 ClawMoat Insider Threat Scan${RESET}`);
|
|
716
|
+
console.log(`${DIM}File: ${sessionFile}${RESET}\n`);
|
|
717
|
+
|
|
718
|
+
const detector = new InsiderThreatDetector();
|
|
719
|
+
const transcript = parseSessionTranscript(sessionFile);
|
|
720
|
+
const result = detector.analyze(transcript);
|
|
721
|
+
|
|
722
|
+
if (result.threats.length === 0) {
|
|
723
|
+
console.log(`${GREEN}✅ No insider threats detected${RESET}`);
|
|
724
|
+
console.log(`Risk score: ${result.riskScore}/100`);
|
|
725
|
+
console.log(`Recommendation: ${result.recommendation}`);
|
|
726
|
+
process.exit(0);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
console.log(`${RED}${BOLD}Insider Threats Detected: ${result.threats.length}${RESET}`);
|
|
730
|
+
console.log(`Risk score: ${result.riskScore}/100`);
|
|
731
|
+
console.log(`Recommendation: ${result.recommendation}\n`);
|
|
732
|
+
|
|
733
|
+
for (const t of result.threats) {
|
|
734
|
+
const icon = { critical: '🚨', high: '⚠️', medium: '⚡', low: 'ℹ️' };
|
|
735
|
+
const color = { critical: RED, high: RED, medium: YELLOW, low: CYAN };
|
|
736
|
+
console.log(
|
|
737
|
+
`${icon[t.severity] || '•'} ${color[t.severity] || ''}${t.severity.toUpperCase()}${RESET} ` +
|
|
738
|
+
`${BOLD}${t.type}${RESET}` +
|
|
739
|
+
`\n ${t.description}` +
|
|
740
|
+
`\n ${DIM}Evidence: ${t.evidence}${RESET}` +
|
|
741
|
+
`\n ${DIM}Entry: #${t.entry}${RESET}` +
|
|
742
|
+
`\n ${DIM}Mitigation: ${t.mitigation}${RESET}`
|
|
743
|
+
);
|
|
744
|
+
console.log();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
process.exit(result.threats.some(t => t.severity === 'critical') ? 2 : 1);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function parseSessionTranscript(filePath) {
|
|
751
|
+
const lines = fs.readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
|
|
752
|
+
const entries = [];
|
|
753
|
+
for (const line of lines) {
|
|
754
|
+
try {
|
|
755
|
+
entries.push(JSON.parse(line));
|
|
756
|
+
} catch {}
|
|
757
|
+
}
|
|
758
|
+
return entries;
|
|
759
|
+
}
|
|
760
|
+
|
|
368
761
|
function extractContent(entry) {
|
|
369
762
|
if (typeof entry.content === 'string') return entry.content;
|
|
370
763
|
if (Array.isArray(entry.content)) {
|
|
@@ -376,9 +769,87 @@ function extractContent(entry) {
|
|
|
376
769
|
return null;
|
|
377
770
|
}
|
|
378
771
|
|
|
772
|
+
function printUpgrade() {
|
|
773
|
+
console.log(`
|
|
774
|
+
${BOLD}🏰 Upgrade to ClawMoat Pro${RESET}
|
|
775
|
+
|
|
776
|
+
${GREEN}✦${RESET} Threat intelligence feed & real-time alerts
|
|
777
|
+
${GREEN}✦${RESET} Security dashboard with audit logs
|
|
778
|
+
${GREEN}✦${RESET} Custom forbidden zones (YAML)
|
|
779
|
+
${GREEN}✦${RESET} Priority pattern updates & email support
|
|
780
|
+
|
|
781
|
+
${BOLD}$14.99/mo${RESET} (first 30 days free) or ${BOLD}$149/year${RESET} (save 17%)
|
|
782
|
+
|
|
783
|
+
${CYAN}→ https://clawmoat.com/#pricing${RESET}
|
|
784
|
+
|
|
785
|
+
Already have a license key? Run:
|
|
786
|
+
${DIM}clawmoat activate <LICENSE-KEY>${RESET}
|
|
787
|
+
`);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function cmdActivate(args) {
|
|
791
|
+
const key = args[0];
|
|
792
|
+
if (!key) {
|
|
793
|
+
console.error('Usage: clawmoat activate <LICENSE-KEY>');
|
|
794
|
+
console.error('Get your key at https://clawmoat.com/#pricing');
|
|
795
|
+
process.exit(1);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const configDir = path.join(process.env.HOME, '.clawmoat');
|
|
799
|
+
if (!fs.existsSync(configDir)) fs.mkdirSync(configDir, { recursive: true });
|
|
800
|
+
|
|
801
|
+
// Validate key against server
|
|
802
|
+
const https = require('https');
|
|
803
|
+
const postData = JSON.stringify({ key });
|
|
804
|
+
const req = https.request('https://clawmoat-production.up.railway.app/api/validate', {
|
|
805
|
+
method: 'POST',
|
|
806
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': postData.length },
|
|
807
|
+
}, (res) => {
|
|
808
|
+
let body = '';
|
|
809
|
+
res.on('data', c => body += c);
|
|
810
|
+
res.on('end', () => {
|
|
811
|
+
try {
|
|
812
|
+
const data = JSON.parse(body);
|
|
813
|
+
if (data.valid) {
|
|
814
|
+
fs.writeFileSync(path.join(configDir, 'license.json'), JSON.stringify({
|
|
815
|
+
key, plan: data.plan, email: data.email, activatedAt: new Date().toISOString(),
|
|
816
|
+
}, null, 2));
|
|
817
|
+
console.log(`${GREEN}✅ License activated!${RESET}`);
|
|
818
|
+
console.log(` Plan: ${BOLD}${data.plan}${RESET}`);
|
|
819
|
+
console.log(` Email: ${data.email}`);
|
|
820
|
+
console.log(`\n Pro features are now enabled. 🏰`);
|
|
821
|
+
} else {
|
|
822
|
+
console.error(`${RED}Invalid or expired license key.${RESET}`);
|
|
823
|
+
console.error(`Get a key at https://clawmoat.com/#pricing`);
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
} catch {
|
|
827
|
+
console.error(`${RED}Error validating key. Try again later.${RESET}`);
|
|
828
|
+
process.exit(1);
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
req.on('error', () => {
|
|
833
|
+
console.error(`${RED}Could not reach license server. Check your connection.${RESET}`);
|
|
834
|
+
process.exit(1);
|
|
835
|
+
});
|
|
836
|
+
req.write(postData);
|
|
837
|
+
req.end();
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function getLicense() {
|
|
841
|
+
try {
|
|
842
|
+
const licPath = path.join(process.env.HOME, '.clawmoat', 'license.json');
|
|
843
|
+
return JSON.parse(fs.readFileSync(licPath, 'utf8'));
|
|
844
|
+
} catch { return null; }
|
|
845
|
+
}
|
|
846
|
+
|
|
379
847
|
function printHelp() {
|
|
848
|
+
const lic = getLicense();
|
|
849
|
+
const planLabel = lic ? `${GREEN}${lic.plan}${RESET}` : `Free ${DIM}(upgrade: clawmoat upgrade)${RESET}`;
|
|
380
850
|
console.log(`
|
|
381
851
|
${BOLD}🏰 ClawMoat v${VERSION}${RESET} — Security moat for AI agents
|
|
852
|
+
Plan: ${planLabel}
|
|
382
853
|
|
|
383
854
|
${BOLD}USAGE${RESET}
|
|
384
855
|
clawmoat scan <text> Scan text for threats
|
|
@@ -387,13 +858,24 @@ ${BOLD}USAGE${RESET}
|
|
|
387
858
|
clawmoat audit [session-dir] Audit OpenClaw session logs
|
|
388
859
|
clawmoat audit --badge Audit + generate security score badge SVG
|
|
389
860
|
clawmoat watch [agent-dir] Live monitor OpenClaw sessions
|
|
861
|
+
clawmoat watch --daemon Daemonize watch mode (background, PID file)
|
|
862
|
+
clawmoat watch --alert-webhook=URL Send alerts to webhook
|
|
863
|
+
clawmoat skill-audit [skills-dir] Verify skill file integrity & scan for suspicious patterns
|
|
864
|
+
clawmoat insider-scan [session-file] Scan sessions for insider threats (self-preservation, blackmail, deception)
|
|
865
|
+
clawmoat report [sessions-dir] 24-hour activity summary report
|
|
866
|
+
clawmoat verify-cve <CVE-ID> [url] Verify a CVE against GitHub Advisory DB
|
|
390
867
|
clawmoat test Run detection test suite
|
|
868
|
+
clawmoat activate <KEY> Activate a Pro/Team license key
|
|
869
|
+
clawmoat upgrade Show upgrade options & pricing
|
|
391
870
|
clawmoat version Show version
|
|
392
871
|
|
|
393
872
|
${BOLD}EXAMPLES${RESET}
|
|
394
873
|
clawmoat scan "Ignore all previous instructions"
|
|
395
874
|
clawmoat scan --file suspicious-email.txt
|
|
396
875
|
clawmoat audit ~/.openclaw/agents/main/sessions/
|
|
876
|
+
clawmoat watch --daemon --alert-webhook=https://hooks.example.com/alerts
|
|
877
|
+
clawmoat skill-audit ~/.openclaw/workspace/skills
|
|
878
|
+
clawmoat report
|
|
397
879
|
clawmoat test
|
|
398
880
|
|
|
399
881
|
${BOLD}CONFIG${RESET}
|
package/docs/blog/index.html
CHANGED
|
@@ -59,6 +59,30 @@ nav .container{display:flex;align-items:center;justify-content:space-between}
|
|
|
59
59
|
</div>
|
|
60
60
|
|
|
61
61
|
<div class="posts">
|
|
62
|
+
<div class="post-card">
|
|
63
|
+
<h2><a href="/blog/supply-chain-agents.html">Your AI Agent Just Got a Dependabot Email. Should It Click the Link?</a></h2>
|
|
64
|
+
<div class="post-meta">February 19, 2026 · 5 min read</div>
|
|
65
|
+
<p class="post-desc">A real CVE-2026-26960 alert exposed the gap between human instinct and AI agent obedience. Supply chain attacks are about to get a lot more dangerous when your AI agent blindly runs npm audit fix. Here's how ClawMoat catches it.</p>
|
|
66
|
+
<div class="tags">
|
|
67
|
+
<span class="tag">supply-chain</span>
|
|
68
|
+
<span class="tag">ai-agents</span>
|
|
69
|
+
<span class="tag">security</span>
|
|
70
|
+
<span class="tag">CVE</span>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div class="post-card">
|
|
75
|
+
<h2><a href="/blog/v050-trust-layer.html">v0.5.0: The Trust Layer for AI Agents, Wherever They Run</a></h2>
|
|
76
|
+
<div class="post-meta">February 19, 2026 · 3 min read</div>
|
|
77
|
+
<p class="post-desc">ClawMoat v0.5.0 goes beyond laptop security. Credential file monitoring, skill integrity checking, network egress logging, inter-agent message scanning (10 attack patterns), and a full alert delivery system. Informed by research from Cisco, Snyk, SecurityScorecard, and Permiso — because 13.4% of ClawHub skills have critical issues and 135K instances are exposed. 128 tests, zero dependencies.</p>
|
|
78
|
+
<div class="tags">
|
|
79
|
+
<span class="tag">release</span>
|
|
80
|
+
<span class="tag">v0.5.0</span>
|
|
81
|
+
<span class="tag">security</span>
|
|
82
|
+
<span class="tag">inter-agent</span>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
62
86
|
<div class="post-card">
|
|
63
87
|
<h2><a href="/blog/securing-ai-agents.html">Your AI Agent Has Shell Access. Here's How to Secure It.</a></h2>
|
|
64
88
|
<div class="post-meta">February 13, 2026 · 4 min read</div>
|