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 CHANGED
@@ -1,163 +1,67 @@
1
- <div align="center">
1
+ # ClawArmor
2
2
 
3
- # ๐Ÿ›ก ClawArmor
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
  [![npm version](https://img.shields.io/npm/v/clawarmor?color=3fb950&label=npm&style=flat-square)](https://www.npmjs.com/package/clawarmor)
11
6
  [![license](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
12
- [![node](https://img.shields.io/badge/node-%3E%3D18-green?style=flat-square)](package.json)
13
-
14
- ```bash
15
- npm install -g clawarmor && clawarmor audit
16
- ```
17
-
18
- </div>
19
-
20
- ---
21
-
22
- ```
23
- โ„น Reads: ~/.openclaw/openclaw.json + file permissions only
24
- Network: registry.npmjs.org (version check) + 127.0.0.1:18789 (live probes)
25
- Sends nothing. Source: github.com/pinzasai/clawarmor
26
-
27
- โ”€โ”€ LIVE GATEWAY PROBES (connecting to 127.0.0.1) โ”€โ”€
28
- โœ“ Gateway running on port 18789
29
- โœ“ Not reachable on network interfaces (probed live)
30
- โœ“ Authentication required (WebSocket probe confirmed)
31
- โœ“ /health endpoint does not leak sensitive data
32
- โœ“ CORS not open to arbitrary origins
33
-
34
- Security Score: 100/100 โ”ƒ Grade: A
35
- โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 100%
36
-
37
- Verdict: Your instance is secure. No issues found.
38
-
39
- โ”€โ”€ PASSED (30 checks) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
40
- โœ“ Gateway bound to loopback only
41
- โœ“ Auth token is strong
42
- โœ“ Agent sandbox mode: "non-main" (sessions isolated)
43
- โœ“ Browser SSRF to private networks blocked
44
- โœ“ All channel allowFrom settings are restricted
45
- ... 25 more
46
- ```
47
-
48
- ---
7
+ [![zero deps](https://img.shields.io/badge/deps-zero-green?style=flat-square)](package.json)
49
8
 
50
- ## Why ClawArmor
9
+ ## What it does
51
10
 
52
- Every other OpenClaw security tool reads your config file and tells you if things look right on paper.
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
- **ClawArmor also connects locally to your running gateway and verifies live behavior.**
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
- clawarmor audit # 30 checks + 5 live gateway probes. Score 0-100. Plain-English verdict.
66
- clawarmor scan # Scan every skill file (.js .sh .py .ts SKILL.md) for malicious code.
67
- clawarmor fix # Auto-apply safe fixes. --dry-run to preview, --apply to execute.
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
- | Probe | What it checks |
25
+ | Command | Description |
79
26
  |---|---|
80
- | Port reachability | TCP-connects to gateway on every non-loopback interface |
81
- | Auth enforcement | WebSocket handshake without token โ€” does server reject it? |
82
- | Health endpoint | GET /health โ€” does response contain config data or secrets? |
83
- | CORS headers | OPTIONS with `Origin: https://evil.example.com` |
84
-
85
- These probes are **read-only and non-destructive**. They observe โ€” they don't modify anything.
86
-
87
- ### Config audit (30 checks)
88
-
89
- Gateway bind ยท auth mode ยท token strength ยท dangerous flags ยท mDNS exposure ยท real-IP fallback ยท trusted proxy config ยท file permissions (`~/.openclaw/`, `openclaw.json`, `agent-accounts.json`, `credentials/`) ยท channel allowFrom policies ยท wildcard detection ยท group policies ยท elevated tools ยท exec sandbox ยท tool restrictions (filesystem scope, apply_patch scope) ยท browser SSRF policy ยท plugin allowlist ยท log redaction ยท version currency ยท webhook security ยท multi-user trust model
90
-
91
- ### Skill supply chain scan
92
-
93
- Scans **all files** in every installed skill โ€” `.js`, `.ts`, `.sh`, `.py`, `.rb` and `SKILL.md`. Not just markdown.
94
-
95
- **Code patterns:** `eval()`, `new Function()`, `child_process`, credential file reads, pipe-to-shell, known exfil domains, large base64 blobs, dynamic `require()`
96
-
97
- **SKILL.md instruction patterns:** credential read instructions, system prompt overrides, exfiltration instructions, deception instructions, hardcoded IP fetches
98
-
99
- > **Honest limitation:** The scanner catches unsophisticated threats and common patterns. Obfuscated code (string concatenation, encoded payloads) can bypass static analysis. Treat a clean scan as a good signal, not a guarantee.
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
- | T-ACCESS-003: Token/config exposure | โœ… | File permission checks + config hardening |
108
- | T-PERSIST-001: Malicious skill supply chain | โœ… | All skill files scanned, not just SKILL.md |
109
- | T-EXEC-001/002: Prompt injection | โŒ | Runtime policy layer โ€” use [SupraWall](https://suprawall.io) |
110
- | T-EXFIL-001: Data exfiltration | โŒ | Runtime policy layer โ€” use SupraWall |
111
-
112
- ClawArmor hardens your configuration and detects supply chain threats. It does not provide runtime policy enforcement โ€” that's a different layer.
113
-
114
- ---
115
-
116
- ## Auto-fix
117
-
118
- ```bash
119
- clawarmor fix --dry-run # preview what would change
120
- clawarmor fix --apply # apply safe one-liner fixes + gateway restart instructions
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 โ€” see [LICENSE](LICENSE)
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-alpha.1';
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
+ }