@contextfort-ai/openclaw-secure 0.1.6 → 0.1.8

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.
@@ -81,6 +81,175 @@ if (args[0] === 'enable') {
81
81
  process.exit(0);
82
82
  }
83
83
 
84
+ if (args[0] === 'dashboard') {
85
+ const portArg = args.find(a => a.startsWith('--port='));
86
+ const port = portArg ? parseInt(portArg.split('=')[1]) : 9009;
87
+ const startDashboard = require('../monitor/dashboard/server');
88
+ const server = startDashboard({ port });
89
+
90
+ // Auto-open browser
91
+ const openUrl = `http://localhost:${port}`;
92
+ try {
93
+ const { execSync } = require('child_process');
94
+ if (process.platform === 'darwin') execSync(`open "${openUrl}"`);
95
+ else if (process.platform === 'linux') execSync(`xdg-open "${openUrl}" 2>/dev/null`);
96
+ else if (process.platform === 'win32') execSync(`start "" "${openUrl}"`);
97
+ } catch {}
98
+
99
+ process.on('SIGINT', () => { server.close(); process.exit(0); });
100
+ // Keep alive — don't fall through
101
+ return;
102
+ }
103
+
104
+ if (args[0] === 'scan' || args[0] === 'solve') {
105
+ const { spawnSync } = require('child_process');
106
+ const readline = require('readline');
107
+ const secretsGuard = require('../monitor/secrets_guard')({
108
+ spawnSync,
109
+ baseDir: packageDir,
110
+ analytics: null,
111
+ });
112
+
113
+ if (!secretsGuard.isTrufflehogInstalled()) {
114
+ console.error('\n trufflehog is not installed.\n');
115
+ console.error(' Install it with: brew install trufflehog');
116
+ console.error(' Or see: https://github.com/trufflesecurity/trufflehog#installation\n');
117
+ process.exit(1);
118
+ }
119
+
120
+ const onlyVerified = !args.includes('--all');
121
+ const cwd = args.find(a => !a.startsWith('-') && a !== 'scan' && a !== 'solve') || process.cwd();
122
+
123
+ console.log('\n Running TruffleHog secret scan...');
124
+ if (args[0] === 'scan' && onlyVerified) {
125
+ console.log(' Mode: verified secrets only (use --all to include unverified)\n');
126
+ } else if (args[0] === 'scan') {
127
+ console.log(' Mode: all findings (including unverified)\n');
128
+ } else {
129
+ console.log(' Mode: solve — scanning for live secrets to replace\n');
130
+ }
131
+
132
+ // For solve, always scan with onlyVerified=true (only replace live secrets)
133
+ const scanVerified = args[0] === 'solve' ? true : onlyVerified;
134
+ const result = secretsGuard.scan(cwd, { onlyVerified: scanVerified });
135
+ console.log(secretsGuard.formatResults(result));
136
+
137
+ if (args[0] === 'scan') {
138
+ process.exit(result.findings.filter(f => f.verified).length > 0 ? 1 : 0);
139
+ }
140
+
141
+ // === SOLVE mode ===
142
+ const verified = result.findings.filter(f => f.verified && f.rawFull && f.file);
143
+ if (verified.length === 0) {
144
+ console.log(' Nothing to solve — no live hardcoded secrets found.\n');
145
+ process.exit(0);
146
+ }
147
+
148
+ // Group findings by file
149
+ const fileMap = new Map(); // file → [findings]
150
+ for (const f of verified) {
151
+ if (!fileMap.has(f.file)) fileMap.set(f.file, []);
152
+ fileMap.get(f.file).push(f);
153
+ }
154
+ const fileList = [...fileMap.entries()]; // [[file, [findings]], ...]
155
+
156
+ console.log(` OpenClaw can read these ${fileList.length} file(s) containing ${verified.length} live secret(s).`);
157
+ console.log(' If not replaced, OpenClaw could read and leak them.\n');
158
+ console.log(' Use \u2191\u2193 to move, SPACE to select, A to toggle all, ENTER to confirm, Q to quit.\n');
159
+
160
+ // Build items for checkbox UI
161
+ const items = fileList.map(([filePath, findings]) => {
162
+ const types = [...new Set(findings.map(f => f.detectorName))].join(', ');
163
+ const count = findings.length;
164
+ return {
165
+ label: `${filePath.replace(os.homedir(), '~')} (${count} ${types})`,
166
+ selected: false,
167
+ };
168
+ });
169
+
170
+ let cursor = 0;
171
+
172
+ function render() {
173
+ // Move cursor up to overwrite previous render
174
+ if (items._rendered) {
175
+ process.stdout.write(`\x1b[${items.length + 1}A`);
176
+ }
177
+ for (let i = 0; i < items.length; i++) {
178
+ const check = items[i].selected ? '\x1b[32m\u25c9\x1b[0m' : '\u25cb';
179
+ const pointer = i === cursor ? '\x1b[36m\u276f\x1b[0m ' : ' ';
180
+ const dim = i === cursor ? '' : '\x1b[2m';
181
+ const reset = i === cursor ? '' : '\x1b[0m';
182
+ process.stdout.write(`\x1b[2K ${pointer}${check} ${dim}${items[i].label}${reset}\n`);
183
+ }
184
+ const selectedCount = items.filter(it => it.selected).length;
185
+ process.stdout.write(`\x1b[2K \x1b[2m${selectedCount}/${items.length} selected\x1b[0m\n`);
186
+ items._rendered = true;
187
+ }
188
+
189
+ render();
190
+
191
+ const stdin = process.stdin;
192
+ stdin.setRawMode(true);
193
+ stdin.resume();
194
+ stdin.setEncoding('utf8');
195
+
196
+ stdin.on('data', (key) => {
197
+ if (key === 'q' || key === '\x03') {
198
+ // q or Ctrl+C
199
+ stdin.setRawMode(false);
200
+ console.log('\n\n Aborted. No changes made.\n');
201
+ process.exit(0);
202
+ }
203
+ if (key === '\r' || key === '\n') {
204
+ // Enter — confirm
205
+ stdin.setRawMode(false);
206
+ stdin.pause();
207
+ const selectedFindings = [];
208
+ for (let i = 0; i < items.length; i++) {
209
+ if (items[i].selected) {
210
+ selectedFindings.push(...fileList[i][1]);
211
+ }
212
+ }
213
+ if (selectedFindings.length === 0) {
214
+ console.log('\n\n No files selected. Aborted.\n');
215
+ process.exit(0);
216
+ }
217
+ const selectedFiles = items.filter(it => it.selected).length;
218
+ console.log(`\n\n Replacing secrets in ${selectedFiles} file(s)...`);
219
+ const results = secretsGuard.solve(selectedFindings);
220
+ console.log(secretsGuard.formatSolveResults(results));
221
+ process.exit(0);
222
+ return;
223
+ }
224
+ if (key === ' ') {
225
+ // Space — toggle current item
226
+ items[cursor].selected = !items[cursor].selected;
227
+ render();
228
+ return;
229
+ }
230
+ if (key === 'a' || key === 'A') {
231
+ // A — toggle all
232
+ const allSelected = items.every(it => it.selected);
233
+ for (const it of items) it.selected = !allSelected;
234
+ render();
235
+ return;
236
+ }
237
+ if (key === '\x1b[A' || key === 'k') {
238
+ // Up arrow or k
239
+ cursor = (cursor - 1 + items.length) % items.length;
240
+ render();
241
+ return;
242
+ }
243
+ if (key === '\x1b[B' || key === 'j') {
244
+ // Down arrow or j
245
+ cursor = (cursor + 1) % items.length;
246
+ render();
247
+ return;
248
+ }
249
+ });
250
+ return; // don't fall through — raw mode is async
251
+ }
252
+
84
253
  if (args[0] === 'disable') {
85
254
  try {
86
255
  const original = fs.readFileSync(backupLink, 'utf8').trim();
@@ -9,7 +9,7 @@ const ANALYTICS_DISABLED = ['1', 'true', 'yes'].includes(
9
9
  (process.env.CONTEXTFORT_NO_ANALYTICS || '').toLowerCase()
10
10
  );
11
11
 
12
- module.exports = function createAnalytics({ httpsRequest, readFileSync, baseDir }) {
12
+ module.exports = function createAnalytics({ httpsRequest, readFileSync, baseDir, localLogger }) {
13
13
  if (ANALYTICS_DISABLED || !httpsRequest) {
14
14
  return { track() {} };
15
15
  }
@@ -27,7 +27,7 @@ module.exports = function createAnalytics({ httpsRequest, readFileSync, baseDir
27
27
 
28
28
  function track(event, properties) {
29
29
  if (!installId) return;
30
- const body = JSON.stringify({
30
+ const payload = {
31
31
  api_key: POSTHOG_API_KEY,
32
32
  event,
33
33
  properties: {
@@ -35,7 +35,13 @@ module.exports = function createAnalytics({ httpsRequest, readFileSync, baseDir
35
35
  ...properties,
36
36
  },
37
37
  timestamp: new Date().toISOString(),
38
- });
38
+ };
39
+ const body = JSON.stringify(payload);
40
+
41
+ // Log what we're sending to PostHog
42
+ if (localLogger) {
43
+ try { localLogger.logServerSend({ destination: 'posthog', event, properties: properties || {} }); } catch {}
44
+ }
39
45
 
40
46
  try {
41
47
  const req = httpsRequest({