clawarmor 2.0.0-alpha.2 โ 2.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/README.md +50 -146
- package/cli.js +23 -1
- package/lib/digest.js +157 -0
- package/lib/harden.js +312 -0
- package/lib/output/progress.js +2 -1
- package/lib/prescan.js +166 -70
- package/lib/protect.js +90 -15
- package/lib/status.js +273 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,163 +1,67 @@
|
|
|
1
|
-
|
|
1
|
+
# ClawArmor
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
**The security auditor for OpenClaw agents.**
|
|
6
|
-
|
|
7
|
-
Checks your config. Probes your live gateway. Scans your skills.
|
|
8
|
-
Runs in 30 seconds. Finds what config-only tools miss. Free forever.
|
|
3
|
+
Security armor for OpenClaw agents โ audit, scan, monitor.
|
|
9
4
|
|
|
10
5
|
[](https://www.npmjs.com/package/clawarmor)
|
|
11
6
|
[](LICENSE)
|
|
12
|
-
[](package.json)
|
|
49
8
|
|
|
50
|
-
##
|
|
9
|
+
## What it does
|
|
51
10
|
|
|
52
|
-
|
|
11
|
+
- Audits your OpenClaw config and live gateway with 30+ checks โ scored 0โ100
|
|
12
|
+
- Scans every installed skill file for malicious code and prompt injection patterns
|
|
13
|
+
- Guards every install: intercepts `openclaw clawhub install`, pre-scans before activation
|
|
53
14
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Config says `bind: loopback`. Is your gateway *actually* unreachable on LAN? Config says auth is enabled. Does the live WebSocket endpoint *actually* reject unauthenticated connections? A misconfigured nginx in front can make your config lie. Live probes can't be faked.
|
|
57
|
-
|
|
58
|
-
> All probes connect from your machine to `127.0.0.1` (and your local network interfaces). Nothing leaves your machine.
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Five commands
|
|
15
|
+
## Quick start
|
|
63
16
|
|
|
64
17
|
```bash
|
|
65
|
-
|
|
66
|
-
clawarmor
|
|
67
|
-
clawarmor
|
|
68
|
-
clawarmor verify # Re-run only previously-failed checks. Exit 0 if all fixed (CI-friendly).
|
|
69
|
-
clawarmor trend # ASCII chart of your security score over time.
|
|
18
|
+
npm install -g clawarmor
|
|
19
|
+
clawarmor protect --install
|
|
20
|
+
clawarmor audit
|
|
70
21
|
```
|
|
71
22
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## What it checks
|
|
75
|
-
|
|
76
|
-
### Live gateway probes (behavioral โ not just config reads)
|
|
23
|
+
## Commands
|
|
77
24
|
|
|
78
|
-
|
|
|
25
|
+
| Command | Description |
|
|
79
26
|
|---|---|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## What it protects against
|
|
104
|
-
|
|
105
|
-
| Threat | Covered | Notes |
|
|
27
|
+
| `audit` | Score your OpenClaw config (0โ100), live gateway probes, plain-English verdict |
|
|
28
|
+
| `scan` | Scan all installed skill files for malicious code and SKILL.md instructions |
|
|
29
|
+
| `prescan <skill>` | Pre-scan a skill before installing โ blocks on CRITICAL findings |
|
|
30
|
+
| `protect --install` | Install guard hook, shell intercept (zsh/bash/fish), and watch daemon |
|
|
31
|
+
| `protect --uninstall` | Remove all ClawArmor protection components |
|
|
32
|
+
| `protect --status` | Show current protection state |
|
|
33
|
+
| `watch` | Monitor config and skill changes in real time |
|
|
34
|
+
| `watch --daemon` | Start the watcher as a background daemon |
|
|
35
|
+
| `harden` | Interactive hardening wizard (--dry-run, --auto) |
|
|
36
|
+
| `status` | One-screen security posture dashboard |
|
|
37
|
+
| `log` | View the audit event log |
|
|
38
|
+
| `digest` | Show weekly security digest |
|
|
39
|
+
| `verify` | Re-run only previously-failed checks (CI-friendly, exit 0 = all fixed) |
|
|
40
|
+
| `trend` | ASCII chart of your security score over time |
|
|
41
|
+
| `compare` | Compare coverage vs openclaw security audit |
|
|
42
|
+
| `fix` | Auto-apply safe fixes (--dry-run to preview, --apply to run) |
|
|
43
|
+
|
|
44
|
+
## What it catches
|
|
45
|
+
|
|
46
|
+
| Threat | Description | Coverage |
|
|
106
47
|
|---|---|---|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Sandbox isolation is enabled safely: if Docker is installed, `fix --apply` sets `sandbox.mode=non-main` + `workspaceAccess=rw` so your Telegram/group sessions keep workspace access.
|
|
124
|
-
|
|
125
|
-
---
|
|
126
|
-
|
|
127
|
-
## CI integration
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
# Fail CI if security score drops
|
|
131
|
-
clawarmor verify # exit 0 = all previously-failed checks now pass
|
|
132
|
-
# exit 1 = still failing
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Score history persists in `~/.clawarmor/history.json`.
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## Privacy & security
|
|
140
|
-
|
|
141
|
-
- `audit`, `scan`, `fix`, `verify`, `trend` run **entirely locally**
|
|
142
|
-
- One optional network call: `registry.npmjs.org` for version check (skippable with `--offline`)
|
|
143
|
-
- Every run prints exactly what files it reads and what network calls it makes before executing
|
|
144
|
-
- Nothing is sent anywhere
|
|
145
|
-
|
|
146
|
-
**Found a vulnerability in ClawArmor itself?** Please email `pinzasrojas@proton.me` before public disclosure.
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Installation
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
npm install -g clawarmor # requires Node.js 18+
|
|
154
|
-
clawarmor audit
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Zero runtime npm dependencies. Node.js built-ins only (`net`, `http`, `os`, `fs`, `crypto`).
|
|
158
|
-
|
|
159
|
-
---
|
|
48
|
+
| Token/config exposure | File permission checks, config hardening | Full |
|
|
49
|
+
| Malicious skill supply chain | All skill files scanned โ not just SKILL.md | Full |
|
|
50
|
+
| Credential hygiene | Token age, rotation reminders, access scope | Full |
|
|
51
|
+
| Config drift | Baseline hashing, change detection on every startup | Full |
|
|
52
|
+
| Obfuscation | Base64 blobs, dynamic eval, encoded payloads | Partial |
|
|
53
|
+
| Prompt injection via SKILL.md | Instruction patterns, exfil, deception, system overrides | Full |
|
|
54
|
+
| Live gateway auth | WebSocket probe โ does server actually reject unauthenticated connections? | Full |
|
|
55
|
+
| CORS misconfiguration | OPTIONS probe with arbitrary origin | Full |
|
|
56
|
+
| Gateway exposure | TCP-connects to every non-loopback interface | Full |
|
|
57
|
+
| Runtime policy enforcement | Requires a runtime layer (SupraWall) | None |
|
|
58
|
+
|
|
59
|
+
## Philosophy
|
|
60
|
+
|
|
61
|
+
ClawArmor runs entirely on your machine โ no telemetry, no cloud, no accounts.
|
|
62
|
+
It has zero npm runtime dependencies, using only Node.js built-ins.
|
|
63
|
+
Every run prints exactly what files it reads and what network calls it makes before executing anything.
|
|
160
64
|
|
|
161
65
|
## License
|
|
162
66
|
|
|
163
|
-
MIT
|
|
67
|
+
MIT
|
package/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { paint } from './lib/output/colors.js';
|
|
5
5
|
|
|
6
|
-
const VERSION = '2.0.0
|
|
6
|
+
const VERSION = '2.0.0';
|
|
7
7
|
const GATEWAY_PORT_DEFAULT = 18789;
|
|
8
8
|
|
|
9
9
|
function isLocalhost(host) {
|
|
@@ -45,10 +45,13 @@ function usage() {
|
|
|
45
45
|
console.log(` ${paint.cyan('trend')} Show score over last N audits (ASCII chart)`);
|
|
46
46
|
console.log(` ${paint.cyan('compare')} Compare coverage vs openclaw security audit`);
|
|
47
47
|
console.log(` ${paint.cyan('fix')} Auto-apply safe fixes (--dry-run to preview, --apply to run)`);
|
|
48
|
+
console.log(` ${paint.cyan('harden')} Interactive hardening wizard (--dry-run, --auto)`);
|
|
49
|
+
console.log(` ${paint.cyan('status')} One-screen security posture dashboard`);
|
|
48
50
|
console.log(` ${paint.cyan('watch')} Monitor config and skill changes in real time`);
|
|
49
51
|
console.log(` ${paint.cyan('protect')} Install/uninstall/status the full guard system`);
|
|
50
52
|
console.log(` ${paint.cyan('prescan')} Pre-scan a skill before installing it`);
|
|
51
53
|
console.log(` ${paint.cyan('log')} View the audit event log`);
|
|
54
|
+
console.log(` ${paint.cyan('digest')} Show weekly security digest`);
|
|
52
55
|
console.log('');
|
|
53
56
|
console.log(` ${paint.dim('Flags:')}`);
|
|
54
57
|
console.log(` ${paint.dim('--url <host:port>')} Probe a specific host:port instead of 127.0.0.1`);
|
|
@@ -195,5 +198,24 @@ if (cmd === 'log') {
|
|
|
195
198
|
process.exit(await runLog(logFlags));
|
|
196
199
|
}
|
|
197
200
|
|
|
201
|
+
if (cmd === 'harden') {
|
|
202
|
+
const hardenFlags = {
|
|
203
|
+
dryRun: args.includes('--dry-run'),
|
|
204
|
+
auto: args.includes('--auto'),
|
|
205
|
+
};
|
|
206
|
+
const { runHarden } = await import('./lib/harden.js');
|
|
207
|
+
process.exit(await runHarden(hardenFlags));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (cmd === 'status') {
|
|
211
|
+
const { runStatus } = await import('./lib/status.js');
|
|
212
|
+
process.exit(await runStatus());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (cmd === 'digest') {
|
|
216
|
+
const { runDigest } = await import('./lib/digest.js');
|
|
217
|
+
process.exit(await runDigest());
|
|
218
|
+
}
|
|
219
|
+
|
|
198
220
|
console.log(` ${paint.red('โ')} Unknown command: ${paint.bold(cmd)}`);
|
|
199
221
|
usage(); process.exit(1);
|
package/lib/digest.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// clawarmor digest โ Weekly security digest + cron job installer.
|
|
2
|
+
// Reads ~/.clawarmor/audit.log for past 7 days and outputs a formatted summary.
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { paint } from './output/colors.js';
|
|
8
|
+
import { scoreToGrade } from './output/progress.js';
|
|
9
|
+
|
|
10
|
+
const HOME = homedir();
|
|
11
|
+
const OC_DIR = join(HOME, '.openclaw');
|
|
12
|
+
const CLAWARMOR_DIR = join(HOME, '.clawarmor');
|
|
13
|
+
const AUDIT_LOG = join(CLAWARMOR_DIR, 'audit.log');
|
|
14
|
+
const HISTORY_FILE = join(CLAWARMOR_DIR, 'history.json');
|
|
15
|
+
const CRON_DIR = join(OC_DIR, 'cron');
|
|
16
|
+
const CRON_JOBS_FILE = join(CRON_DIR, 'jobs.json');
|
|
17
|
+
|
|
18
|
+
// โโ Cron installer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
19
|
+
|
|
20
|
+
export function installDigestCron() {
|
|
21
|
+
try {
|
|
22
|
+
mkdirSync(CRON_DIR, { recursive: true });
|
|
23
|
+
|
|
24
|
+
let jobs = [];
|
|
25
|
+
if (existsSync(CRON_JOBS_FILE)) {
|
|
26
|
+
try { jobs = JSON.parse(readFileSync(CRON_JOBS_FILE, 'utf8')); }
|
|
27
|
+
catch { jobs = []; }
|
|
28
|
+
if (!Array.isArray(jobs)) jobs = [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Remove existing entry with same ID to avoid duplicates
|
|
32
|
+
jobs = jobs.filter(j => j.id !== 'clawarmor-weekly-digest');
|
|
33
|
+
|
|
34
|
+
jobs.push({
|
|
35
|
+
id: 'clawarmor-weekly-digest',
|
|
36
|
+
schedule: '0 9 * * 0',
|
|
37
|
+
task: 'clawarmor digest',
|
|
38
|
+
announce: true,
|
|
39
|
+
deliver: 'main',
|
|
40
|
+
description: 'ClawArmor weekly security digest โ every Sunday at 9am',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const tmp = CRON_JOBS_FILE + '.tmp';
|
|
44
|
+
writeFileSync(tmp, JSON.stringify(jobs, null, 2), 'utf8');
|
|
45
|
+
renameSync(tmp, CRON_JOBS_FILE);
|
|
46
|
+
return true;
|
|
47
|
+
} catch { return false; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// โโ Log parsing โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
51
|
+
|
|
52
|
+
function parseLog() {
|
|
53
|
+
if (!existsSync(AUDIT_LOG)) return [];
|
|
54
|
+
try {
|
|
55
|
+
return readFileSync(AUDIT_LOG, 'utf8')
|
|
56
|
+
.split('\n')
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
59
|
+
.filter(Boolean);
|
|
60
|
+
} catch { return []; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readHistory() {
|
|
64
|
+
try {
|
|
65
|
+
if (!existsSync(HISTORY_FILE)) return [];
|
|
66
|
+
return JSON.parse(readFileSync(HISTORY_FILE, 'utf8')) || [];
|
|
67
|
+
} catch { return []; }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatDateRange(from, to) {
|
|
71
|
+
const opts = { month: 'short', day: 'numeric' };
|
|
72
|
+
const f = from.toLocaleDateString('en-US', opts);
|
|
73
|
+
const t = to.toLocaleDateString('en-US', opts);
|
|
74
|
+
const year = to.getFullYear();
|
|
75
|
+
return `${f} โ ${t}, ${year}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function nextSunday() {
|
|
79
|
+
const now = new Date();
|
|
80
|
+
const day = now.getDay();
|
|
81
|
+
const daysUntil = day === 0 ? 7 : 7 - day;
|
|
82
|
+
const next = new Date(now);
|
|
83
|
+
next.setDate(now.getDate() + daysUntil);
|
|
84
|
+
next.setHours(9, 0, 0, 0);
|
|
85
|
+
return next.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// โโ Main export โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
89
|
+
|
|
90
|
+
export async function runDigest() {
|
|
91
|
+
const now = new Date();
|
|
92
|
+
const weekAgo = new Date(now.getTime() - 7 * 86_400_000);
|
|
93
|
+
|
|
94
|
+
const allEntries = parseLog();
|
|
95
|
+
const weekEntries = allEntries.filter(e => new Date(e.ts) >= weekAgo);
|
|
96
|
+
|
|
97
|
+
const history = readHistory();
|
|
98
|
+
const weekAgoHistory = [...history].reverse().find(h => new Date(h.timestamp) < weekAgo);
|
|
99
|
+
|
|
100
|
+
// Current score from latest history entry
|
|
101
|
+
const latestHistory = history[history.length - 1];
|
|
102
|
+
const currentScore = latestHistory?.score ?? null;
|
|
103
|
+
const currentGrade = latestHistory?.grade ?? (currentScore != null ? scoreToGrade(currentScore) : null);
|
|
104
|
+
const prevScore = weekAgoHistory?.score ?? null;
|
|
105
|
+
const scoreDelta = (currentScore != null && prevScore != null) ? currentScore - prevScore : null;
|
|
106
|
+
|
|
107
|
+
// Stats from this week's log entries
|
|
108
|
+
const auditsRun = weekEntries.filter(e => e.cmd === 'audit').length;
|
|
109
|
+
const skillsScanned = weekEntries.filter(e => e.cmd === 'scan' || e.cmd === 'prescan').length;
|
|
110
|
+
const incidents = weekEntries.filter(e =>
|
|
111
|
+
Array.isArray(e.findings) && e.findings.some(f => f.severity === 'CRITICAL')
|
|
112
|
+
).length;
|
|
113
|
+
const configChanges = weekEntries.filter(e => e.trigger === 'watch').length;
|
|
114
|
+
|
|
115
|
+
// Collect still-open findings from latest audit history entry
|
|
116
|
+
const openFindings = latestHistory?.failedIds ?? [];
|
|
117
|
+
|
|
118
|
+
// โโ Output โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
119
|
+
const dateRange = formatDateRange(weekAgo, now);
|
|
120
|
+
const arrowStr = scoreDelta != null
|
|
121
|
+
? (scoreDelta > 0 ? `โ+${scoreDelta}` : scoreDelta < 0 ? `โ${scoreDelta}` : 'โยฑ0')
|
|
122
|
+
: '';
|
|
123
|
+
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(` ๐ก ClawArmor Weekly โ ${dateRange}`);
|
|
126
|
+
console.log('');
|
|
127
|
+
|
|
128
|
+
if (currentScore != null) {
|
|
129
|
+
const scoreStr = `${currentScore}/100 ${currentGrade}`;
|
|
130
|
+
const deltaStr = arrowStr ? ` ${arrowStr} vs last week` : '';
|
|
131
|
+
console.log(` Security posture: ${scoreStr}${deltaStr}`);
|
|
132
|
+
} else {
|
|
133
|
+
console.log(` Security posture: no data โ run: clawarmor audit`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(` Skills installed: ${skillsScanned} scanned this week`);
|
|
137
|
+
console.log(` Config changes: ${configChanges}`);
|
|
138
|
+
console.log(` Audits run: ${auditsRun}`);
|
|
139
|
+
console.log(` Incidents: ${incidents}`);
|
|
140
|
+
|
|
141
|
+
if (openFindings.length) {
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log(` Recommendations:`);
|
|
144
|
+
for (const id of openFindings.slice(0, 5)) {
|
|
145
|
+
console.log(` โข ${id}`);
|
|
146
|
+
}
|
|
147
|
+
if (openFindings.length > 5) {
|
|
148
|
+
console.log(` โข โฆ and ${openFindings.length - 5} more (run: clawarmor audit)`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log(` Next digest: ${nextSunday()}`);
|
|
154
|
+
console.log('');
|
|
155
|
+
|
|
156
|
+
return 0;
|
|
157
|
+
}
|