MAL-2026-6310
Vulnerability from ossf_malicious_packages
Published
2026-06-22 12:00
Modified
2026-06-23 19:34
Summary
Malicious code in @petitcode/eb-retry (npm)
Details

@petitcode/eb-retry (malicious version 1.3.5, published by petitcode-eq1efk@wshu.net) is a trojanized npm package belonging to the wshu.net credential-stealer campaign. The campaign published trojanized look-alike utility packages across 12+ scopes whose publisher accounts all follow the pattern -<6 random chars>@wshu.net, with every scope created on June 4, 2026 in a ~40-minute burst. This package masquerades as a retry wrapper (a decoy copy of the popular retry utility) and ships real, working utility code so it passes a glance, while bundling a much larger malicious payload at lib/warmup.js. package.json declares a postinstall hook ("node lib/warmup.js") that runs the payload automatically on npm install. It is additionally a dual-trigger variant: even if postinstall is skipped with --ignore-scripts, the payload still executes at require-time the first time application code calls retry(). The payload is heavily obfuscated with javascript-obfuscator (hex-named identifiers, a while (!![]) array-rotation IIFE, base64+RC4 string decoding, control-flow flattening, and runtime-decrypted module resolution to stay out of the static module graph). At runtime it is a Chromium browser credential stealer: it reads Chromium Cookies and Login Data and decrypts saved passwords protected by AES-256-GCM (the v10/v11 app-bound key schemes), then exfiltrates them over HTTPS using a spoofed Mozilla/5.0 user agent. Malicious payload lib/warmup.js SHA-256: 32d02f806d58a6670f7cc9b93f1d85b22e0e0f535e1f90a62d86918033896f54.


-= Per source details. Do not edit below this line.=-

Source: amazon-inspector (4386267addad1d2b89d4d471966e028ea201469edd6ece252f9710cd679c20aa)

Package advertises itself as a small retry/exponential-backoff helper (lib/index.js is ~50 lines) but ships a 282KB obfuscator.io-packed lib/warmup.js (and matching lib/warmup.mjs at 250KB) whose runPrepare() is invoked unconditionally on every require('@petitcode/eb-retry'). The same file is self-executing as a standalone script via if (require.main === module) onInstall(); so it also runs during postinstall flows. The packed code contains AES-256-GCM decryption of an embedded encrypted blob (which carries a remote URL), an HTTPS fetch of additional payload bytes, and a child_process.spawn of process.execPath with the original argv — i.e. it re-runs Node against attacker-supplied code. Obfuscator.io packing (1267-element rotated string array, RC4-style decoder, control-flow flattening, self-defending console overrides, debug-protection timer that crashes under devtools/inspector) is used to hide the URL, key derivation, and exec invocation. The package.json points repository, bugs, and homepage URLs at github.com/tim-kos/node-retry — an unrelated legitimate project by Tim Koschützki — while the actual publisher is petitcode <petitcode@pm.me>, a deliberate impersonation to lure developers searching for retry utilities. Any installer that runs npm install @petitcode/eb-retry or any code path that requires the package will execute the dropper.

CWE
  • CWE-506 - The product contains code that appears to be malicious in nature.
  • CWE-506 - The product contains code that appears to be malicious in nature.
  • CWE-506 - The product contains code that appears to be malicious in nature.
  • CWE-506 - The product contains code that appears to be malicious in nature.
Credits

{
  "affected": [
    {
      "database_specific": {
        "cwes": [
          {
            "cweId": "CWE-506",
            "description": "The product contains code that appears to be malicious in nature.",
            "name": "Embedded Malicious Code"
          },
          {
            "cweId": "CWE-506",
            "description": "The product contains code that appears to be malicious in nature.",
            "name": "Embedded Malicious Code"
          },
          {
            "cweId": "CWE-506",
            "description": "The product contains code that appears to be malicious in nature.",
            "name": "Embedded Malicious Code"
          },
          {
            "cweId": "CWE-506",
            "description": "The product contains code that appears to be malicious in nature.",
            "name": "Embedded Malicious Code"
          }
        ],
        "indicators": {
          "evidence_files": [
            {
              "path": "lib/warmup.js",
              "sha256": "c8e38d3015adb295ff8e8b060d5a3c053bed3cb697072e03e25305af9a33c924",
              "tlsh": "f6f0e1ca38fa46b03b5a13119687aca3b9b574142305606087ce8be42790139a3668bf"
            }
          ],
          "package_integrity": [
            {
              "filename": "eb-retry-1.3.4.tgz",
              "hashes": {
                "sha1": "2e1d741c5e1fefdd1a87fafe2c724927c01e7933",
                "sha512_sri": "sha512-uf8q6t0D3O1F0MHz1nYAdh9CuzrVBuD20peEXQPmB+WAFCWYA3T23c+aowd/XCd4i4GFVn0xGrN45xi/bFoHoA=="
              }
            }
          ]
        }
      },
      "package": {
        "ecosystem": "npm",
        "name": "@petitcode/eb-retry"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            }
          ],
          "type": "SEMVER"
        }
      ],
      "versions": [
        "1.3.4",
        "1.3.5",
        "1.3.6",
        "1.3.3"
      ]
    }
  ],
  "credits": [
    {
      "contact": [
        "inspector-research@amazon.com"
      ],
      "name": "Amazon Inspector",
      "type": "FINDER"
    },
    {
      "contact": [
        "https://safedep.io"
      ],
      "name": "SafeDep",
      "type": "FINDER"
    }
  ],
  "database_specific": {
    "malicious-packages-origins": [
      {
        "id": "IN-MAL-2026-007325",
        "import_time": "2026-06-23T16:54:17.916779458Z",
        "modified_time": "2026-06-23T16:23:07Z",
        "sha256": "39f6df9234ec7224f9094f0532931550e76edb000f9773185a83732086bcc4af",
        "source": "amazon-inspector",
        "versions": [
          "1.3.4"
        ]
      },
      {
        "id": "IN-MAL-2026-007280",
        "import_time": "2026-06-23T16:54:14.341738988Z",
        "modified_time": "2026-06-23T16:22:26Z",
        "sha256": "3fc2453be99614e7494cc5648d430003db18fb7f9b488b0988aa308da80ed1d8",
        "source": "amazon-inspector",
        "versions": [
          "1.3.5"
        ]
      },
      {
        "id": "IN-MAL-2026-007288",
        "import_time": "2026-06-23T16:54:15.048705708Z",
        "modified_time": "2026-06-23T16:22:32Z",
        "sha256": "4386267addad1d2b89d4d471966e028ea201469edd6ece252f9710cd679c20aa",
        "source": "amazon-inspector",
        "versions": [
          "1.3.6"
        ]
      },
      {
        "id": "IN-MAL-2026-007330",
        "import_time": "2026-06-23T16:54:18.325987243Z",
        "modified_time": "2026-06-23T16:23:12Z",
        "sha256": "6f68260c08dfaaf8fe42a04a761e46f9d7397f29c751dd673cb87a04cfa7af14",
        "source": "amazon-inspector",
        "versions": [
          "1.3.3"
        ]
      }
    ]
  },
  "details": "@petitcode/eb-retry (malicious version 1.3.5, published by petitcode-eq1efk@wshu.net) is a trojanized npm package belonging to the wshu.net credential-stealer campaign. The campaign published trojanized look-alike utility packages across 12+ scopes whose publisher accounts all follow the pattern \u003cscope\u003e-\u003c6 random chars\u003e@wshu.net, with every scope created on June 4, 2026 in a ~40-minute burst. This package masquerades as a retry wrapper (a decoy copy of the popular retry utility) and ships real, working utility code so it passes a glance, while bundling a much larger malicious payload at lib/warmup.js. package.json declares a postinstall hook (\"node lib/warmup.js\") that runs the payload automatically on npm install. It is additionally a dual-trigger variant: even if postinstall is skipped with --ignore-scripts, the payload still executes at require-time the first time application code calls retry(). The payload is heavily obfuscated with javascript-obfuscator (hex-named identifiers, a while (!![]) array-rotation IIFE, base64+RC4 string decoding, control-flow flattening, and runtime-decrypted module resolution to stay out of the static module graph). At runtime it is a Chromium browser credential stealer: it reads Chromium Cookies and Login Data and decrypts saved passwords protected by AES-256-GCM (the v10/v11 app-bound key schemes), then exfiltrates them over HTTPS using a spoofed Mozilla/5.0 user agent. Malicious payload lib/warmup.js SHA-256: 32d02f806d58a6670f7cc9b93f1d85b22e0e0f535e1f90a62d86918033896f54.\n\n---\n_-= Per source details. Do not edit below this line.=-_\n\n## Source: amazon-inspector (4386267addad1d2b89d4d471966e028ea201469edd6ece252f9710cd679c20aa)\nPackage advertises itself as a small retry/exponential-backoff helper (lib/index.js is ~50 lines) but ships a 282KB obfuscator.io-packed lib/warmup.js (and matching lib/warmup.mjs at 250KB) whose runPrepare() is invoked unconditionally on every require(\u0027@petitcode/eb-retry\u0027). The same file is self-executing as a standalone script via `if (require.main === module) onInstall();` so it also runs during postinstall flows. The packed code contains AES-256-GCM decryption of an embedded encrypted blob (which carries a remote URL), an HTTPS fetch of additional payload bytes, and a child_process.spawn of process.execPath with the original argv \u2014 i.e. it re-runs Node against attacker-supplied code. Obfuscator.io packing (1267-element rotated string array, RC4-style decoder, control-flow flattening, self-defending console overrides, debug-protection timer that crashes under devtools/inspector) is used to hide the URL, key derivation, and exec invocation. The package.json points repository, bugs, and homepage URLs at github.com/tim-kos/node-retry \u2014 an unrelated legitimate project by Tim Kosch\u00fctzki \u2014 while the actual publisher is `petitcode \u003cpetitcode@pm.me\u003e`, a deliberate impersonation to lure developers searching for retry utilities. Any installer that runs `npm install @petitcode/eb-retry` or any code path that requires the package will execute the dropper.\n",
  "id": "MAL-2026-6310",
  "modified": "2026-06-23T19:34:37Z",
  "published": "2026-06-22T12:00:00Z",
  "references": [
    {
      "type": "PACKAGE",
      "url": "https://www.npmjs.com/package/@petitcode/eb-retry/v/1.3.4"
    },
    {
      "type": "PACKAGE",
      "url": "https://www.npmjs.com/package/@petitcode/eb-retry/v/1.3.5"
    },
    {
      "type": "PACKAGE",
      "url": "https://www.npmjs.com/package/@petitcode/eb-retry/v/1.3.6"
    },
    {
      "type": "PACKAGE",
      "url": "https://www.npmjs.com/package/@petitcode/eb-retry/v/1.3.3"
    },
    {
      "type": "REPORT",
      "url": "https://safedep.io/wshu-net-npm-credential-stealer-campaign/"
    },
    {
      "type": "PACKAGE",
      "url": "https://www.npmjs.com/package/@petitcode/eb-retry"
    }
  ],
  "schema_version": "1.7.4",
  "summary": "Malicious code in @petitcode/eb-retry (npm)"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…