Open Source · · 3 days

num2words maintainer phishing shipped malware

A pypj.org phishing page stole PyPI credentials and led to malicious num2words releases. Versions 0.5.15 and 0.5.16 appeared on PyPI without matching upstream GitHub releases.

On July 28, 2025, an attacker used a phished PyPI credential to publish two malicious releases of num2words, the widely used Python library that turns numbers into their written-out form across dozens of languages. The compromise extended a string of registry takeovers that ran through the summer, this time on the Python side rather than npm.

The maintainer was lured to pypj.org, a typosquat of the legitimate pypi.org domain, where the attacker harvested credentials and a 2FA code. The stolen session was used to mint a fresh PyPI API token and to push version 0.5.15 to the registry, with 0.5.16 following soon after. Neither release had a matching commit, tag, or GitHub Release on the upstream savoirfairelinux/num2words repository, the divergence that first drew the analysts' attention. The bad version sat on PyPI for roughly two hours before being flagged, long enough for automated dependency-update bots to begin opening pull requests against downstream projects.

Researchers tied the incident to an actor commonly tracked as Scavenger, which has been linked to a series of supply-chain intrusions earlier in the year. Public reporting on the num2words payload itself remains thin: the advisory tracked as GHSA-jxr6-qrxx-2ph2 rates it critical and confirms the releases shipped malicious code, but neither StepSecurity's writeup nor GitHub's advisory enumerates the specific behavior. The files themselves were recovered in September 2026, and they answer what the advisories do not. Each release appends thirteen lines to num2words/__init__.py that, on Windows, hand num2words/_build.py to ctypes.CDLL and call its main(). _build.py is not Python at all: it is a 1.3 MB Windows PE DLL wearing a .py extension so that it travels inside a source distribution without looking out of place.

PyPI removed both versions, and the project's legitimate maintainers continued publishing from the unaffected GitHub branch. Operators who installed 0.5.15 or 0.5.16, or whose dependency bots resolved them automatically, were advised to pin to a known-good version and rotate any credential reachable from the affected host.

Appendix · Affected releases

num2words pypi
0.5.15 sha256 d31480b0…55fd77ab download unavailable
0.5.16 sha256 ee90ff8c…50a00106 download unavailable
  • StepSecurity cited package popularity as exposure context; no confirmed affected-user count is stored.
  • Two SHA-256 values previously attributed to the OSV record appear in neither MAL-2025-6794 nor PYSEC-2025-72 and match none of the four files PyPI served. They are removed; the hashes above come from PyPI's own API.
  • A 2026-08-14 audit queried those two values through MalwareBazaar, Triage and MalShare and found nothing. Re-running the lookup on 2026-09-02 with the correct digests recovered all four files from Hopper on the first attempt.

References

  1. Supply Chain Security Alert - num2words PyPI Package Shows Signs of Compromisestepsecurity.io
  2. num2words subjected to phishing attack, two versions published containing malwaregithub.com
  3. PYSEC-2025-72github.com
  4. MAL-2025-6794github.com
  5. Savoir-faire Linux post about the compromisex.com

Source record: oss/attacks/num2words/meta.yaml