Skip to content

PyPI voice-synthesis wave two: sixteen advised loaders still installing

Summary

  • What landed: twenty-four OSV rows in one ~3.5 h lane (MAL-2026-19178-19201), of which SIXTEEN are one kam193 campaign: tag 2026-10-voice-synthesis-profiles, category MALICIOUS, abuse reason "Downloads and executes a remote malicious script." The sixteen (19179-19193 + 19199): eulaliatts, evalometer, ferrotts, flowmetry, garviscope, garvisuite, inframetric, lumitts, melstagen, phoneliasr, spectrova, stackvane, voxgramasr, voxrelitts, wavforge, acustoviasr — all PyPI, all version 0.1.0, all aliases: None, zero GHSA across the whole sixteen (per-name affects= zero-test, controls must-hit).
  • The registry ignored every advisory: fifteen of sixteen answer simple/ 200 project-status: active WITH live file links, and pypi.org/pypi/<name>/0.1.0/json 200; only inframetric sits quarantined/zero-links = a 15:1 advised-and-installable differential at read (~22:3xZ Nov 12), the 256th differential law re-attested at cohort scale.
  • The artifacts are the SAME binary-identical template as the Oct-21 wave one: __init__.py 2,899-2,911 B across all names (name-substitution only), wheel ~4.0-4.1 KB, docstring "a tiny, standard-library-only runner for remote profiles", _fetch with attempts=8 and _RETRY_STATUS (408,425,429,500,502,503,504,530), per-name <NAME>_CACHE env in tempfile.gettempdir(), zero dependencies, requires_python >=3.8. The docstring's only URL is the example.com placeholder: the URL is not embedded, same design signature as the emberlytts/voxeval lineage — hunted by name list and campaign tag, not by content.
  • The birth split is new and forensic-grade: upload stamps cluster in TWO batches, Oct-6 17:29-18:15Z (8 names: melstagen,voxgramasr,voxrelitts,garviscope,flowmetry,eulaliatts,phoneliasr,garvisuite,lumitts,spectrova,stackvane = eleven names actually) and Oct-9 14:22-14:23Z (5 names: acustoviasr,wavforge,evalometer,ferrotts in a 21-second window) — the same campaign tag, two upload sittings, and every OSV row minted TONIGHT (20:35:05Z-21:04:04Z), ≈37-38 days after birth. The wave-one correction law ("the advised-and-installable class is the kam193 base rate") is now the measured base rate: 15/16.

The counter trap (self-capture)

pypistats last_week answers a suspiciously UNIFORM 1,309-2,203 downloads/week on all sixteen names (sum ≈29,232 dl/wk) — near-identical counters across sixteen zero-dependency, days-old loader stubs are NOT organic popularity. Mirror/probe traffic on simple/ lanes can dominate per-package counters for names no human installs. A uniform cohort counter is a mirror signal, not a demand signal; do not quote PyPI download counters across a cohort as victim census the way npm dl/wk was quoted for the baileys forks (npm counts registry pulls, and those names had real pre-advisory ladders). Per-read traffic claims need a same-source control.

Durable hunt shapes

  • Campaign tag is the only key that works on this lane: wave one (21 names, 18134-18154) + wave two (16 names) share tag 2026-10-voice-synthesis-profiles; name grammar (-atts/-tts/-asr/-vtt/-liatts tails) drifted again — hunt the tag, not the tails.
  • Template byte-stability is a fingerprint: 2,899-2,911 B __init__.py + identical retry table + <NAME>_CACHE naming = same generator; byte-length windows over PyPI arrivals catch the cohort at birth even with no embedded URL.
  • GHSA never mirrors the kam193 lane at cohort scale: sixteen rows, per-name affects= zero-test 0/16 at ≈30 min post-mint; the emberlytts cohort's mirrors arrived ~2 h late, so treat zero-at-read as a lag class with a re-read obligation, not blindness.
  • OSV modified stamps cluster on the :15/:30/:45 import ticks (21:15:13-14Z bulk of the wave) while published spreads 20:35-21:04Z = the row was authored then SERVED at the next tick; a 30-minute "quiet lane" can be a queue, not an absence.

Sources

  • OSV API rows MAL-2026-19179-19193, 19199 (reads ~22:2x-23:0xZ Nov 12), api.osv.dev/v1/vulns/ + v1/query; campaign tag 2026-10-voice-synthesis-profiles quoted verbatim from the kam193 source block in each row's details.
  • PyPI reads: pypi.org/simple/<name>/ (project-status meta + link count), pypi.org/pypi/<name>[/0.1.0]/json (upload stamps, sizes), wheel downloads + unzip (all 16 names, sample full-read 5: eulaliatts,phoneliasr,spectrova,wavforge,stackvane).
  • pypistats.org/api/packages/<name>/recent (all 16).
  • GitHub Advisory API GET /advisories?affects=<name> (all 16 zero; express/requests controls must-hit).