clawhatch 0.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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -0
  3. package/dist/checks/cloud-sync.d.ts +10 -0
  4. package/dist/checks/cloud-sync.d.ts.map +1 -0
  5. package/dist/checks/cloud-sync.js +62 -0
  6. package/dist/checks/cloud-sync.js.map +1 -0
  7. package/dist/checks/data-protection.d.ts +9 -0
  8. package/dist/checks/data-protection.d.ts.map +1 -0
  9. package/dist/checks/data-protection.js +197 -0
  10. package/dist/checks/data-protection.js.map +1 -0
  11. package/dist/checks/identity.d.ts +14 -0
  12. package/dist/checks/identity.d.ts.map +1 -0
  13. package/dist/checks/identity.js +327 -0
  14. package/dist/checks/identity.js.map +1 -0
  15. package/dist/checks/model.d.ts +10 -0
  16. package/dist/checks/model.d.ts.map +1 -0
  17. package/dist/checks/model.js +337 -0
  18. package/dist/checks/model.js.map +1 -0
  19. package/dist/checks/network.d.ts +9 -0
  20. package/dist/checks/network.d.ts.map +1 -0
  21. package/dist/checks/network.js +177 -0
  22. package/dist/checks/network.js.map +1 -0
  23. package/dist/checks/operational.d.ts +9 -0
  24. package/dist/checks/operational.d.ts.map +1 -0
  25. package/dist/checks/operational.js +158 -0
  26. package/dist/checks/operational.js.map +1 -0
  27. package/dist/checks/sandbox.d.ts +9 -0
  28. package/dist/checks/sandbox.d.ts.map +1 -0
  29. package/dist/checks/sandbox.js +135 -0
  30. package/dist/checks/sandbox.js.map +1 -0
  31. package/dist/checks/secrets.d.ts +9 -0
  32. package/dist/checks/secrets.d.ts.map +1 -0
  33. package/dist/checks/secrets.js +816 -0
  34. package/dist/checks/secrets.js.map +1 -0
  35. package/dist/checks/skills.d.ts +9 -0
  36. package/dist/checks/skills.d.ts.map +1 -0
  37. package/dist/checks/skills.js +303 -0
  38. package/dist/checks/skills.js.map +1 -0
  39. package/dist/checks/tools.d.ts +9 -0
  40. package/dist/checks/tools.d.ts.map +1 -0
  41. package/dist/checks/tools.js +397 -0
  42. package/dist/checks/tools.js.map +1 -0
  43. package/dist/discover.d.ts +22 -0
  44. package/dist/discover.d.ts.map +1 -0
  45. package/dist/discover.js +281 -0
  46. package/dist/discover.js.map +1 -0
  47. package/dist/fixer.d.ts +16 -0
  48. package/dist/fixer.d.ts.map +1 -0
  49. package/dist/fixer.js +361 -0
  50. package/dist/fixer.js.map +1 -0
  51. package/dist/index.d.ts +16 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +230 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/init.d.ts +14 -0
  56. package/dist/init.d.ts.map +1 -0
  57. package/dist/init.js +108 -0
  58. package/dist/init.js.map +1 -0
  59. package/dist/notify.d.ts +28 -0
  60. package/dist/notify.d.ts.map +1 -0
  61. package/dist/notify.js +217 -0
  62. package/dist/notify.js.map +1 -0
  63. package/dist/parsers/config.d.ts +16 -0
  64. package/dist/parsers/config.d.ts.map +1 -0
  65. package/dist/parsers/config.js +54 -0
  66. package/dist/parsers/config.js.map +1 -0
  67. package/dist/parsers/env.d.ts +6 -0
  68. package/dist/parsers/env.d.ts.map +1 -0
  69. package/dist/parsers/env.js +35 -0
  70. package/dist/parsers/env.js.map +1 -0
  71. package/dist/parsers/jsonl.d.ts +12 -0
  72. package/dist/parsers/jsonl.d.ts.map +1 -0
  73. package/dist/parsers/jsonl.js +61 -0
  74. package/dist/parsers/jsonl.js.map +1 -0
  75. package/dist/parsers/markdown.d.ts +17 -0
  76. package/dist/parsers/markdown.d.ts.map +1 -0
  77. package/dist/parsers/markdown.js +57 -0
  78. package/dist/parsers/markdown.js.map +1 -0
  79. package/dist/reporter-html.d.ts +9 -0
  80. package/dist/reporter-html.d.ts.map +1 -0
  81. package/dist/reporter-html.js +581 -0
  82. package/dist/reporter-html.js.map +1 -0
  83. package/dist/reporter.d.ts +10 -0
  84. package/dist/reporter.d.ts.map +1 -0
  85. package/dist/reporter.js +133 -0
  86. package/dist/reporter.js.map +1 -0
  87. package/dist/sanitize.d.ts +17 -0
  88. package/dist/sanitize.d.ts.map +1 -0
  89. package/dist/sanitize.js +83 -0
  90. package/dist/sanitize.js.map +1 -0
  91. package/dist/scanner.d.ts +18 -0
  92. package/dist/scanner.d.ts.map +1 -0
  93. package/dist/scanner.js +236 -0
  94. package/dist/scanner.js.map +1 -0
  95. package/dist/scoring.d.ts +17 -0
  96. package/dist/scoring.d.ts.map +1 -0
  97. package/dist/scoring.js +47 -0
  98. package/dist/scoring.js.map +1 -0
  99. package/dist/telemetry.d.ts +16 -0
  100. package/dist/telemetry.d.ts.map +1 -0
  101. package/dist/telemetry.js +52 -0
  102. package/dist/telemetry.js.map +1 -0
  103. package/dist/threat-feed.d.ts +14 -0
  104. package/dist/threat-feed.d.ts.map +1 -0
  105. package/dist/threat-feed.js +133 -0
  106. package/dist/threat-feed.js.map +1 -0
  107. package/dist/types.d.ts +221 -0
  108. package/dist/types.d.ts.map +1 -0
  109. package/dist/types.js +11 -0
  110. package/dist/types.js.map +1 -0
  111. package/dist/utils.d.ts +12 -0
  112. package/dist/utils.d.ts.map +1 -0
  113. package/dist/utils.js +34 -0
  114. package/dist/utils.js.map +1 -0
  115. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Richard / Clawhatch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,348 @@
1
+ # clawhatch
2
+
3
+ > Security scanner for OpenClaw AI agents — 100-point audit with auto-fix.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/clawhatch.svg)](https://www.npmjs.com/package/clawhatch)
6
+ [![license](https://img.shields.io/npm/l/clawhatch.svg)](https://opensource.org/licenses/MIT)
7
+ [![node](https://img.shields.io/node/v/clawhatch.svg)](https://nodejs.org/)
8
+
9
+ Clawhatch runs **100 automated security checks** against your [OpenClaw](https://openclaw.com) installation, scores it on a 100-point scale (A+ to F), and can auto-fix safe issues. Think of it as `npm audit` for your AI agent.
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ npx clawhatch scan
15
+ ```
16
+
17
+ No installation required. Clawhatch auto-detects your OpenClaw installation at `~/.openclaw` (or `%APPDATA%\openclaw` on Windows).
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install -g clawhatch
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ # Basic scan — auto-detects your OpenClaw config
29
+ clawhatch scan
30
+
31
+ # Scan with workspace files (SOUL.md, skills, markdown)
32
+ clawhatch scan --workspace .
33
+
34
+ # Deep scan — analyze full session logs (slower)
35
+ clawhatch scan --deep
36
+
37
+ # JSON output for scripting and CI
38
+ clawhatch scan --json
39
+
40
+ # HTML report
41
+ clawhatch scan --format html
42
+
43
+ # Auto-fix safe issues (prompts for behavioral changes)
44
+ clawhatch scan --fix
45
+
46
+ # Custom OpenClaw installation path
47
+ clawhatch scan --path /custom/path
48
+
49
+ # Combine flags
50
+ clawhatch scan --workspace . --deep --fix
51
+ ```
52
+
53
+ ## Example Output
54
+
55
+ ```
56
+ Clawhatch Security Scanner v0.1.0
57
+
58
+ Clawhatch Security Scan
59
+ ==================================================
60
+
61
+ Security Score: 72/100 (B — Acceptable)
62
+
63
+ Platform: win32
64
+ OpenClaw: 1.2.0
65
+ Checks: 100 run, 94 passed, 6 findings
66
+ Duration: 842ms
67
+ Scanned: 14 files
68
+
69
+ --------------------------------------------------
70
+
71
+ HIGH (2 findings)
72
+
73
+ ! Gateway auth token is weak
74
+ Auth token is short or low-entropy (< 32 characters)
75
+ Risk: Weak tokens can be brute-forced
76
+ Fix: Generate a strong token: openssl rand -hex 32
77
+
78
+ ! Session logs contain potential secrets
79
+ API keys or tokens detected in session log files
80
+ Risk: Secrets persisted in plaintext logs
81
+ Fix: Enable log sanitization and rotate exposed keys
82
+
83
+ MEDIUM (3 findings)
84
+
85
+ ~ DM policy set to "open"
86
+ Channel accepts messages from any sender
87
+ Risk: Unauthorized users can interact with the agent
88
+ Fix: Set dmPolicy to "allowlist" and configure allowFrom
89
+
90
+ ~ No tool rate limiting configured
91
+ Tool execution has no throttle (tools.rateLimit missing)
92
+ Risk: Rapid tool invocation can spam external APIs
93
+ Fix: Set tools.rateLimit in openclaw.json (e.g., 60)
94
+
95
+ ~ Session log retention not configured
96
+ No sessionLogTTL set — logs kept indefinitely
97
+ Risk: Stale data accumulation and potential compliance issues
98
+ Fix: Set retention.sessionLogTTL (e.g., 30 days)
99
+
100
+ LOW (1 finding)
101
+
102
+ - No health check endpoint configured
103
+ Gateway has no monitoring.enabled setting
104
+ Risk: No automated way to verify agent is running correctly
105
+ Fix: Enable monitoring in openclaw.json
106
+
107
+ ==================================================
108
+
109
+ 3 issue(s) can be auto-fixed. Run with --fix
110
+ Run with --json for machine-readable output
111
+ Run with --deep for thorough session log scanning
112
+ ```
113
+
114
+ ## What It Checks
115
+
116
+ Clawhatch runs 100 checks across **10 security categories**:
117
+
118
+ | Category | Checks | What It Covers |
119
+ |----------|--------|----------------|
120
+ | **Identity & Access** | 15 | DM policies, allowlists, pairing config, access groups, OAuth, API key rotation |
121
+ | **Network Exposure** | 10 | Gateway binding, auth mode, TLS, trusted proxies, insecure auth flags |
122
+ | **Sandbox Configuration** | 8 | Sandbox mode, workspace access, Docker isolation, browser host control |
123
+ | **Secret Scanning** | 10 | Hardcoded API keys, .env permissions, secrets in markdown, session log leakage |
124
+ | **Model Security** | 7 | Model config, legacy models, injection resistance, SOUL.md analysis, fallback order |
125
+ | **Cloud Sync** | 1 | iCloud, OneDrive, Dropbox, Google Drive detection |
126
+ | **Tool Security** | 20 | Elevated tools, command injection, Docker socket exposure, audit logging |
127
+ | **Skill Security** | 12 | Untrusted sources, dangerous dependencies, native modules, sandboxing |
128
+ | **Data Protection** | 10 | PII in logs, retention policies, encryption at rest, log rotation |
129
+ | **Operational** | 7 | Logging config, monitoring, git secrets, health checks, dependency staleness |
130
+
131
+ ## Scoring
132
+
133
+ Clawhatch uses a **100-point scoring system** with severity-based penalties:
134
+
135
+ | Severity | Penalty per finding |
136
+ |----------|---------------------|
137
+ | Critical | −15 points |
138
+ | High | −8 points |
139
+ | Medium | −3 points |
140
+ | Low | −1 point |
141
+
142
+ **Critical cap:** Any critical finding hard-caps the score at **40**, regardless of calculated total. Fix critical issues first.
143
+
144
+ ### Grade Scale
145
+
146
+ | Score | Grade | Label |
147
+ |-------|-------|-------|
148
+ | 90–100 | A+ | Excellent |
149
+ | 80–89 | A | Good |
150
+ | 70–79 | B | Acceptable |
151
+ | 50–69 | C | Needs Work |
152
+ | 30–49 | D | Poor |
153
+ | 0–29 | F | Critical |
154
+
155
+ ## Auto-Fix (`--fix`)
156
+
157
+ When you run `clawhatch scan --fix`, the scanner applies fixes in two tiers:
158
+
159
+ **Safe fixes** — applied automatically:
160
+ - File permission corrections (e.g., tightening `.env` to 600)
161
+ - Adding secrets to `.gitignore`
162
+ - Generating strong replacement tokens
163
+
164
+ **Behavioral fixes** — prompts for confirmation:
165
+ - Changing DM policies from "open" to "allowlist"
166
+ - Enabling sandbox mode
167
+ - Modifying gateway bind addresses
168
+
169
+ All fixes create timestamped backups (`.bak.<timestamp>`) before modifying any file.
170
+
171
+ ## JSON Export (`--json`)
172
+
173
+ ```bash
174
+ clawhatch scan --json > report.json
175
+ ```
176
+
177
+ Outputs a structured `ScanResult` object:
178
+
179
+ ```json
180
+ {
181
+ "timestamp": "2026-02-06T12:00:00.000Z",
182
+ "openclawVersion": "1.2.0",
183
+ "score": 82,
184
+ "findings": [
185
+ {
186
+ "id": "NETWORK-001",
187
+ "severity": "CRITICAL",
188
+ "confidence": "high",
189
+ "category": "Network Exposure",
190
+ "title": "Gateway bound to 0.0.0.0",
191
+ "description": "...",
192
+ "risk": "...",
193
+ "remediation": "...",
194
+ "autoFixable": true,
195
+ "fixType": "behavioral"
196
+ }
197
+ ],
198
+ "suggestions": [],
199
+ "summary": {
200
+ "score": 82,
201
+ "grade": "A",
202
+ "label": "Good",
203
+ "critical": 0,
204
+ "high": 1,
205
+ "medium": 2,
206
+ "low": 0,
207
+ "suggestions": 3,
208
+ "autoFixable": 1
209
+ },
210
+ "filesScanned": 14,
211
+ "checksRun": 100,
212
+ "checksPassed": 97,
213
+ "duration": 1234,
214
+ "platform": "win32"
215
+ }
216
+ ```
217
+
218
+ ### Exit Codes
219
+
220
+ | Code | Meaning |
221
+ |------|---------|
222
+ | `0` | Scan passed (no critical findings) |
223
+ | `1` | Critical findings detected |
224
+
225
+ ## Findings vs. Suggestions
226
+
227
+ Clawhatch separates output into two groups:
228
+
229
+ - **Findings** (high/medium confidence) — count toward your score and represent actionable security issues.
230
+ - **Suggestions** (low confidence) — informational recommendations that do not affect your score.
231
+
232
+ ## CI/CD Integration
233
+
234
+ ### GitHub Actions
235
+
236
+ ```yaml
237
+ name: Security Audit
238
+ on: [push, pull_request]
239
+
240
+ jobs:
241
+ clawhatch:
242
+ runs-on: ubuntu-latest
243
+ steps:
244
+ - uses: actions/checkout@v4
245
+ - uses: actions/setup-node@v4
246
+ with:
247
+ node-version: '18'
248
+
249
+ - name: Run Clawhatch scan
250
+ run: npx clawhatch scan --workspace . --json > clawhatch-report.json
251
+
252
+ - name: Check score
253
+ run: |
254
+ score=$(jq '.score' clawhatch-report.json)
255
+ echo "Security score: $score"
256
+ [ "$score" -ge 50 ] || exit 1
257
+
258
+ - uses: actions/upload-artifact@v4
259
+ if: always()
260
+ with:
261
+ name: clawhatch-report
262
+ path: clawhatch-report.json
263
+ ```
264
+
265
+ ## Platform Support
266
+
267
+ | Platform | Status | Notes |
268
+ |----------|--------|-------|
269
+ | **Windows** | ✅ Supported | Full support including `%APPDATA%\openclaw` detection |
270
+ | **Linux** | 🔄 Coming soon | Core checks work, platform-specific checks in progress |
271
+ | **macOS** | 🔄 Coming soon | Core checks work, platform-specific checks in progress |
272
+
273
+ ## Other Commands
274
+
275
+ ### `clawhatch init`
276
+
277
+ Generate a secure baseline OpenClaw configuration:
278
+
279
+ ```bash
280
+ clawhatch init
281
+ clawhatch init --path /custom/path
282
+ ```
283
+
284
+ Creates a hardened `openclaw.json` and `.env` template with secure defaults.
285
+
286
+ ## Community Threat Intelligence
287
+
288
+ Clawhatch includes a community threat intelligence network. When you share your scan results, they're anonymized and aggregated to protect everyone:
289
+
290
+ ### Share your results
291
+
292
+ ```bash
293
+ clawhatch scan --share # Anonymize and share with community
294
+ clawhatch scan --upload # Same as --share
295
+ ```
296
+
297
+ Only check IDs, severity levels, and categories are shared. **No file paths, secrets, or descriptions ever leave your machine.**
298
+
299
+ ### View community threats
300
+
301
+ ```bash
302
+ clawhatch threats # View the community threat feed
303
+ ```
304
+
305
+ Shows the top threats across all users, trending attacks, new advisories, and the community average score.
306
+
307
+ ### Subscribe to alerts
308
+
309
+ ```bash
310
+ clawhatch subscribe --webhook https://discord.com/api/webhooks/... # Discord
311
+ clawhatch subscribe --webhook https://hooks.slack.com/services/... # Slack
312
+ clawhatch subscribe --threshold CRITICAL # Only critical alerts
313
+ ```
314
+
315
+ When a new threat is detected across the community, subscribers are notified instantly via their configured webhook.
316
+
317
+ ### How it works
318
+
319
+ 1. You run `clawhatch scan --share`
320
+ 2. Findings are stripped to just check IDs + severity (no file paths, no secrets)
321
+ 3. Anonymized report is uploaded to the community feed
322
+ 4. If 45% of users suddenly have NETWORK-001, that's flagged as trending
323
+ 5. Subscribers with that vulnerability get an instant webhook alert
324
+
325
+ ### Privacy
326
+
327
+ - Instance ID is a SHA-256 hash of your hostname -- we never see your actual machine name
328
+ - No file paths, descriptions, or secret values are ever transmitted
329
+ - You can inspect exactly what's sent with `clawhatch scan --json --share`
330
+
331
+ ## Requirements
332
+
333
+ - **Node.js** >= 18.0.0
334
+ - **OpenClaw** installed (auto-detected or specify with `--path`)
335
+
336
+ ## Contributing
337
+
338
+ Contributions welcome! Please see the [GitHub repository](https://github.com/wlshlad85/clawhatch) for details.
339
+
340
+ 1. Fork the repo
341
+ 2. Create a feature branch (`git checkout -b feat/my-check`)
342
+ 3. Add your checks following the existing pattern in `src/checks/`
343
+ 4. Run tests: `npm test`
344
+ 5. Submit a PR
345
+
346
+ ## License
347
+
348
+ [MIT](LICENSE) © Clawhatch
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Cloud Sync Detection check (51).
3
+ *
4
+ * Detects if ~/.openclaw/ is inside a known cloud sync directory
5
+ * (iCloud, OneDrive, Dropbox, Google Drive). If so, credentials
6
+ * are being uploaded to the cloud in plaintext.
7
+ */
8
+ import { type Finding } from "../types.js";
9
+ export declare function runCloudSyncCheck(openclawDir: string): Promise<Finding[]>;
10
+ //# sourceMappingURL=cloud-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-sync.d.ts","sourceRoot":"","sources":["../../src/checks/cloud-sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AA4CrD,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,EAAE,CAAC,CAgCpB"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Cloud Sync Detection check (51).
3
+ *
4
+ * Detects if ~/.openclaw/ is inside a known cloud sync directory
5
+ * (iCloud, OneDrive, Dropbox, Google Drive). If so, credentials
6
+ * are being uploaded to the cloud in plaintext.
7
+ */
8
+ import { Severity } from "../types.js";
9
+ import { platform, homedir } from "node:os";
10
+ import { resolve } from "node:path";
11
+ import { stat } from "node:fs/promises";
12
+ function getCloudServices() {
13
+ const home = homedir();
14
+ const os = platform();
15
+ const services = [];
16
+ if (os === "darwin") {
17
+ services.push({ name: "iCloud Drive", paths: [`${home}/Library/Mobile Documents`] }, { name: "Dropbox", paths: [`${home}/Dropbox`, `${home}/Library/CloudStorage/Dropbox`] }, { name: "OneDrive", paths: [`${home}/OneDrive`, `${home}/Library/CloudStorage/OneDrive-Personal`] }, { name: "Google Drive", paths: [`${home}/Google Drive`, `${home}/Library/CloudStorage/GoogleDrive`] });
18
+ }
19
+ else if (os === "win32") {
20
+ // FIX: Removed duplicate path — `home` already contains the full path like C:\Users\RICHARD
21
+ // so we don't need to construct C:\Users\<username> separately
22
+ services.push({ name: "OneDrive", paths: [`${home}\\OneDrive`] }, { name: "Dropbox", paths: [`${home}\\Dropbox`] }, { name: "Google Drive", paths: [`${home}\\Google Drive`] }, { name: "iCloud Drive", paths: [`${home}\\iCloudDrive`] });
23
+ }
24
+ else {
25
+ // Linux
26
+ services.push({ name: "Dropbox", paths: [`${home}/Dropbox`] }, { name: "Google Drive", paths: [`${home}/Google Drive`] }, { name: "OneDrive", paths: [`${home}/OneDrive`] });
27
+ }
28
+ return services;
29
+ }
30
+ export async function runCloudSyncCheck(openclawDir) {
31
+ const findings = [];
32
+ const resolvedOcDir = resolve(openclawDir).toLowerCase();
33
+ const services = getCloudServices();
34
+ for (const service of services) {
35
+ for (const syncPath of service.paths) {
36
+ try {
37
+ await stat(syncPath);
38
+ // Sync directory exists — check if openclaw dir is inside it
39
+ const resolvedSync = resolve(syncPath).toLowerCase();
40
+ if (resolvedOcDir.startsWith(resolvedSync)) {
41
+ findings.push({
42
+ id: "CLOUD-001",
43
+ severity: Severity.High,
44
+ confidence: "high",
45
+ category: "Cloud Sync",
46
+ title: `OpenClaw directory is inside ${service.name}`,
47
+ description: `${openclawDir} is inside ${syncPath} — credentials are being synced to the cloud`,
48
+ risk: "API keys, OAuth tokens, and session logs are uploaded to cloud storage in plaintext",
49
+ remediation: `Move ~/.openclaw/ outside of ${service.name} or exclude it from sync`,
50
+ autoFixable: false,
51
+ });
52
+ break; // One finding per service
53
+ }
54
+ }
55
+ catch {
56
+ // Sync dir doesn't exist — skip
57
+ }
58
+ }
59
+ }
60
+ return findings;
61
+ }
62
+ //# sourceMappingURL=cloud-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-sync.js","sourceRoot":"","sources":["../../src/checks/cloud-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Data Protection checks (DATA-001 to DATA-010).
3
+ *
4
+ * Checks for PII in logs, data retention policies, encryption,
5
+ * log rotation, and data access audit trails.
6
+ */
7
+ import { type Finding, type OpenClawConfig, type DiscoveredFiles } from "../types.js";
8
+ export declare function runDataProtectionChecks(config: OpenClawConfig, files: DiscoveredFiles): Promise<Finding[]>;
9
+ //# sourceMappingURL=data-protection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-protection.d.ts","sourceRoot":"","sources":["../../src/checks/data-protection.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAahG,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CA8LpB"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Data Protection checks (DATA-001 to DATA-010).
3
+ *
4
+ * Checks for PII in logs, data retention policies, encryption,
5
+ * log rotation, and data access audit trails.
6
+ */
7
+ import { Severity } from "../types.js";
8
+ import { readFile, stat } from "node:fs/promises";
9
+ import { basename } from "node:path";
10
+ // FIX: Tightened PII patterns to reduce false positives.
11
+ // Phone/SSN patterns removed — they match timestamps, version numbers, ports, etc.
12
+ // with extremely high false-positive rates. Kept email and credit-card (Luhn check
13
+ // would be ideal but is out of scope; the 4-group pattern is reasonably specific).
14
+ const PII_PATTERNS = [
15
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/, // email (FIX: [A-Za-z] not [A-Z|a-z])
16
+ /\b\d{4}[\s-]\d{4}[\s-]\d{4}[\s-]\d{4}\b/, // credit card-like (require separators to avoid matching hex/ids)
17
+ ];
18
+ export async function runDataProtectionChecks(config, files) {
19
+ const findings = [];
20
+ // DATA-001: Session logs contain PII
21
+ for (const logFile of files.sessionLogFiles.slice(0, 5)) {
22
+ try {
23
+ const content = await readFile(logFile, "utf-8").then((c) => c.slice(0, 512 * 1024));
24
+ const hasPII = PII_PATTERNS.some((p) => p.test(content));
25
+ if (hasPII) {
26
+ findings.push({
27
+ id: "DATA-001",
28
+ severity: Severity.High,
29
+ confidence: "medium",
30
+ category: "Data Protection",
31
+ title: "Session log may contain PII",
32
+ description: `${basename(logFile)} contains patterns resembling personal data (email, phone, etc.)`,
33
+ risk: "PII in logs may violate privacy regulations (GDPR, CCPA) and expose user data",
34
+ remediation: "Enable PII scrubbing in session logs or reduce log verbosity",
35
+ autoFixable: false,
36
+ file: logFile,
37
+ });
38
+ break;
39
+ }
40
+ }
41
+ catch {
42
+ // Can't read
43
+ }
44
+ }
45
+ // DATA-002: No data retention policy
46
+ if (!config.retention?.sessionLogTTL) {
47
+ findings.push({
48
+ id: "DATA-002",
49
+ severity: Severity.Medium,
50
+ confidence: "medium",
51
+ category: "Data Protection",
52
+ title: "No data retention policy",
53
+ description: "No session log TTL configured — logs accumulate indefinitely",
54
+ risk: "Unbounded log retention increases data breach exposure window",
55
+ remediation: "Set retention.sessionLogTTL to a reasonable period (e.g., 30 days)",
56
+ autoFixable: false,
57
+ });
58
+ }
59
+ // DATA-003: Logs not encrypted at rest
60
+ if (!config.retention?.encryptAtRest) {
61
+ findings.push({
62
+ id: "DATA-003",
63
+ severity: Severity.Low,
64
+ confidence: "low",
65
+ category: "Data Protection",
66
+ title: "Session logs not encrypted at rest",
67
+ description: "retention.encryptAtRest is not enabled for session logs",
68
+ risk: "Logs stored in plaintext can be read if the disk is accessed by unauthorized parties",
69
+ remediation: "Set retention.encryptAtRest: true or use full-disk encryption",
70
+ autoFixable: false,
71
+ });
72
+ }
73
+ // DATA-004: No log rotation
74
+ if (!config.retention?.logRotation) {
75
+ // Also flag if session logs are large
76
+ let hasLargeLog = false;
77
+ for (const logFile of files.sessionLogFiles.slice(0, 5)) {
78
+ try {
79
+ const s = await stat(logFile);
80
+ if (s.size > 50 * 1024 * 1024) {
81
+ hasLargeLog = true;
82
+ break;
83
+ }
84
+ }
85
+ catch {
86
+ // Can't stat
87
+ }
88
+ }
89
+ findings.push({
90
+ id: "DATA-004",
91
+ severity: hasLargeLog ? Severity.Medium : Severity.Low,
92
+ confidence: hasLargeLog ? "high" : "medium",
93
+ category: "Data Protection",
94
+ title: "No log rotation configured",
95
+ description: hasLargeLog
96
+ ? "Log rotation disabled and session logs exceed 50MB"
97
+ : "retention.logRotation is not enabled",
98
+ risk: "Unrotated logs consume disk space and increase exposure in case of breach",
99
+ remediation: "Set retention.logRotation: true to enable automatic log rotation",
100
+ autoFixable: false,
101
+ });
102
+ }
103
+ // DATA-005: Backups not encrypted
104
+ if (config.retention && !config.retention.encryptAtRest) {
105
+ findings.push({
106
+ id: "DATA-005",
107
+ severity: Severity.Low,
108
+ confidence: "low",
109
+ category: "Data Protection",
110
+ title: "Backups not encrypted",
111
+ description: "Data retention is configured but encryption at rest is not enabled",
112
+ risk: "Backup files containing session data are stored in plaintext",
113
+ remediation: "Enable retention.encryptAtRest: true to encrypt stored data",
114
+ autoFixable: false,
115
+ });
116
+ }
117
+ // DATA-006: No data anonymization
118
+ if (files.sessionLogFiles.length > 0 && !config.retention?.sessionLogTTL) {
119
+ findings.push({
120
+ id: "DATA-006",
121
+ severity: Severity.Low,
122
+ confidence: "low",
123
+ category: "Data Protection",
124
+ title: "No data anonymization",
125
+ description: "Session logs exist with no retention policy or anonymization configured",
126
+ risk: "Historical logs contain identifiable conversation data",
127
+ remediation: "Configure data anonymization or set a retention TTL to limit data lifespan",
128
+ autoFixable: false,
129
+ });
130
+ }
131
+ // DATA-007: Third-party data sharing
132
+ if (config.monitoring?.enabled && config.monitoring?.provider) {
133
+ findings.push({
134
+ id: "DATA-007",
135
+ severity: Severity.Medium,
136
+ confidence: "medium",
137
+ category: "Data Protection",
138
+ title: "Third-party monitoring enabled",
139
+ description: `Monitoring data is sent to third-party provider: ${config.monitoring.provider}`,
140
+ risk: "Session data, tool invocations, or PII may be shared with external services",
141
+ remediation: "Review monitoring configuration to ensure only necessary data is shared",
142
+ autoFixable: false,
143
+ });
144
+ }
145
+ // DATA-008: No right-to-deletion
146
+ if (files.sessionLogFiles.length > 0 && !config.retention?.sessionLogTTL) {
147
+ findings.push({
148
+ id: "DATA-008",
149
+ severity: Severity.Low,
150
+ confidence: "low",
151
+ category: "Data Protection",
152
+ title: "No data deletion mechanism",
153
+ description: "No retention TTL or deletion mechanism for user session data",
154
+ risk: "Cannot fulfil data deletion requests (GDPR Article 17) without manual cleanup",
155
+ remediation: "Configure retention.sessionLogTTL or provide a data deletion workflow",
156
+ autoFixable: false,
157
+ });
158
+ }
159
+ // DATA-009: Logs in public directory
160
+ if (files.workspaceDir) {
161
+ const publicDirs = ["public", "static", "dist", "build", "www"];
162
+ for (const logFile of files.sessionLogFiles) {
163
+ const inPublic = publicDirs.some((d) => logFile.toLowerCase().includes(`${d}/`) || logFile.toLowerCase().includes(`${d}\\`));
164
+ if (inPublic) {
165
+ findings.push({
166
+ id: "DATA-009",
167
+ severity: Severity.High,
168
+ confidence: "high",
169
+ category: "Data Protection",
170
+ title: "Session logs in public directory",
171
+ description: `${basename(logFile)} is inside a public/static directory`,
172
+ risk: "Logs in public directories may be served by web servers and accessible via URL",
173
+ remediation: "Move session logs out of public-facing directories",
174
+ autoFixable: false,
175
+ file: logFile,
176
+ });
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ // DATA-010: No audit trail for data access
182
+ if (!config.tools?.auditLog && files.sessionLogFiles.length > 0) {
183
+ findings.push({
184
+ id: "DATA-010",
185
+ severity: Severity.Low,
186
+ confidence: "low",
187
+ category: "Data Protection",
188
+ title: "No audit trail for data access",
189
+ description: "No audit logging configured to track who accesses session data",
190
+ risk: "Cannot determine who accessed, modified, or exported conversation data",
191
+ remediation: "Enable tools.auditLog: true to track data access operations",
192
+ autoFixable: false,
193
+ });
194
+ }
195
+ return findings;
196
+ }
197
+ //# sourceMappingURL=data-protection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-protection.js","sourceRoot":"","sources":["../../src/checks/data-protection.ts"],"names":[],"mappings":"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"}