clawarmor 2.0.0 → 2.1.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/cli.js +3 -2
- package/demo-preview.gif +0 -0
- package/demo.cast +680 -0
- package/demo.gif +0 -0
- package/lib/fix.js +76 -14
- package/lib/harden.js +114 -13
- package/package.json +2 -2
- package/scripts/record-demo.py +125 -0
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.
|
|
6
|
+
const VERSION = '2.1.0';
|
|
7
7
|
const GATEWAY_PORT_DEFAULT = 18789;
|
|
8
8
|
|
|
9
9
|
function isLocalhost(host) {
|
|
@@ -156,7 +156,7 @@ if (cmd === 'compare') {
|
|
|
156
156
|
|
|
157
157
|
if (cmd === 'fix') {
|
|
158
158
|
const { runFix } = await import('./lib/fix.js');
|
|
159
|
-
const fixFlags = { apply: process.argv.includes('--apply'), dryRun: process.argv.includes('--dry-run') };
|
|
159
|
+
const fixFlags = { apply: process.argv.includes('--apply'), dryRun: process.argv.includes('--dry-run'), force: process.argv.includes('--force') };
|
|
160
160
|
process.exit(await runFix(fixFlags));
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -202,6 +202,7 @@ if (cmd === 'harden') {
|
|
|
202
202
|
const hardenFlags = {
|
|
203
203
|
dryRun: args.includes('--dry-run'),
|
|
204
204
|
auto: args.includes('--auto'),
|
|
205
|
+
force: args.includes('--force'),
|
|
205
206
|
};
|
|
206
207
|
const { runHarden } = await import('./lib/harden.js');
|
|
207
208
|
process.exit(await runHarden(hardenFlags));
|
package/demo-preview.gif
ADDED
|
Binary file
|
package/demo.cast
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
{"version": 2, "width": 110, "height": 35, "timestamp": 1772424511, "title": "ClawArmor v2.0 \u2014 Security Audit Demo", "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
|
|
2
|
+
[0.0, "o", "$ "]
|
|
3
|
+
[0.808, "o", "#"]
|
|
4
|
+
[0.838, "o", " "]
|
|
5
|
+
[0.868, "o", "S"]
|
|
6
|
+
[0.898, "o", "t"]
|
|
7
|
+
[0.928, "o", "e"]
|
|
8
|
+
[0.958, "o", "p"]
|
|
9
|
+
[0.988, "o", " "]
|
|
10
|
+
[1.018, "o", "1"]
|
|
11
|
+
[1.048, "o", ":"]
|
|
12
|
+
[1.078, "o", " "]
|
|
13
|
+
[1.108, "o", "C"]
|
|
14
|
+
[1.138, "o", "h"]
|
|
15
|
+
[1.168, "o", "e"]
|
|
16
|
+
[1.198, "o", "c"]
|
|
17
|
+
[1.228, "o", "k"]
|
|
18
|
+
[1.258, "o", " "]
|
|
19
|
+
[1.288, "o", "y"]
|
|
20
|
+
[1.318, "o", "o"]
|
|
21
|
+
[1.348, "o", "u"]
|
|
22
|
+
[1.378, "o", "r"]
|
|
23
|
+
[1.408, "o", " "]
|
|
24
|
+
[1.438, "o", "O"]
|
|
25
|
+
[1.468, "o", "p"]
|
|
26
|
+
[1.498, "o", "e"]
|
|
27
|
+
[1.528, "o", "n"]
|
|
28
|
+
[1.558, "o", "C"]
|
|
29
|
+
[1.588, "o", "l"]
|
|
30
|
+
[1.618, "o", "a"]
|
|
31
|
+
[1.648, "o", "w"]
|
|
32
|
+
[1.678, "o", " "]
|
|
33
|
+
[1.708, "o", "s"]
|
|
34
|
+
[1.738, "o", "e"]
|
|
35
|
+
[1.768, "o", "c"]
|
|
36
|
+
[1.798, "o", "u"]
|
|
37
|
+
[1.828, "o", "r"]
|
|
38
|
+
[1.858, "o", "i"]
|
|
39
|
+
[1.888, "o", "t"]
|
|
40
|
+
[1.918, "o", "y"]
|
|
41
|
+
[1.948, "o", " "]
|
|
42
|
+
[1.978, "o", "p"]
|
|
43
|
+
[2.008, "o", "o"]
|
|
44
|
+
[2.038, "o", "s"]
|
|
45
|
+
[2.068, "o", "t"]
|
|
46
|
+
[2.098, "o", "u"]
|
|
47
|
+
[2.128, "o", "r"]
|
|
48
|
+
[2.158, "o", "e"]
|
|
49
|
+
[2.188, "o", "\n"]
|
|
50
|
+
[2.288, "o", "$ "]
|
|
51
|
+
[2.796, "o", "c"]
|
|
52
|
+
[2.841, "o", "l"]
|
|
53
|
+
[2.886, "o", "a"]
|
|
54
|
+
[2.931, "o", "w"]
|
|
55
|
+
[2.976, "o", "a"]
|
|
56
|
+
[3.021, "o", "r"]
|
|
57
|
+
[3.066, "o", "m"]
|
|
58
|
+
[3.111, "o", "o"]
|
|
59
|
+
[3.156, "o", "r"]
|
|
60
|
+
[3.201, "o", " "]
|
|
61
|
+
[3.246, "o", "a"]
|
|
62
|
+
[3.291, "o", "u"]
|
|
63
|
+
[3.336, "o", "d"]
|
|
64
|
+
[3.381, "o", "i"]
|
|
65
|
+
[3.426, "o", "t"]
|
|
66
|
+
[3.471, "o", "\n"]
|
|
67
|
+
[3.671, "o", "\n"]
|
|
68
|
+
[3.683, "o", " \u2139 Config: local (~/.openclaw/openclaw.json)\n"]
|
|
69
|
+
[3.695, "o", " Probes: 127.0.0.1:18789 (local)\n"]
|
|
70
|
+
[3.707, "o", " Sends nothing. Source: github.com/pinzasai/clawarmor\n"]
|
|
71
|
+
[3.719, "o", "\n"]
|
|
72
|
+
[3.731, "o", "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n"]
|
|
73
|
+
[3.743, "o", "\u2551 ClawArmor Audit v2.0.0-alpha.1 \u2551\n"]
|
|
74
|
+
[3.755, "o", "\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n"]
|
|
75
|
+
[3.767, "o", "\n"]
|
|
76
|
+
[3.779, "o", " Config: /Users/pinzas/.openclaw/openclaw.json\n"]
|
|
77
|
+
[3.791, "o", " Scanned: Mar 1, 2026, 8:08 PM\n"]
|
|
78
|
+
[3.803, "o", "\n"]
|
|
79
|
+
[3.815, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
80
|
+
[3.827, "o", " LIVE GATEWAY PROBES (connecting to 127.0.0.1:18789)\n"]
|
|
81
|
+
[3.839, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
82
|
+
[3.851, "o", " \u2713 Gateway running on port 18789\n"]
|
|
83
|
+
[3.863, "o", " \u2713 Not reachable on network interfaces (probed live)\n"]
|
|
84
|
+
[3.875, "o", " \u2713 Authentication required (WebSocket probe confirmed)\n"]
|
|
85
|
+
[3.887, "o", " \u2713 /health endpoint does not leak sensitive data\n"]
|
|
86
|
+
[3.899, "o", " \u2713 CORS not open to arbitrary origins\n"]
|
|
87
|
+
[3.911, "o", "\n"]
|
|
88
|
+
[3.923, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
89
|
+
[3.935, "o", " Security Score: 45/100 \u2503 Grade: D\n"]
|
|
90
|
+
[3.947, "o", " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 45%\n"]
|
|
91
|
+
[3.959, "o", "\n"]
|
|
92
|
+
[3.971, "o", " Verdict: Your instance has CRITICAL exposure. Fix immediately before using.\n"]
|
|
93
|
+
[3.983, "o", "\n"]
|
|
94
|
+
[3.995, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
95
|
+
[4.007, "o", " CRITICAL (1 finding)\n"]
|
|
96
|
+
[4.019, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
97
|
+
[4.031, "o", "\n"]
|
|
98
|
+
[4.043, "o", " \u2717 World-readable credential files in ~/.openclaw/ (3)\n"]
|
|
99
|
+
[4.055, "o", " The following files are readable by any user on the system:\n"]
|
|
100
|
+
[4.067, "o", " \u2022 .env (644)\n"]
|
|
101
|
+
[4.079, "o", " \u2022 google-auth-setup.py (644)\n"]
|
|
102
|
+
[4.091, "o", " \u2022 update-check.json (644)\n"]
|
|
103
|
+
[4.103, "o", " \n"]
|
|
104
|
+
[4.115, "o", " Any local process or user can read your API keys and tokens.\n"]
|
|
105
|
+
[4.127, "o", "\n"]
|
|
106
|
+
[4.139, "o", " Fix: Fix immediately:\n"]
|
|
107
|
+
[4.151, "o", " chmod 600 /Users/pinzas/.openclaw/.env\n"]
|
|
108
|
+
[4.163, "o", " chmod 600 /Users/pinzas/.openclaw/google-auth-setup.py\n"]
|
|
109
|
+
[4.175, "o", " chmod 600 /Users/pinzas/.openclaw/update-check.json\n"]
|
|
110
|
+
[4.187, "o", "\n"]
|
|
111
|
+
[4.199, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
112
|
+
[4.211, "o", " HIGH (2 findings)\n"]
|
|
113
|
+
[4.223, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
114
|
+
[4.235, "o", "\n"]
|
|
115
|
+
[4.247, "o", " \u2717 Exec approval disabled \u2014 all shell commands run without confirmation\n"]
|
|
116
|
+
[4.259, "o", " tools.exec.ask=\"off\" means every shell command the agent triggers\n"]
|
|
117
|
+
[4.271, "o", " runs immediately with zero user approval. Any prompt injection or malicious\n"]
|
|
118
|
+
[4.283, "o", " skill can execute arbitrary commands on your system without you seeing them.\n"]
|
|
119
|
+
[4.295, "o", " Attack: attacker injects \"run rm -rf ~/important\" \u2014 it executes silently.\n"]
|
|
120
|
+
[4.307, "o", "\n"]
|
|
121
|
+
[4.319, "o", " Fix: openclaw config set tools.exec.ask always\n"]
|
|
122
|
+
[4.331, "o", " # or, to allow a specific set without prompts:\n"]
|
|
123
|
+
[4.343, "o", " openctl config set tools.exec.ask on-miss\n"]
|
|
124
|
+
[4.355, "o", " openctl config set tools.exec.allowed '[\"git\",\"npm\",\"node\"]'\n"]
|
|
125
|
+
[4.367, "o", "\n"]
|
|
126
|
+
[4.379, "o", " \u2717 API key patterns found in ~/.openclaw/ JSON files (3 files)\n"]
|
|
127
|
+
[4.391, "o", " The following JSON files in ~/.openclaw/ contain patterns matching API keys or secrets:\n"]
|
|
128
|
+
[4.403, "o", " \u2022 agent-accounts.json\n"]
|
|
129
|
+
[4.415, "o", " \u2022 exec-approvals.json\n"]
|
|
130
|
+
[4.427, "o", " \u2022 openclaw.json\n"]
|
|
131
|
+
[4.439, "o", " \n"]
|
|
132
|
+
[4.451, "o", " Note: Only key name patterns are detected \u2014 actual values are never read or stored.\n"]
|
|
133
|
+
[4.463, "o", " Credentials in the wrong files may be at risk if file permissions are too open.\n"]
|
|
134
|
+
[4.475, "o", "\n"]
|
|
135
|
+
[4.487, "o", " Fix: Ensure all credential files use 0600 permissions:\n"]
|
|
136
|
+
[4.499, "o", " chmod 600 ~/.openclaw/*.json\n"]
|
|
137
|
+
[4.511, "o", " \n"]
|
|
138
|
+
[4.523, "o", " If credentials are in unexpected files, move them to agent-accounts.json.\n"]
|
|
139
|
+
[4.535, "o", "\n"]
|
|
140
|
+
[4.547, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
141
|
+
[4.559, "o", " PASSED (34 checks)\n"]
|
|
142
|
+
[4.571, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
143
|
+
[4.583, "o", " \u2713 Gateway bound to loopback only\n"]
|
|
144
|
+
[4.595, "o", " \u2713 Tailscale Funnel not enabled\n"]
|
|
145
|
+
[4.607, "o", " \u2713 Auth token is strong\n"]
|
|
146
|
+
[4.619, "o", " \u2713 No dangerous flags enabled\n"]
|
|
147
|
+
[4.631, "o", " \u2713 mDNS mode: \"minimal\" (not leaking sensitive data)\n"]
|
|
148
|
+
[4.643, "o", " \u2713 Real-IP fallback disabled\n"]
|
|
149
|
+
[4.655, "o", " \u2713 Gateway is loopback-only \u2014 trustedProxies not needed\n"]
|
|
150
|
+
[4.667, "o", " \u2713 Trust model appropriate for current channel configuration\n"]
|
|
151
|
+
[4.679, "o", " \u2713 ~/.openclaw/ is owner-only (700)\n"]
|
|
152
|
+
[4.691, "o", " \u2713 openclaw.json is owner-only (600)\n"]
|
|
153
|
+
[4.703, "o", " \u2713 agent-accounts.json is owner-only (600)\n"]
|
|
154
|
+
[4.715, "o", " \u2713 credentials/ directory is locked down\n"]
|
|
155
|
+
[4.727, "o", " \u2713 Session transcripts are private\n"]
|
|
156
|
+
[4.739, "o", " \u2713 Telegram DM policy: \"pairing\" (restricted)\n"]
|
|
157
|
+
[4.751, "o", " \u2713 All group policies use allowlist\n"]
|
|
158
|
+
[4.763, "o", " \u2713 No open groups with elevated tools (safe)\n"]
|
|
159
|
+
[4.775, "o", " \u2713 DM sessions are isolated per user\n"]
|
|
160
|
+
[4.787, "o", " \u2713 Agent sandbox mode: \"all\" (sessions isolated)\n"]
|
|
161
|
+
[4.799, "o", " \u2713 exec sandbox configuration is consistent\n"]
|
|
162
|
+
[4.811, "o", " \u2713 Thinking stream not leaking reasoning\n"]
|
|
163
|
+
[4.823, "o", " \u2713 Elevated tools not configured\n"]
|
|
164
|
+
[4.835, "o", " \u2713 Filesystem restricted to workspace\n"]
|
|
165
|
+
[4.847, "o", " \u2713 apply_patch restricted to workspace\n"]
|
|
166
|
+
[4.859, "o", " \u2713 Browser SSRF to private networks blocked\n"]
|
|
167
|
+
[4.871, "o", " \u2713 Plugin allowlist configured\n"]
|
|
168
|
+
[4.883, "o", " \u2713 Log redaction enabled\n"]
|
|
169
|
+
[4.895, "o", " \u2713 OpenClaw 2026.2.26 (up to date)\n"]
|
|
170
|
+
[4.907, "o", " \u2713 Webhooks cannot control session routing\n"]
|
|
171
|
+
[4.919, "o", " \u2713 No webhook token configured\n"]
|
|
172
|
+
[4.931, "o", " \u2713 All channel allowFrom settings are restricted\n"]
|
|
173
|
+
[4.943, "o", " \u2713 All credential date fields are within 90 days\n"]
|
|
174
|
+
[4.955, "o", " \u2713 All installed skills have explicit version pins\n"]
|
|
175
|
+
[4.967, "o", " \u2713 Workspace directory not found \u2014 git credential leak check skipped\n"]
|
|
176
|
+
[4.979, "o", " \u2713 ~/.openclaw/ directory permissions are secure (700)\n"]
|
|
177
|
+
[4.991, "o", "\n"]
|
|
178
|
+
[5.003, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
179
|
+
[5.015, "o", " 3 issues found. Fix above to improve score.\n"]
|
|
180
|
+
[5.027, "o", " Run clawarmor scan to check installed skills.\n"]
|
|
181
|
+
[5.039, "o", " Run clawarmor trend to see score history.\n"]
|
|
182
|
+
[5.051, "o", " Continuous monitoring: github.com/pinzasai/clawarmor\n"]
|
|
183
|
+
[5.063, "o", "\n"]
|
|
184
|
+
[5.075, "o", "\n"]
|
|
185
|
+
[5.087, "o", " ! Config changed since last clean audit\n"]
|
|
186
|
+
[5.099, "o", " Size: 4751 \u2192 5295 bytes (+544)\n"]
|
|
187
|
+
[5.111, "o", " Lines: 186 \u2192 197 (+11)\n"]
|
|
188
|
+
[5.123, "o", " Hash: fda00bbb0ac793f7 \u2192 d757fa979eb98f41\n"]
|
|
189
|
+
[5.135, "o", " Baseline set: 2026-03-01\n"]
|
|
190
|
+
[5.147, "o", " Run clawarmor audit --accept-changes to update baseline\n"]
|
|
191
|
+
[5.159, "o", ""]
|
|
192
|
+
[8.171, "o", "$ "]
|
|
193
|
+
[8.679, "o", "#"]
|
|
194
|
+
[8.709, "o", " "]
|
|
195
|
+
[8.739, "o", "S"]
|
|
196
|
+
[8.769, "o", "t"]
|
|
197
|
+
[8.799, "o", "e"]
|
|
198
|
+
[8.829, "o", "p"]
|
|
199
|
+
[8.859, "o", " "]
|
|
200
|
+
[8.889, "o", "2"]
|
|
201
|
+
[8.919, "o", ":"]
|
|
202
|
+
[8.949, "o", " "]
|
|
203
|
+
[8.979, "o", "S"]
|
|
204
|
+
[9.009, "o", "e"]
|
|
205
|
+
[9.039, "o", "e"]
|
|
206
|
+
[9.069, "o", " "]
|
|
207
|
+
[9.099, "o", "w"]
|
|
208
|
+
[9.129, "o", "h"]
|
|
209
|
+
[9.159, "o", "a"]
|
|
210
|
+
[9.189, "o", "t"]
|
|
211
|
+
[9.219, "o", " "]
|
|
212
|
+
[9.249, "o", "C"]
|
|
213
|
+
[9.279, "o", "l"]
|
|
214
|
+
[9.309, "o", "a"]
|
|
215
|
+
[9.339, "o", "w"]
|
|
216
|
+
[9.369, "o", "A"]
|
|
217
|
+
[9.399, "o", "r"]
|
|
218
|
+
[9.429, "o", "m"]
|
|
219
|
+
[9.459, "o", "o"]
|
|
220
|
+
[9.489, "o", "r"]
|
|
221
|
+
[9.519, "o", " "]
|
|
222
|
+
[9.549, "o", "c"]
|
|
223
|
+
[9.579, "o", "a"]
|
|
224
|
+
[9.609, "o", "n"]
|
|
225
|
+
[9.639, "o", " "]
|
|
226
|
+
[9.669, "o", "f"]
|
|
227
|
+
[9.699, "o", "i"]
|
|
228
|
+
[9.729, "o", "x"]
|
|
229
|
+
[9.759, "o", " "]
|
|
230
|
+
[9.789, "o", "a"]
|
|
231
|
+
[9.819, "o", "u"]
|
|
232
|
+
[9.849, "o", "t"]
|
|
233
|
+
[9.879, "o", "o"]
|
|
234
|
+
[9.909, "o", "m"]
|
|
235
|
+
[9.939, "o", "a"]
|
|
236
|
+
[9.969, "o", "t"]
|
|
237
|
+
[9.999, "o", "i"]
|
|
238
|
+
[10.029, "o", "c"]
|
|
239
|
+
[10.059, "o", "a"]
|
|
240
|
+
[10.089, "o", "l"]
|
|
241
|
+
[10.119, "o", "l"]
|
|
242
|
+
[10.149, "o", "y"]
|
|
243
|
+
[10.179, "o", "\n"]
|
|
244
|
+
[10.279, "o", "$ "]
|
|
245
|
+
[10.787, "o", "c"]
|
|
246
|
+
[10.832, "o", "l"]
|
|
247
|
+
[10.877, "o", "a"]
|
|
248
|
+
[10.922, "o", "w"]
|
|
249
|
+
[10.967, "o", "a"]
|
|
250
|
+
[11.012, "o", "r"]
|
|
251
|
+
[11.057, "o", "m"]
|
|
252
|
+
[11.102, "o", "o"]
|
|
253
|
+
[11.147, "o", "r"]
|
|
254
|
+
[11.192, "o", " "]
|
|
255
|
+
[11.237, "o", "h"]
|
|
256
|
+
[11.282, "o", "a"]
|
|
257
|
+
[11.327, "o", "r"]
|
|
258
|
+
[11.372, "o", "d"]
|
|
259
|
+
[11.417, "o", "e"]
|
|
260
|
+
[11.462, "o", "n"]
|
|
261
|
+
[11.507, "o", " "]
|
|
262
|
+
[11.552, "o", "-"]
|
|
263
|
+
[11.597, "o", "-"]
|
|
264
|
+
[11.642, "o", "d"]
|
|
265
|
+
[11.687, "o", "r"]
|
|
266
|
+
[11.732, "o", "y"]
|
|
267
|
+
[11.777, "o", "-"]
|
|
268
|
+
[11.822, "o", "r"]
|
|
269
|
+
[11.867, "o", "u"]
|
|
270
|
+
[11.912, "o", "n"]
|
|
271
|
+
[11.957, "o", "\n"]
|
|
272
|
+
[12.157, "o", "\n"]
|
|
273
|
+
[12.169, "o", " \u2139 Config: local (~/.openclaw/openclaw.json)\n"]
|
|
274
|
+
[12.181, "o", " Probes: 127.0.0.1:18789 (local)\n"]
|
|
275
|
+
[12.193, "o", " Sends nothing. Source: github.com/pinzasai/clawarmor\n"]
|
|
276
|
+
[12.205, "o", "\n"]
|
|
277
|
+
[12.217, "o", "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n"]
|
|
278
|
+
[12.229, "o", "\u2551 ClawArmor Harden v2.0 \u2551\n"]
|
|
279
|
+
[12.241, "o", "\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n"]
|
|
280
|
+
[12.253, "o", "\n"]
|
|
281
|
+
[12.265, "o", " Dry run \u2014 showing what would be fixed (no changes applied):\n"]
|
|
282
|
+
[12.277, "o", "\n"]
|
|
283
|
+
[12.289, "o", " ! .env is readable by other users (permissions: 644)\n"]
|
|
284
|
+
[12.301, "o", " Fix: Set permissions to 600 (owner-only) on /Users/pinzas/.openclaw/.env\n"]
|
|
285
|
+
[12.313, "o", " Cmd: chmod 600 /Users/pinzas/.openclaw/.env\n"]
|
|
286
|
+
[12.325, "o", "\n"]
|
|
287
|
+
[12.337, "o", " ! google-auth-setup.py is readable by other users (permissions: 644)\n"]
|
|
288
|
+
[12.349, "o", " Fix: Set permissions to 600 (owner-only) on /Users/pinzas/.openclaw/google-auth-setup.py\n"]
|
|
289
|
+
[12.361, "o", " Cmd: chmod 600 /Users/pinzas/.openclaw/google-auth-setup.py\n"]
|
|
290
|
+
[12.373, "o", "\n"]
|
|
291
|
+
[12.385, "o", " ! update-check.json is readable by other users (permissions: 644)\n"]
|
|
292
|
+
[12.397, "o", " Fix: Set permissions to 600 (owner-only) on /Users/pinzas/.openclaw/update-check.json\n"]
|
|
293
|
+
[12.409, "o", " Cmd: chmod 600 /Users/pinzas/.openclaw/update-check.json\n"]
|
|
294
|
+
[12.421, "o", "\n"]
|
|
295
|
+
[12.433, "o", " ! exec.ask is off \u2014 shell commands run without user confirmation\n"]
|
|
296
|
+
[12.445, "o", " Fix: Enable exec.ask so shell commands require confirmation\n"]
|
|
297
|
+
[12.457, "o", " Cmd: openclaw config set exec.ask always\n"]
|
|
298
|
+
[12.469, "o", " Note: Restart gateway after applying: openclaw gateway restart\n"]
|
|
299
|
+
[12.481, "o", "\n"]
|
|
300
|
+
[12.493, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
301
|
+
[12.505, "o", " 4 fixes available.\n"]
|
|
302
|
+
[12.517, "o", " Run clawarmor harden to apply interactively.\n"]
|
|
303
|
+
[12.529, "o", " Run clawarmor harden --auto to apply all without prompts.\n"]
|
|
304
|
+
[12.541, "o", "\n"]
|
|
305
|
+
[12.553, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
306
|
+
[12.565, "o", " Manual follow-up required:\n"]
|
|
307
|
+
[12.577, "o", " \u2022 Rotate tokens older than 90 days (run: clawarmor log --tokens)\n"]
|
|
308
|
+
[12.589, "o", " \u2022 Review and rotate any compromised or exposed credentials\n"]
|
|
309
|
+
[12.601, "o", " \u2022 Enable agent sandbox isolation if Docker Desktop is available\n"]
|
|
310
|
+
[12.613, "o", "\n"]
|
|
311
|
+
[12.625, "o", ""]
|
|
312
|
+
[15.637, "o", "$ "]
|
|
313
|
+
[16.145, "o", "#"]
|
|
314
|
+
[16.175, "o", " "]
|
|
315
|
+
[16.205, "o", "S"]
|
|
316
|
+
[16.235, "o", "t"]
|
|
317
|
+
[16.265, "o", "e"]
|
|
318
|
+
[16.295, "o", "p"]
|
|
319
|
+
[16.325, "o", " "]
|
|
320
|
+
[16.355, "o", "3"]
|
|
321
|
+
[16.385, "o", ":"]
|
|
322
|
+
[16.415, "o", " "]
|
|
323
|
+
[16.445, "o", "F"]
|
|
324
|
+
[16.475, "o", "i"]
|
|
325
|
+
[16.505, "o", "x"]
|
|
326
|
+
[16.535, "o", " "]
|
|
327
|
+
[16.565, "o", "e"]
|
|
328
|
+
[16.595, "o", "v"]
|
|
329
|
+
[16.625, "o", "e"]
|
|
330
|
+
[16.655, "o", "r"]
|
|
331
|
+
[16.685, "o", "y"]
|
|
332
|
+
[16.715, "o", "t"]
|
|
333
|
+
[16.745, "o", "h"]
|
|
334
|
+
[16.775, "o", "i"]
|
|
335
|
+
[16.805, "o", "n"]
|
|
336
|
+
[16.835, "o", "g"]
|
|
337
|
+
[16.865, "o", " "]
|
|
338
|
+
[16.895, "o", "a"]
|
|
339
|
+
[16.925, "o", "u"]
|
|
340
|
+
[16.955, "o", "t"]
|
|
341
|
+
[16.985, "o", "o"]
|
|
342
|
+
[17.015, "o", "m"]
|
|
343
|
+
[17.045, "o", "a"]
|
|
344
|
+
[17.075, "o", "t"]
|
|
345
|
+
[17.105, "o", "i"]
|
|
346
|
+
[17.135, "o", "c"]
|
|
347
|
+
[17.165, "o", "a"]
|
|
348
|
+
[17.195, "o", "l"]
|
|
349
|
+
[17.225, "o", "l"]
|
|
350
|
+
[17.255, "o", "y"]
|
|
351
|
+
[17.285, "o", "\n"]
|
|
352
|
+
[17.385, "o", "$ "]
|
|
353
|
+
[17.893, "o", "c"]
|
|
354
|
+
[17.938, "o", "l"]
|
|
355
|
+
[17.983, "o", "a"]
|
|
356
|
+
[18.028, "o", "w"]
|
|
357
|
+
[18.073, "o", "a"]
|
|
358
|
+
[18.118, "o", "r"]
|
|
359
|
+
[18.163, "o", "m"]
|
|
360
|
+
[18.208, "o", "o"]
|
|
361
|
+
[18.253, "o", "r"]
|
|
362
|
+
[18.298, "o", " "]
|
|
363
|
+
[18.343, "o", "h"]
|
|
364
|
+
[18.388, "o", "a"]
|
|
365
|
+
[18.433, "o", "r"]
|
|
366
|
+
[18.478, "o", "d"]
|
|
367
|
+
[18.523, "o", "e"]
|
|
368
|
+
[18.568, "o", "n"]
|
|
369
|
+
[18.613, "o", " "]
|
|
370
|
+
[18.658, "o", "-"]
|
|
371
|
+
[18.703, "o", "-"]
|
|
372
|
+
[18.748, "o", "a"]
|
|
373
|
+
[18.793, "o", "u"]
|
|
374
|
+
[18.838, "o", "t"]
|
|
375
|
+
[18.883, "o", "o"]
|
|
376
|
+
[18.928, "o", "\n"]
|
|
377
|
+
[19.128, "o", "\n"]
|
|
378
|
+
[19.14, "o", " \u2139 Config: local (~/.openclaw/openclaw.json)\n"]
|
|
379
|
+
[19.152, "o", " Probes: 127.0.0.1:18789 (local)\n"]
|
|
380
|
+
[19.164, "o", " Sends nothing. Source: github.com/pinzasai/clawarmor\n"]
|
|
381
|
+
[19.176, "o", "\n"]
|
|
382
|
+
[19.188, "o", "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n"]
|
|
383
|
+
[19.2, "o", "\u2551 ClawArmor Harden v2.0 \u2551\n"]
|
|
384
|
+
[19.212, "o", "\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n"]
|
|
385
|
+
[19.224, "o", "\n"]
|
|
386
|
+
[19.236, "o", " Auto mode \u2014 applying all safe fixes without confirmation\n"]
|
|
387
|
+
[19.248, "o", "\n"]
|
|
388
|
+
[19.26, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
389
|
+
[19.272, "o", " Problem: .env is readable by other users (permissions: 644)\n"]
|
|
390
|
+
[19.284, "o", " Fix: Set permissions to 600 (owner-only) on /Users/pinzas/.openclaw/.env\n"]
|
|
391
|
+
[19.296, "o", " Command: chmod 600 /Users/pinzas/.openclaw/.env\n"]
|
|
392
|
+
[19.308, "o", "\n"]
|
|
393
|
+
[19.32, "o", " \u2713 Fixed\n"]
|
|
394
|
+
[19.332, "o", "\n"]
|
|
395
|
+
[19.344, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
396
|
+
[19.356, "o", " Problem: google-auth-setup.py is readable by other users (permissions: 644)\n"]
|
|
397
|
+
[19.368, "o", " Fix: Set permissions to 600 (owner-only) on /Users/pinzas/.openclaw/google-auth-setup.py\n"]
|
|
398
|
+
[19.38, "o", " Command: chmod 600 /Users/pinzas/.openclaw/google-auth-setup.py\n"]
|
|
399
|
+
[19.392, "o", "\n"]
|
|
400
|
+
[19.404, "o", " \u2713 Fixed\n"]
|
|
401
|
+
[19.416, "o", "\n"]
|
|
402
|
+
[19.428, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
403
|
+
[19.44, "o", " Problem: update-check.json is readable by other users (permissions: 644)\n"]
|
|
404
|
+
[19.452, "o", " Fix: Set permissions to 600 (owner-only) on /Users/pinzas/.openclaw/update-check.json\n"]
|
|
405
|
+
[19.464, "o", " Command: chmod 600 /Users/pinzas/.openclaw/update-check.json\n"]
|
|
406
|
+
[19.476, "o", "\n"]
|
|
407
|
+
[19.488, "o", " \u2713 Fixed\n"]
|
|
408
|
+
[19.5, "o", "\n"]
|
|
409
|
+
[19.512, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
410
|
+
[19.524, "o", " Problem: exec.ask is off \u2014 shell commands run without user confirmation\n"]
|
|
411
|
+
[19.536, "o", " Fix: Enable exec.ask so shell commands require confirmation\n"]
|
|
412
|
+
[19.548, "o", " Command: openclaw config set exec.ask always\n"]
|
|
413
|
+
[19.56, "o", "\n"]
|
|
414
|
+
[19.572, "o", " \u2717 Failed: Command failed: openclaw config set exec.ask always\n"]
|
|
415
|
+
[19.584, "o", "\n"]
|
|
416
|
+
[19.596, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
417
|
+
[19.608, "o", "\n"]
|
|
418
|
+
[19.62, "o", " Applied: 3 Skipped: 0 Failed: 1\n"]
|
|
419
|
+
[19.632, "o", "\n"]
|
|
420
|
+
[19.644, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
421
|
+
[19.656, "o", " Manual follow-up required:\n"]
|
|
422
|
+
[19.668, "o", " \u2022 Rotate tokens older than 90 days (run: clawarmor log --tokens)\n"]
|
|
423
|
+
[19.68, "o", " \u2022 Review and rotate any compromised or exposed credentials\n"]
|
|
424
|
+
[19.692, "o", " \u2022 Enable agent sandbox isolation if Docker Desktop is available\n"]
|
|
425
|
+
[19.704, "o", "\n"]
|
|
426
|
+
[19.716, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
427
|
+
[19.728, "o", " Re-running audit to measure impact...\n"]
|
|
428
|
+
[19.74, "o", "\n"]
|
|
429
|
+
[19.752, "o", " Before: 45/100 Grade: D\n"]
|
|
430
|
+
[19.764, "o", " After: 70/100 Grade: C +25\n"]
|
|
431
|
+
[19.776, "o", "\n"]
|
|
432
|
+
[19.788, "o", ""]
|
|
433
|
+
[22.3, "o", "$ "]
|
|
434
|
+
[22.808, "o", "#"]
|
|
435
|
+
[22.838, "o", " "]
|
|
436
|
+
[22.868, "o", "S"]
|
|
437
|
+
[22.898, "o", "t"]
|
|
438
|
+
[22.928, "o", "e"]
|
|
439
|
+
[22.958, "o", "p"]
|
|
440
|
+
[22.988, "o", " "]
|
|
441
|
+
[23.018, "o", "4"]
|
|
442
|
+
[23.048, "o", ":"]
|
|
443
|
+
[23.078, "o", " "]
|
|
444
|
+
[23.108, "o", "V"]
|
|
445
|
+
[23.138, "o", "e"]
|
|
446
|
+
[23.168, "o", "r"]
|
|
447
|
+
[23.198, "o", "i"]
|
|
448
|
+
[23.228, "o", "f"]
|
|
449
|
+
[23.258, "o", "y"]
|
|
450
|
+
[23.288, "o", " "]
|
|
451
|
+
[23.318, "o", "\u2014"]
|
|
452
|
+
[23.348, "o", " "]
|
|
453
|
+
[23.378, "o", "r"]
|
|
454
|
+
[23.408, "o", "e"]
|
|
455
|
+
[23.438, "o", "-"]
|
|
456
|
+
[23.468, "o", "a"]
|
|
457
|
+
[23.498, "o", "u"]
|
|
458
|
+
[23.528, "o", "d"]
|
|
459
|
+
[23.558, "o", "i"]
|
|
460
|
+
[23.588, "o", "t"]
|
|
461
|
+
[23.618, "o", " "]
|
|
462
|
+
[23.648, "o", "a"]
|
|
463
|
+
[23.678, "o", "f"]
|
|
464
|
+
[23.708, "o", "t"]
|
|
465
|
+
[23.738, "o", "e"]
|
|
466
|
+
[23.768, "o", "r"]
|
|
467
|
+
[23.798, "o", " "]
|
|
468
|
+
[23.828, "o", "h"]
|
|
469
|
+
[23.858, "o", "a"]
|
|
470
|
+
[23.888, "o", "r"]
|
|
471
|
+
[23.918, "o", "d"]
|
|
472
|
+
[23.948, "o", "e"]
|
|
473
|
+
[23.978, "o", "n"]
|
|
474
|
+
[24.008, "o", "i"]
|
|
475
|
+
[24.038, "o", "n"]
|
|
476
|
+
[24.068, "o", "g"]
|
|
477
|
+
[24.098, "o", "\n"]
|
|
478
|
+
[24.198, "o", "$ "]
|
|
479
|
+
[24.706, "o", "c"]
|
|
480
|
+
[24.751, "o", "l"]
|
|
481
|
+
[24.796, "o", "a"]
|
|
482
|
+
[24.841, "o", "w"]
|
|
483
|
+
[24.886, "o", "a"]
|
|
484
|
+
[24.931, "o", "r"]
|
|
485
|
+
[24.976, "o", "m"]
|
|
486
|
+
[25.021, "o", "o"]
|
|
487
|
+
[25.066, "o", "r"]
|
|
488
|
+
[25.111, "o", " "]
|
|
489
|
+
[25.156, "o", "a"]
|
|
490
|
+
[25.201, "o", "u"]
|
|
491
|
+
[25.246, "o", "d"]
|
|
492
|
+
[25.291, "o", "i"]
|
|
493
|
+
[25.336, "o", "t"]
|
|
494
|
+
[25.381, "o", "\n"]
|
|
495
|
+
[25.581, "o", "\n"]
|
|
496
|
+
[25.593, "o", " \u2139 Config: local (~/.openclaw/openclaw.json)\n"]
|
|
497
|
+
[25.605, "o", " Probes: 127.0.0.1:18789 (local)\n"]
|
|
498
|
+
[25.617, "o", " Sends nothing. Source: github.com/pinzasai/clawarmor\n"]
|
|
499
|
+
[25.629, "o", "\n"]
|
|
500
|
+
[25.641, "o", "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n"]
|
|
501
|
+
[25.653, "o", "\u2551 ClawArmor Audit v2.0.0-alpha.1 \u2551\n"]
|
|
502
|
+
[25.665, "o", "\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d\n"]
|
|
503
|
+
[25.677, "o", "\n"]
|
|
504
|
+
[25.689, "o", " Config: /Users/pinzas/.openclaw/openclaw.json\n"]
|
|
505
|
+
[25.701, "o", " Scanned: Mar 1, 2026, 8:08 PM\n"]
|
|
506
|
+
[25.713, "o", "\n"]
|
|
507
|
+
[25.725, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
508
|
+
[25.737, "o", " LIVE GATEWAY PROBES (connecting to 127.0.0.1:18789)\n"]
|
|
509
|
+
[25.749, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
510
|
+
[25.761, "o", " \u2713 Gateway running on port 18789\n"]
|
|
511
|
+
[25.773, "o", " \u2713 Not reachable on network interfaces (probed live)\n"]
|
|
512
|
+
[25.785, "o", " \u2713 Authentication required (WebSocket probe confirmed)\n"]
|
|
513
|
+
[25.797, "o", " \u2713 /health endpoint does not leak sensitive data\n"]
|
|
514
|
+
[25.809, "o", " \u2713 CORS not open to arbitrary origins\n"]
|
|
515
|
+
[25.821, "o", "\n"]
|
|
516
|
+
[25.833, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
517
|
+
[25.845, "o", " Security Score: 70/100 \u2503 Grade: C\n"]
|
|
518
|
+
[25.857, "o", " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2591 70%\n"]
|
|
519
|
+
[25.869, "o", "\n"]
|
|
520
|
+
[25.881, "o", " Verdict: Your instance has HIGH-risk issues. Fix before going to production.\n"]
|
|
521
|
+
[25.893, "o", "\n"]
|
|
522
|
+
[25.905, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
523
|
+
[25.917, "o", " HIGH (2 findings)\n"]
|
|
524
|
+
[25.929, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
525
|
+
[25.941, "o", "\n"]
|
|
526
|
+
[25.953, "o", " \u2717 Exec approval disabled \u2014 all shell commands run without confirmation\n"]
|
|
527
|
+
[25.965, "o", " tools.exec.ask=\"off\" means every shell command the agent triggers\n"]
|
|
528
|
+
[25.977, "o", " runs immediately with zero user approval. Any prompt injection or malicious\n"]
|
|
529
|
+
[25.989, "o", " skill can execute arbitrary commands on your system without you seeing them.\n"]
|
|
530
|
+
[26.001, "o", " Attack: attacker injects \"run rm -rf ~/important\" \u2014 it executes silently.\n"]
|
|
531
|
+
[26.013, "o", "\n"]
|
|
532
|
+
[26.025, "o", " Fix: openclaw config set tools.exec.ask always\n"]
|
|
533
|
+
[26.037, "o", " # or, to allow a specific set without prompts:\n"]
|
|
534
|
+
[26.049, "o", " openctl config set tools.exec.ask on-miss\n"]
|
|
535
|
+
[26.061, "o", " openctl config set tools.exec.allowed '[\"git\",\"npm\",\"node\"]'\n"]
|
|
536
|
+
[26.073, "o", "\n"]
|
|
537
|
+
[26.085, "o", " \u2717 API key patterns found in ~/.openclaw/ JSON files (3 files)\n"]
|
|
538
|
+
[26.097, "o", " The following JSON files in ~/.openclaw/ contain patterns matching API keys or secrets:\n"]
|
|
539
|
+
[26.109, "o", " \u2022 agent-accounts.json\n"]
|
|
540
|
+
[26.121, "o", " \u2022 exec-approvals.json\n"]
|
|
541
|
+
[26.133, "o", " \u2022 openclaw.json\n"]
|
|
542
|
+
[26.145, "o", " \n"]
|
|
543
|
+
[26.157, "o", " Note: Only key name patterns are detected \u2014 actual values are never read or stored.\n"]
|
|
544
|
+
[26.169, "o", " Credentials in the wrong files may be at risk if file permissions are too open.\n"]
|
|
545
|
+
[26.181, "o", "\n"]
|
|
546
|
+
[26.193, "o", " Fix: Ensure all credential files use 0600 permissions:\n"]
|
|
547
|
+
[26.205, "o", " chmod 600 ~/.openclaw/*.json\n"]
|
|
548
|
+
[26.217, "o", " \n"]
|
|
549
|
+
[26.229, "o", " If credentials are in unexpected files, move them to agent-accounts.json.\n"]
|
|
550
|
+
[26.241, "o", "\n"]
|
|
551
|
+
[26.253, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
552
|
+
[26.265, "o", " PASSED (35 checks)\n"]
|
|
553
|
+
[26.277, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
554
|
+
[26.289, "o", " \u2713 Gateway bound to loopback only\n"]
|
|
555
|
+
[26.301, "o", " \u2713 Tailscale Funnel not enabled\n"]
|
|
556
|
+
[26.313, "o", " \u2713 Auth token is strong\n"]
|
|
557
|
+
[26.325, "o", " \u2713 No dangerous flags enabled\n"]
|
|
558
|
+
[26.337, "o", " \u2713 mDNS mode: \"minimal\" (not leaking sensitive data)\n"]
|
|
559
|
+
[26.349, "o", " \u2713 Real-IP fallback disabled\n"]
|
|
560
|
+
[26.361, "o", " \u2713 Gateway is loopback-only \u2014 trustedProxies not needed\n"]
|
|
561
|
+
[26.373, "o", " \u2713 Trust model appropriate for current channel configuration\n"]
|
|
562
|
+
[26.385, "o", " \u2713 ~/.openclaw/ is owner-only (700)\n"]
|
|
563
|
+
[26.397, "o", " \u2713 openclaw.json is owner-only (600)\n"]
|
|
564
|
+
[26.409, "o", " \u2713 agent-accounts.json is owner-only (600)\n"]
|
|
565
|
+
[26.421, "o", " \u2713 credentials/ directory is locked down\n"]
|
|
566
|
+
[26.433, "o", " \u2713 Session transcripts are private\n"]
|
|
567
|
+
[26.445, "o", " \u2713 Telegram DM policy: \"pairing\" (restricted)\n"]
|
|
568
|
+
[26.457, "o", " \u2713 All group policies use allowlist\n"]
|
|
569
|
+
[26.469, "o", " \u2713 No open groups with elevated tools (safe)\n"]
|
|
570
|
+
[26.481, "o", " \u2713 DM sessions are isolated per user\n"]
|
|
571
|
+
[26.493, "o", " \u2713 Agent sandbox mode: \"all\" (sessions isolated)\n"]
|
|
572
|
+
[26.505, "o", " \u2713 exec sandbox configuration is consistent\n"]
|
|
573
|
+
[26.517, "o", " \u2713 Thinking stream not leaking reasoning\n"]
|
|
574
|
+
[26.529, "o", " \u2713 Elevated tools not configured\n"]
|
|
575
|
+
[26.541, "o", " \u2713 Filesystem restricted to workspace\n"]
|
|
576
|
+
[26.553, "o", " \u2713 apply_patch restricted to workspace\n"]
|
|
577
|
+
[26.565, "o", " \u2713 Browser SSRF to private networks blocked\n"]
|
|
578
|
+
[26.577, "o", " \u2713 Plugin allowlist configured\n"]
|
|
579
|
+
[26.589, "o", " \u2713 Log redaction enabled\n"]
|
|
580
|
+
[26.601, "o", " \u2713 OpenClaw 2026.2.26 (up to date)\n"]
|
|
581
|
+
[26.613, "o", " \u2713 Webhooks cannot control session routing\n"]
|
|
582
|
+
[26.625, "o", " \u2713 No webhook token configured\n"]
|
|
583
|
+
[26.637, "o", " \u2713 All channel allowFrom settings are restricted\n"]
|
|
584
|
+
[26.649, "o", " \u2713 All credential date fields are within 90 days\n"]
|
|
585
|
+
[26.661, "o", " \u2713 All installed skills have explicit version pins\n"]
|
|
586
|
+
[26.673, "o", " \u2713 Workspace directory not found \u2014 git credential leak check skipped\n"]
|
|
587
|
+
[26.685, "o", " \u2713 ~/.openclaw/ directory permissions are secure (700)\n"]
|
|
588
|
+
[26.697, "o", " \u2713 Credential file permissions are secure (all \u2264 0600)\n"]
|
|
589
|
+
[26.709, "o", "\n"]
|
|
590
|
+
[26.721, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
591
|
+
[26.733, "o", " 2 issues found. Fix above to improve score.\n"]
|
|
592
|
+
[26.745, "o", " Run clawarmor scan to check installed skills.\n"]
|
|
593
|
+
[26.757, "o", " Run clawarmor trend to see score history.\n"]
|
|
594
|
+
[26.769, "o", " Continuous monitoring: github.com/pinzasai/clawarmor\n"]
|
|
595
|
+
[26.781, "o", "\n"]
|
|
596
|
+
[26.793, "o", "\n"]
|
|
597
|
+
[26.805, "o", " ! Config changed since last clean audit\n"]
|
|
598
|
+
[26.817, "o", " Size: 4751 \u2192 5295 bytes (+544)\n"]
|
|
599
|
+
[26.829, "o", " Lines: 186 \u2192 197 (+11)\n"]
|
|
600
|
+
[26.841, "o", " Hash: fda00bbb0ac793f7 \u2192 d757fa979eb98f41\n"]
|
|
601
|
+
[26.853, "o", " Baseline set: 2026-03-01\n"]
|
|
602
|
+
[26.865, "o", " Run clawarmor audit --accept-changes to update baseline\n"]
|
|
603
|
+
[26.877, "o", ""]
|
|
604
|
+
[29.889, "o", "$ "]
|
|
605
|
+
[30.397, "o", "#"]
|
|
606
|
+
[30.427, "o", " "]
|
|
607
|
+
[30.457, "o", "B"]
|
|
608
|
+
[30.487, "o", "o"]
|
|
609
|
+
[30.517, "o", "n"]
|
|
610
|
+
[30.547, "o", "u"]
|
|
611
|
+
[30.577, "o", "s"]
|
|
612
|
+
[30.607, "o", ":"]
|
|
613
|
+
[30.637, "o", " "]
|
|
614
|
+
[30.667, "o", "F"]
|
|
615
|
+
[30.697, "o", "u"]
|
|
616
|
+
[30.727, "o", "l"]
|
|
617
|
+
[30.757, "o", "l"]
|
|
618
|
+
[30.787, "o", " "]
|
|
619
|
+
[30.817, "o", "s"]
|
|
620
|
+
[30.847, "o", "e"]
|
|
621
|
+
[30.877, "o", "c"]
|
|
622
|
+
[30.907, "o", "u"]
|
|
623
|
+
[30.937, "o", "r"]
|
|
624
|
+
[30.967, "o", "i"]
|
|
625
|
+
[30.997, "o", "t"]
|
|
626
|
+
[31.027, "o", "y"]
|
|
627
|
+
[31.057, "o", " "]
|
|
628
|
+
[31.087, "o", "d"]
|
|
629
|
+
[31.117, "o", "a"]
|
|
630
|
+
[31.147, "o", "s"]
|
|
631
|
+
[31.177, "o", "h"]
|
|
632
|
+
[31.207, "o", "b"]
|
|
633
|
+
[31.237, "o", "o"]
|
|
634
|
+
[31.267, "o", "a"]
|
|
635
|
+
[31.297, "o", "r"]
|
|
636
|
+
[31.327, "o", "d"]
|
|
637
|
+
[31.357, "o", "\n"]
|
|
638
|
+
[31.457, "o", "$ "]
|
|
639
|
+
[31.965, "o", "c"]
|
|
640
|
+
[32.01, "o", "l"]
|
|
641
|
+
[32.055, "o", "a"]
|
|
642
|
+
[32.1, "o", "w"]
|
|
643
|
+
[32.145, "o", "a"]
|
|
644
|
+
[32.19, "o", "r"]
|
|
645
|
+
[32.235, "o", "m"]
|
|
646
|
+
[32.28, "o", "o"]
|
|
647
|
+
[32.325, "o", "r"]
|
|
648
|
+
[32.37, "o", " "]
|
|
649
|
+
[32.415, "o", "s"]
|
|
650
|
+
[32.46, "o", "t"]
|
|
651
|
+
[32.505, "o", "a"]
|
|
652
|
+
[32.55, "o", "t"]
|
|
653
|
+
[32.595, "o", "u"]
|
|
654
|
+
[32.64, "o", "s"]
|
|
655
|
+
[32.685, "o", "\n"]
|
|
656
|
+
[32.885, "o", "\n"]
|
|
657
|
+
[32.897, "o", " \u2139 Config: local (~/.openclaw/openclaw.json)\n"]
|
|
658
|
+
[32.909, "o", " Probes: 127.0.0.1:18789 (local)\n"]
|
|
659
|
+
[32.921, "o", " Sends nothing. Source: github.com/pinzasai/clawarmor\n"]
|
|
660
|
+
[32.933, "o", "\n"]
|
|
661
|
+
[32.945, "o", " ClawArmor v2.0.0 \u2014 Security Status\n"]
|
|
662
|
+
[32.957, "o", "\n"]
|
|
663
|
+
[32.969, "o", " Posture D 50/100 \u2014\n"]
|
|
664
|
+
[32.981, "o", " Last audit 0s ago (manual)\n"]
|
|
665
|
+
[32.993, "o", " Watcher \u25cf running (PID 16371)\n"]
|
|
666
|
+
[33.005, "o", " Intercept \u2713 active (~/.zshrc)\n"]
|
|
667
|
+
[33.017, "o", " Audit log 36 events (clawarmor log to view)\n"]
|
|
668
|
+
[33.029, "o", "\n"]
|
|
669
|
+
[33.041, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
670
|
+
[33.053, "o", " Skills 0 installed (clawarmor scan to check)\n"]
|
|
671
|
+
[33.065, "o", " Config No baseline yet \u2014 run: clawarmor audit\n"]
|
|
672
|
+
[33.077, "o", " Credentials 6 tokens, oldest: 1d \u2713\n"]
|
|
673
|
+
[33.089, "o", "\n"]
|
|
674
|
+
[33.101, "o", "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"]
|
|
675
|
+
[33.113, "o", " Next digest not scheduled (run: clawarmor protect --install)\n"]
|
|
676
|
+
[33.125, "o", "\n"]
|
|
677
|
+
[33.137, "o", " Full protection: [\u2713 YES]\n"]
|
|
678
|
+
[33.149, "o", "\n"]
|
|
679
|
+
[33.161, "o", ""]
|
|
680
|
+
[36.173, "o", "$ "]
|
package/demo.gif
ADDED
|
Binary file
|
package/lib/fix.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// clawarmor fix — auto-apply safe one-liner fixes
|
|
2
|
+
// Now with impact classification: safe / caution / breaking
|
|
2
3
|
import { readFileSync } from 'fs';
|
|
3
4
|
import { homedir } from 'os';
|
|
4
5
|
import { join } from 'path';
|
|
@@ -9,57 +10,90 @@ import { loadConfig, get } from './config.js';
|
|
|
9
10
|
const HISTORY_PATH = join(homedir(), '.clawarmor', 'history.json');
|
|
10
11
|
const SEP = paint.dim('─'.repeat(52));
|
|
11
12
|
|
|
13
|
+
// Impact levels
|
|
14
|
+
const IMPACT = { SAFE: 'safe', CAUTION: 'caution', BREAKING: 'breaking' };
|
|
15
|
+
|
|
16
|
+
const IMPACT_BADGE = {
|
|
17
|
+
[IMPACT.SAFE]: () => paint.green('🟢 Safe'),
|
|
18
|
+
[IMPACT.CAUTION]: () => paint.yellow('🟡 Caution'),
|
|
19
|
+
[IMPACT.BREAKING]: () => paint.red('🔴 Breaking'),
|
|
20
|
+
};
|
|
21
|
+
|
|
12
22
|
// Fixes that are safe to auto-apply (single config set command, no restart risk)
|
|
13
23
|
const AUTO_FIXABLE = {
|
|
14
24
|
'browser.ssrf': {
|
|
15
25
|
cmd: 'openclaw config set browser.ssrfPolicy.dangerouslyAllowPrivateNetwork false',
|
|
16
26
|
desc: 'Block browser SSRF to private networks',
|
|
17
27
|
needsRestart: true,
|
|
28
|
+
impact: IMPACT.CAUTION,
|
|
29
|
+
impactDetail: 'Browser tool will no longer be able to access local/private network URLs.\n' +
|
|
30
|
+
' If your agent browses internal dashboards or local services, those will be blocked.',
|
|
18
31
|
},
|
|
19
32
|
'discovery.mdns': {
|
|
20
33
|
cmd: 'openclaw config set discovery.mdns.mode minimal',
|
|
21
34
|
desc: 'Set mDNS to minimal mode',
|
|
22
35
|
needsRestart: true,
|
|
36
|
+
impact: IMPACT.SAFE,
|
|
37
|
+
impactDetail: 'Only reduces network advertisement. No functionality change.',
|
|
23
38
|
},
|
|
24
39
|
'logging.redact': {
|
|
25
40
|
cmd: 'openclaw config set logging.redactSensitive tools',
|
|
26
41
|
desc: 'Enable log redaction',
|
|
27
42
|
needsRestart: false,
|
|
43
|
+
impact: IMPACT.SAFE,
|
|
44
|
+
impactDetail: 'Redacts sensitive data from logs. No functionality change.',
|
|
28
45
|
},
|
|
29
46
|
'tools.fs.workspaceOnly': {
|
|
30
47
|
cmd: 'openclaw config set tools.fs.workspaceOnly true',
|
|
31
48
|
desc: 'Restrict filesystem to workspace',
|
|
32
49
|
needsRestart: true,
|
|
50
|
+
impact: IMPACT.BREAKING,
|
|
51
|
+
impactDetail: 'Agent will ONLY be able to read/write files inside the workspace directory.\n' +
|
|
52
|
+
' Access to home directory, system files, and other paths will be blocked.\n' +
|
|
53
|
+
' Skills or workflows that read files outside workspace will break.',
|
|
33
54
|
},
|
|
34
55
|
'tools.applyPatch.workspaceOnly': {
|
|
35
56
|
cmd: 'openclaw config set tools.exec.applyPatch.workspaceOnly true',
|
|
36
57
|
desc: 'Restrict apply_patch to workspace',
|
|
37
58
|
needsRestart: true,
|
|
59
|
+
impact: IMPACT.CAUTION,
|
|
60
|
+
impactDetail: 'Patches can only be applied to files in the workspace. Usually fine unless\n' +
|
|
61
|
+
' your agent patches system files or configs outside the workspace.',
|
|
38
62
|
},
|
|
39
63
|
'fs.config.perms': {
|
|
40
64
|
cmd: 'chmod 600 ~/.openclaw/openclaw.json',
|
|
41
65
|
desc: 'Lock down config file permissions',
|
|
42
66
|
needsRestart: false,
|
|
43
67
|
shell: true,
|
|
68
|
+
impact: IMPACT.SAFE,
|
|
69
|
+
impactDetail: 'Only restricts other system users. Your agent runs as you.',
|
|
44
70
|
},
|
|
45
71
|
'fs.accounts.perms': {
|
|
46
72
|
cmd: 'chmod 600 ~/.openclaw/agent-accounts.json',
|
|
47
73
|
desc: 'Lock down credentials file permissions',
|
|
48
74
|
needsRestart: false,
|
|
49
75
|
shell: true,
|
|
76
|
+
impact: IMPACT.SAFE,
|
|
77
|
+
impactDetail: 'Only restricts other system users. Your agent runs as you.',
|
|
50
78
|
},
|
|
51
79
|
'agents.sandbox': {
|
|
52
|
-
// Multi-step fix: enables sandbox WITH workspace access so Telegram sessions don't lose memory files
|
|
53
80
|
cmd: "openclaw config set agents.defaults.sandbox.mode non-main && openclaw config set agents.defaults.sandbox.workspaceAccess rw && openclaw config set agents.defaults.sandbox.scope session",
|
|
54
81
|
desc: 'Enable sandbox isolation (with workspace access preserved for Telegram/group sessions)',
|
|
55
82
|
needsRestart: true,
|
|
56
83
|
requiresDocker: true,
|
|
84
|
+
impact: IMPACT.BREAKING,
|
|
85
|
+
impactDetail: 'Non-main sessions will run inside Docker containers.\n' +
|
|
86
|
+
' Requires Docker Desktop to be installed and running.\n' +
|
|
87
|
+
' Telegram/group sessions will lose direct host access (shell commands,\n' +
|
|
88
|
+
' file reads outside workspace). Workspace files remain accessible.',
|
|
57
89
|
},
|
|
58
90
|
'fs.dir.perms': {
|
|
59
91
|
cmd: 'chmod 700 ~/.openclaw',
|
|
60
92
|
desc: 'Lock down ~/.openclaw directory',
|
|
61
93
|
needsRestart: false,
|
|
62
94
|
shell: true,
|
|
95
|
+
impact: IMPACT.SAFE,
|
|
96
|
+
impactDetail: 'Only restricts other system users from listing the directory.',
|
|
63
97
|
},
|
|
64
98
|
};
|
|
65
99
|
|
|
@@ -71,7 +105,7 @@ function box(title) {
|
|
|
71
105
|
}
|
|
72
106
|
|
|
73
107
|
export async function runFix(flags = {}) {
|
|
74
|
-
console.log(''); console.log(box('ClawArmor Fix
|
|
108
|
+
console.log(''); console.log(box('ClawArmor Fix v2.1')); console.log('');
|
|
75
109
|
|
|
76
110
|
// Load last audit failures
|
|
77
111
|
let lastFailed = [];
|
|
@@ -89,8 +123,13 @@ export async function runFix(flags = {}) {
|
|
|
89
123
|
const fixable = lastFailed.filter(id => AUTO_FIXABLE[id]);
|
|
90
124
|
const manual = lastFailed.filter(id => !AUTO_FIXABLE[id]);
|
|
91
125
|
|
|
126
|
+
const safeFixes = fixable.filter(id => AUTO_FIXABLE[id].impact === IMPACT.SAFE);
|
|
127
|
+
const cautionFixes = fixable.filter(id => AUTO_FIXABLE[id].impact === IMPACT.CAUTION);
|
|
128
|
+
const breakingFixes = fixable.filter(id => AUTO_FIXABLE[id].impact === IMPACT.BREAKING);
|
|
129
|
+
|
|
92
130
|
console.log(` ${paint.dim('Last audit had')} ${paint.bold(String(lastFailed.length))} ${paint.dim('failing checks.')}`);
|
|
93
|
-
console.log(` ${paint.bold(String(fixable.length))} ${paint.dim('can be auto-fixed.')} ${paint.dim(String(
|
|
131
|
+
console.log(` ${paint.bold(String(fixable.length))} ${paint.dim('can be auto-fixed:')} ${paint.green(String(safeFixes.length) + ' safe')} ${paint.dim('·')} ${paint.yellow(String(cautionFixes.length) + ' caution')} ${paint.dim('·')} ${paint.red(String(breakingFixes.length) + ' breaking')}`);
|
|
132
|
+
console.log(` ${paint.dim(String(manual.length))} ${paint.dim('require manual steps.')}`);
|
|
94
133
|
|
|
95
134
|
if (!fixable.length) {
|
|
96
135
|
console.log('');
|
|
@@ -99,40 +138,60 @@ export async function runFix(flags = {}) {
|
|
|
99
138
|
console.log(''); return 0;
|
|
100
139
|
}
|
|
101
140
|
|
|
102
|
-
// Load config for mainKey check
|
|
141
|
+
// Load config for mainKey check
|
|
103
142
|
const { config: cfg } = loadConfig();
|
|
104
143
|
const mainKey = get(cfg, 'agents.mainKey', 'main');
|
|
105
144
|
|
|
106
145
|
console.log('');
|
|
107
146
|
if (flags.dryRun) {
|
|
108
147
|
console.log(` ${paint.cyan('Dry run — would apply:')}`);
|
|
148
|
+
console.log('');
|
|
109
149
|
for (const id of fixable) {
|
|
110
150
|
const f = AUTO_FIXABLE[id];
|
|
151
|
+
const badge = IMPACT_BADGE[f.impact]();
|
|
111
152
|
if (id === 'agents.sandbox' && mainKey !== 'main') {
|
|
112
153
|
console.log(` ${paint.yellow('⚠')} ${paint.bold('Custom mainKey detected:')} agents.mainKey="${mainKey}"`);
|
|
113
154
|
console.log(` ${paint.dim('Verify that sandbox.mode=non-main won\'t affect your main session.')}`);
|
|
114
155
|
}
|
|
115
|
-
console.log(` ${paint.dim('→')} ${f.desc}`);
|
|
116
|
-
console.log(`
|
|
117
|
-
|
|
118
|
-
if (f.
|
|
156
|
+
console.log(` ${badge} ${paint.dim('→')} ${f.desc}`);
|
|
157
|
+
console.log(` ${paint.dim('Impact:')} ${f.impactDetail}`);
|
|
158
|
+
console.log(` ${paint.dim(f.cmd)}`);
|
|
159
|
+
if (f.requiresDocker) console.log(` ${paint.yellow('⚠')} ${paint.dim('Requires Docker Desktop to be installed and running')}`);
|
|
160
|
+
if (f.needsRestart) console.log(` ${paint.dim('↻ gateway restart required after applying')}`);
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --apply')} ${paint.dim('to apply safe + caution fixes.')}`);
|
|
164
|
+
if (breakingFixes.length) {
|
|
165
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --apply --force')} ${paint.dim('to apply ALL fixes (including breaking).')}`);
|
|
119
166
|
}
|
|
120
|
-
console.log('');
|
|
121
|
-
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --apply')} ${paint.dim('to apply these fixes.')}`);
|
|
122
167
|
console.log(''); return 0;
|
|
123
168
|
}
|
|
124
169
|
|
|
125
170
|
if (!flags.apply) {
|
|
126
|
-
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --dry-run')} ${paint.dim('to preview fixes.')}`);
|
|
127
|
-
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --apply')} ${paint.dim('to apply
|
|
171
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --dry-run')} ${paint.dim('to preview fixes with impact analysis.')}`);
|
|
172
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --apply')} ${paint.dim('to apply safe + caution fixes.')}`);
|
|
173
|
+
if (breakingFixes.length) {
|
|
174
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor fix --apply --force')} ${paint.dim('to apply ALL fixes (including breaking).')}`);
|
|
175
|
+
}
|
|
128
176
|
console.log(''); return 0;
|
|
129
177
|
}
|
|
130
178
|
|
|
131
179
|
// Apply fixes
|
|
132
|
-
let applied = 0, failed = 0, needsRestart = false;
|
|
180
|
+
let applied = 0, failed = 0, skippedBreaking = 0, needsRestart = false;
|
|
133
181
|
console.log(SEP);
|
|
134
182
|
for (const id of fixable) {
|
|
135
183
|
const f = AUTO_FIXABLE[id];
|
|
184
|
+
const badge = IMPACT_BADGE[f.impact]();
|
|
185
|
+
|
|
186
|
+
// Skip breaking unless --force
|
|
187
|
+
if (f.impact === IMPACT.BREAKING && !flags.force) {
|
|
188
|
+
console.log(` ${badge} ${f.desc}`);
|
|
189
|
+
console.log(` ${paint.dim('Impact:')} ${f.impactDetail}`);
|
|
190
|
+
console.log(` ${paint.red('⊘ Skipped')} ${paint.dim('(breaking — use --apply --force to include)')}`);
|
|
191
|
+
console.log('');
|
|
192
|
+
skippedBreaking++;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
136
195
|
|
|
137
196
|
// Warn about custom mainKey before sandbox fix
|
|
138
197
|
if (id === 'agents.sandbox' && mainKey !== 'main') {
|
|
@@ -140,7 +199,7 @@ export async function runFix(flags = {}) {
|
|
|
140
199
|
console.log(` ${paint.dim('Verify that sandbox.mode=non-main won\'t affect your main session.')}`);
|
|
141
200
|
}
|
|
142
201
|
|
|
143
|
-
process.stdout.write(` ${
|
|
202
|
+
process.stdout.write(` ${badge} ${f.desc}...`);
|
|
144
203
|
// Check Docker requirement
|
|
145
204
|
if (f.requiresDocker) {
|
|
146
205
|
const dockerCheck = spawnSync('docker', ['info'], { stdio: 'ignore' });
|
|
@@ -162,6 +221,9 @@ export async function runFix(flags = {}) {
|
|
|
162
221
|
}
|
|
163
222
|
|
|
164
223
|
console.log('');
|
|
224
|
+
if (skippedBreaking > 0) {
|
|
225
|
+
console.log(` ${paint.dim(`${skippedBreaking} breaking fix${skippedBreaking !== 1 ? 'es' : ''} skipped — use --apply --force to include`)}`);
|
|
226
|
+
}
|
|
165
227
|
if (needsRestart) {
|
|
166
228
|
console.log(` ${paint.yellow('!')} ${paint.bold('Restart required:')} ${paint.dim('openclaw gateway restart')}`);
|
|
167
229
|
}
|
package/lib/harden.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Modes:
|
|
3
3
|
// default: show each fix, prompt y/N before applying
|
|
4
4
|
// --dry-run: show what WOULD be fixed, no writes
|
|
5
|
-
// --auto: apply all safe fixes without confirmation (
|
|
5
|
+
// --auto: apply all safe + caution fixes without confirmation (skips breaking)
|
|
6
|
+
// --auto --force: apply ALL fixes including breaking ones
|
|
6
7
|
|
|
7
8
|
import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
|
|
8
9
|
import { join } from 'path';
|
|
@@ -20,6 +21,29 @@ const HISTORY_FILE = join(CLAWARMOR_DIR, 'history.json');
|
|
|
20
21
|
const CLI_PATH = new URL('../cli.js', import.meta.url).pathname;
|
|
21
22
|
const SEP = paint.dim('─'.repeat(52));
|
|
22
23
|
|
|
24
|
+
// ── Impact levels ─────────────────────────────────────────────────────────────
|
|
25
|
+
// SAFE: No functionality impact. Pure security improvement.
|
|
26
|
+
// CAUTION: May change agent behavior. User should be aware.
|
|
27
|
+
// BREAKING: Will disable or restrict features currently in use.
|
|
28
|
+
|
|
29
|
+
const IMPACT = {
|
|
30
|
+
SAFE: 'safe',
|
|
31
|
+
CAUTION: 'caution',
|
|
32
|
+
BREAKING: 'breaking',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const IMPACT_BADGE = {
|
|
36
|
+
[IMPACT.SAFE]: (s) => paint.green(`🟢 Safe`),
|
|
37
|
+
[IMPACT.CAUTION]: (s) => paint.yellow(`🟡 Caution`),
|
|
38
|
+
[IMPACT.BREAKING]: (s) => paint.red(`🔴 Breaking`),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const IMPACT_LABEL = {
|
|
42
|
+
[IMPACT.SAFE]: 'No functionality impact',
|
|
43
|
+
[IMPACT.CAUTION]: 'May change agent behavior',
|
|
44
|
+
[IMPACT.BREAKING]: 'Will disable or restrict features you\'re actively using',
|
|
45
|
+
};
|
|
46
|
+
|
|
23
47
|
function box(title) {
|
|
24
48
|
const W = 52, pad = W - 2 - title.length, l = Math.floor(pad / 2), r = pad - l;
|
|
25
49
|
return [
|
|
@@ -56,12 +80,23 @@ function buildFixes(config) {
|
|
|
56
80
|
// Fix 1: world/group-readable credential files — chmod 600
|
|
57
81
|
const badFiles = findWorldReadableCredFiles();
|
|
58
82
|
for (const f of badFiles) {
|
|
83
|
+
// Classify: credential files (tokens, keys) are safe to lock down.
|
|
84
|
+
// Config files that other tools might read need caution.
|
|
85
|
+
const isSensitive = /\.(env|json|key|pem|token|secret)$/i.test(f.name) ||
|
|
86
|
+
f.name === 'agent-accounts.json' ||
|
|
87
|
+
f.name === 'openclaw.json';
|
|
88
|
+
const isScript = /\.(py|sh|js|ts)$/i.test(f.name);
|
|
89
|
+
|
|
59
90
|
fixes.push({
|
|
60
91
|
id: `cred.perms.${f.name}`,
|
|
61
92
|
problem: `${f.name} is readable by other users (permissions: ${f.mode})`,
|
|
62
93
|
action: `chmod 600 ${f.path}`,
|
|
63
94
|
description: `Set permissions to 600 (owner-only) on ${f.path}`,
|
|
64
95
|
type: 'shell',
|
|
96
|
+
impact: IMPACT.SAFE,
|
|
97
|
+
impactDetail: isScript
|
|
98
|
+
? 'Only restricts other system users. Scripts will still run as you.'
|
|
99
|
+
: 'Only restricts other system users from reading this file. Your agent is unaffected.',
|
|
65
100
|
manualNote: null,
|
|
66
101
|
});
|
|
67
102
|
}
|
|
@@ -75,6 +110,11 @@ function buildFixes(config) {
|
|
|
75
110
|
action: 'openclaw config set gateway.host 127.0.0.1',
|
|
76
111
|
description: 'Change gateway.host to 127.0.0.1 (loopback only)',
|
|
77
112
|
type: 'openclaw',
|
|
113
|
+
impact: IMPACT.BREAKING,
|
|
114
|
+
impactDetail: 'Remote connections to the gateway will stop working.\n' +
|
|
115
|
+
' If you access OpenClaw from other devices on your network (e.g. phone,\n' +
|
|
116
|
+
' tablet, or another computer), those connections will be blocked.\n' +
|
|
117
|
+
' Only localhost access will work after this change.',
|
|
78
118
|
manualNote: 'Restart gateway after applying: openclaw gateway restart',
|
|
79
119
|
});
|
|
80
120
|
}
|
|
@@ -85,9 +125,14 @@ function buildFixes(config) {
|
|
|
85
125
|
fixes.push({
|
|
86
126
|
id: 'exec.ask.off',
|
|
87
127
|
problem: 'exec.ask is off — shell commands run without user confirmation',
|
|
88
|
-
action: 'openclaw config set exec.ask
|
|
89
|
-
description: 'Enable exec
|
|
128
|
+
action: 'openclaw config set tools.exec.ask on-miss',
|
|
129
|
+
description: 'Enable exec approval for unrecognized commands',
|
|
90
130
|
type: 'openclaw',
|
|
131
|
+
impact: IMPACT.BREAKING,
|
|
132
|
+
impactDetail: 'Your agent will need approval for shell commands not in the allowlist.\n' +
|
|
133
|
+
' Autonomous workflows (cron jobs, background tasks, sub-agents) that run\n' +
|
|
134
|
+
' shell commands may pause waiting for approval.\n' +
|
|
135
|
+
' You\'ll need to approve commands via the web UI or CLI.',
|
|
91
136
|
manualNote: 'Restart gateway after applying: openclaw gateway restart',
|
|
92
137
|
});
|
|
93
138
|
}
|
|
@@ -157,10 +202,24 @@ function getManualItems() {
|
|
|
157
202
|
];
|
|
158
203
|
}
|
|
159
204
|
|
|
205
|
+
// ── Print impact summary ──────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
function printImpactSummary(fixes) {
|
|
208
|
+
const counts = { [IMPACT.SAFE]: 0, [IMPACT.CAUTION]: 0, [IMPACT.BREAKING]: 0 };
|
|
209
|
+
for (const fix of fixes) counts[fix.impact]++;
|
|
210
|
+
|
|
211
|
+
const parts = [];
|
|
212
|
+
if (counts[IMPACT.SAFE]) parts.push(paint.green(`${counts[IMPACT.SAFE]} safe`));
|
|
213
|
+
if (counts[IMPACT.CAUTION]) parts.push(paint.yellow(`${counts[IMPACT.CAUTION]} caution`));
|
|
214
|
+
if (counts[IMPACT.BREAKING]) parts.push(paint.red(`${counts[IMPACT.BREAKING]} breaking`));
|
|
215
|
+
|
|
216
|
+
return parts.join(paint.dim(' · '));
|
|
217
|
+
}
|
|
218
|
+
|
|
160
219
|
// ── Main export ───────────────────────────────────────────────────────────────
|
|
161
220
|
|
|
162
221
|
export async function runHarden(flags = {}) {
|
|
163
|
-
console.log(''); console.log(box('ClawArmor Harden v2.
|
|
222
|
+
console.log(''); console.log(box('ClawArmor Harden v2.1')); console.log('');
|
|
164
223
|
|
|
165
224
|
const { config } = loadConfig();
|
|
166
225
|
const fixes = buildFixes(config);
|
|
@@ -170,6 +229,11 @@ export async function runHarden(flags = {}) {
|
|
|
170
229
|
const beforeScore = before?.score ?? null;
|
|
171
230
|
const beforeGrade = before?.grade ?? null;
|
|
172
231
|
|
|
232
|
+
// Count by impact
|
|
233
|
+
const safeFixes = fixes.filter(f => f.impact === IMPACT.SAFE);
|
|
234
|
+
const cautionFixes = fixes.filter(f => f.impact === IMPACT.CAUTION);
|
|
235
|
+
const breakingFixes = fixes.filter(f => f.impact === IMPACT.BREAKING);
|
|
236
|
+
|
|
173
237
|
// ── DRY RUN ────────────────────────────────────────────────────────────────
|
|
174
238
|
if (flags.dryRun) {
|
|
175
239
|
console.log(` ${paint.cyan('Dry run — showing what would be fixed (no changes applied):')}`);
|
|
@@ -183,17 +247,23 @@ export async function runHarden(flags = {}) {
|
|
|
183
247
|
}
|
|
184
248
|
|
|
185
249
|
for (const fix of fixes) {
|
|
250
|
+
const badge = IMPACT_BADGE[fix.impact]();
|
|
186
251
|
console.log(` ${paint.yellow('!')} ${paint.bold(fix.problem)}`);
|
|
187
252
|
console.log(` ${paint.dim('Fix:')} ${fix.description}`);
|
|
188
253
|
console.log(` ${paint.dim('Cmd:')} ${fix.action}`);
|
|
254
|
+
console.log(` ${badge}${paint.dim(':')} ${fix.impactDetail}`);
|
|
189
255
|
if (fix.manualNote) console.log(` ${paint.dim('Note:')} ${fix.manualNote}`);
|
|
190
256
|
console.log('');
|
|
191
257
|
}
|
|
192
258
|
|
|
193
259
|
console.log(SEP);
|
|
194
|
-
console.log(` ${fixes.length} fix${fixes.length !== 1 ? 'es' : ''} available
|
|
260
|
+
console.log(` ${fixes.length} fix${fixes.length !== 1 ? 'es' : ''} available: ${printImpactSummary(fixes)}`);
|
|
261
|
+
console.log('');
|
|
195
262
|
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor harden')} ${paint.dim('to apply interactively.')}`);
|
|
196
|
-
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor harden --auto')} ${paint.dim('to apply
|
|
263
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor harden --auto')} ${paint.dim('to apply safe + caution fixes.')}`);
|
|
264
|
+
if (breakingFixes.length) {
|
|
265
|
+
console.log(` ${paint.dim('Run')} ${paint.cyan('clawarmor harden --auto --force')} ${paint.dim('to apply ALL fixes (including breaking).')}`);
|
|
266
|
+
}
|
|
197
267
|
console.log('');
|
|
198
268
|
|
|
199
269
|
const manualItems = getManualItems();
|
|
@@ -223,25 +293,52 @@ export async function runHarden(flags = {}) {
|
|
|
223
293
|
return 0;
|
|
224
294
|
}
|
|
225
295
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
296
|
+
if (flags.auto) {
|
|
297
|
+
// --auto mode: apply safe + caution, SKIP breaking unless --force
|
|
298
|
+
const autoLabel = flags.force
|
|
299
|
+
? paint.cyan('Auto mode (--force) — applying ALL fixes including breaking')
|
|
300
|
+
: paint.cyan('Auto mode — applying safe + caution fixes (skipping breaking)');
|
|
301
|
+
console.log(` ${autoLabel}`);
|
|
302
|
+
console.log(` ${paint.dim('Fixes found:')} ${printImpactSummary(fixes)}`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(` ${paint.cyan('Interactive mode — review and apply fixes one by one')}`);
|
|
305
|
+
console.log(` ${paint.dim('Fixes found:')} ${printImpactSummary(fixes)}`);
|
|
306
|
+
}
|
|
230
307
|
console.log('');
|
|
231
308
|
|
|
232
|
-
let applied = 0, skipped = 0, failed = 0;
|
|
309
|
+
let applied = 0, skipped = 0, failed = 0, skippedBreaking = 0;
|
|
233
310
|
const restartNotes = [];
|
|
234
311
|
|
|
235
312
|
for (const fix of fixes) {
|
|
313
|
+
const badge = IMPACT_BADGE[fix.impact]();
|
|
314
|
+
|
|
236
315
|
console.log(SEP);
|
|
316
|
+
console.log(` ${badge}`);
|
|
237
317
|
console.log(` ${paint.bold('Problem:')} ${fix.problem}`);
|
|
238
318
|
console.log(` ${paint.dim('Fix:')} ${fix.description}`);
|
|
319
|
+
console.log(` ${paint.dim('Impact:')} ${fix.impactDetail}`);
|
|
239
320
|
console.log(` ${paint.dim('Command:')} ${paint.dim(fix.action)}`);
|
|
240
321
|
console.log('');
|
|
241
322
|
|
|
242
|
-
let doApply
|
|
323
|
+
let doApply;
|
|
243
324
|
|
|
244
|
-
if (
|
|
325
|
+
if (flags.auto) {
|
|
326
|
+
// In auto mode: apply safe + caution, skip breaking unless --force
|
|
327
|
+
if (fix.impact === IMPACT.BREAKING && !flags.force) {
|
|
328
|
+
console.log(` ${paint.red('⊘ Skipped')} ${paint.dim('(breaking — use --auto --force to include)')}`);
|
|
329
|
+
skippedBreaking++;
|
|
330
|
+
skipped++;
|
|
331
|
+
console.log('');
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
doApply = true;
|
|
335
|
+
} else {
|
|
336
|
+
// Interactive mode: always ask, but warn about breaking
|
|
337
|
+
if (fix.impact === IMPACT.BREAKING) {
|
|
338
|
+
console.log(` ${paint.red('⚠ This fix will change how your agent works.')}`);
|
|
339
|
+
console.log(` ${paint.red(' Read the impact above carefully before applying.')}`);
|
|
340
|
+
console.log('');
|
|
341
|
+
}
|
|
245
342
|
doApply = await askYN(` Apply this fix? [y/N] `);
|
|
246
343
|
}
|
|
247
344
|
|
|
@@ -268,6 +365,10 @@ export async function runHarden(flags = {}) {
|
|
|
268
365
|
console.log('');
|
|
269
366
|
console.log(` Applied: ${paint.green(String(applied))} Skipped: ${paint.dim(String(skipped))} Failed: ${failed > 0 ? paint.red(String(failed)) : paint.dim('0')}`);
|
|
270
367
|
|
|
368
|
+
if (skippedBreaking > 0) {
|
|
369
|
+
console.log(` ${paint.dim(`(${skippedBreaking} breaking fix${skippedBreaking !== 1 ? 'es' : ''} skipped — use --auto --force to include)`)}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
271
372
|
// Restart notes
|
|
272
373
|
if (restartNotes.length) {
|
|
273
374
|
console.log('');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawarmor",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Security armor for OpenClaw agents
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Security armor for OpenClaw agents \u2014 audit, scan, monitor",
|
|
5
5
|
"bin": {
|
|
6
6
|
"clawarmor": "cli.js"
|
|
7
7
|
},
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Generate a scripted asciinema .cast file for ClawArmor v2.0 demo.
|
|
4
|
+
Captures real command output with simulated typing for professional pacing.
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
CAST_FILE = os.path.expanduser("~/clawarmor/demo.cast")
|
|
12
|
+
COLS = 110
|
|
13
|
+
ROWS = 35
|
|
14
|
+
PROMPT = "$ "
|
|
15
|
+
|
|
16
|
+
def run_cmd(cmd):
|
|
17
|
+
"""Run a command and return its output."""
|
|
18
|
+
result = subprocess.run(cmd, shell=True, capture_output=True, text=True,
|
|
19
|
+
env={**os.environ, "FORCE_COLOR": "0", "NO_COLOR": "1", "TERM": "dumb"})
|
|
20
|
+
return result.stdout + result.stderr
|
|
21
|
+
|
|
22
|
+
def write_cast(events):
|
|
23
|
+
"""Write asciicast v2 file."""
|
|
24
|
+
header = {
|
|
25
|
+
"version": 2,
|
|
26
|
+
"width": COLS,
|
|
27
|
+
"height": ROWS,
|
|
28
|
+
"timestamp": int(time.time()),
|
|
29
|
+
"title": "ClawArmor v2.0 — Security Audit Demo",
|
|
30
|
+
"env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}
|
|
31
|
+
}
|
|
32
|
+
with open(CAST_FILE, 'w') as f:
|
|
33
|
+
f.write(json.dumps(header) + '\n')
|
|
34
|
+
for ts, etype, data in events:
|
|
35
|
+
f.write(json.dumps([round(ts, 6), etype, data]) + '\n')
|
|
36
|
+
print(f"Written to {CAST_FILE}")
|
|
37
|
+
|
|
38
|
+
def type_text(events, t, text, char_delay=0.045):
|
|
39
|
+
for ch in text:
|
|
40
|
+
events.append((t, "o", ch))
|
|
41
|
+
t += char_delay
|
|
42
|
+
return t
|
|
43
|
+
|
|
44
|
+
def output_text(events, t, text, line_delay=0.008):
|
|
45
|
+
lines = text.split('\n')
|
|
46
|
+
for i, line in enumerate(lines):
|
|
47
|
+
events.append((t, "o", line + ('\n' if i < len(lines) - 1 else '')))
|
|
48
|
+
t += line_delay
|
|
49
|
+
return t
|
|
50
|
+
|
|
51
|
+
def pause(t, seconds):
|
|
52
|
+
return t + seconds
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
events = []
|
|
56
|
+
t = 0.0
|
|
57
|
+
|
|
58
|
+
# Opening
|
|
59
|
+
t = output_text(events, t, PROMPT)
|
|
60
|
+
t = pause(t, 0.8)
|
|
61
|
+
|
|
62
|
+
# 1. Audit
|
|
63
|
+
t = type_text(events, t, "# Step 1: Check your OpenClaw security posture", 0.03)
|
|
64
|
+
events.append((t, "o", "\n")); t += 0.1
|
|
65
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
66
|
+
t = type_text(events, t, "clawarmor audit")
|
|
67
|
+
events.append((t, "o", "\n")); t += 0.2
|
|
68
|
+
output = run_cmd("clawarmor audit 2>&1")
|
|
69
|
+
t = output_text(events, t, output, 0.012)
|
|
70
|
+
t = pause(t, 3.0)
|
|
71
|
+
|
|
72
|
+
# 2. Harden dry-run
|
|
73
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
74
|
+
t = type_text(events, t, "# Step 2: See what ClawArmor can fix automatically", 0.03)
|
|
75
|
+
events.append((t, "o", "\n")); t += 0.1
|
|
76
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
77
|
+
t = type_text(events, t, "clawarmor harden --dry-run")
|
|
78
|
+
events.append((t, "o", "\n")); t += 0.2
|
|
79
|
+
output = run_cmd("clawarmor harden --dry-run 2>&1")
|
|
80
|
+
t = output_text(events, t, output, 0.012)
|
|
81
|
+
t = pause(t, 3.0)
|
|
82
|
+
|
|
83
|
+
# 3. Harden auto
|
|
84
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
85
|
+
t = type_text(events, t, "# Step 3: Fix everything automatically", 0.03)
|
|
86
|
+
events.append((t, "o", "\n")); t += 0.1
|
|
87
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
88
|
+
t = type_text(events, t, "clawarmor harden --auto")
|
|
89
|
+
events.append((t, "o", "\n")); t += 0.2
|
|
90
|
+
output = run_cmd("clawarmor harden --auto 2>&1")
|
|
91
|
+
t = output_text(events, t, output, 0.012)
|
|
92
|
+
t = pause(t, 2.5)
|
|
93
|
+
|
|
94
|
+
# 4. Re-audit
|
|
95
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
96
|
+
t = type_text(events, t, "# Step 4: Verify — re-audit after hardening", 0.03)
|
|
97
|
+
events.append((t, "o", "\n")); t += 0.1
|
|
98
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
99
|
+
t = type_text(events, t, "clawarmor audit")
|
|
100
|
+
events.append((t, "o", "\n")); t += 0.2
|
|
101
|
+
output = run_cmd("clawarmor audit 2>&1")
|
|
102
|
+
t = output_text(events, t, output, 0.012)
|
|
103
|
+
t = pause(t, 3.0)
|
|
104
|
+
|
|
105
|
+
# 5. Status
|
|
106
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
107
|
+
t = type_text(events, t, "# Bonus: Full security dashboard", 0.03)
|
|
108
|
+
events.append((t, "o", "\n")); t += 0.1
|
|
109
|
+
t = output_text(events, t, PROMPT); t = pause(t, 0.5)
|
|
110
|
+
t = type_text(events, t, "clawarmor status")
|
|
111
|
+
events.append((t, "o", "\n")); t += 0.2
|
|
112
|
+
output = run_cmd("clawarmor status 2>&1")
|
|
113
|
+
t = output_text(events, t, output, 0.012)
|
|
114
|
+
t = pause(t, 3.0)
|
|
115
|
+
|
|
116
|
+
# End
|
|
117
|
+
t = output_text(events, t, PROMPT)
|
|
118
|
+
t = pause(t, 1.0)
|
|
119
|
+
|
|
120
|
+
write_cast(events)
|
|
121
|
+
print(f"Total duration: {t:.1f}s")
|
|
122
|
+
print(f"Events: {len(events)}")
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|