Vulnerabilities
LuaRocks.org breach: bytecode escaped the rockspec sandbox
Attackers ran code on LuaRocks.org for six weeks through bytecode uploaded as rockspecs. What was exposed, what to rotate and why to upgrade to 3.12.
LuaRocks.org, the main package registry for the Lua language, has disclosed that attackers ran code on its server several times between July 9 and August 20, 2026. The flaw was reported on September 25 through coordination with CISA, the US Cybersecurity and Infrastructure Security Agency, and fixed on September 26. The attacker had access to the account database: usernames, email addresses, bcrypt password hashes, every API key, two-factor secrets and GitHub linking tokens.
If anyone on your team publishes Lua modules, has a LuaRocks.org account or installs rocks in a build pipeline, this affects you. All API keys and sessions have already been revoked, so publishing jobs will fail until someone creates a new key. The same parsing flaw sits in the LuaRocks command-line tool up to 3.11.1, so developer machines and CI images need the upgrade too.
How it works
A rockspec is the file that describes a Lua package: its name, version, source URL and build steps. It is written in Lua itself, so the easiest way to read it is to run it and look at the variables it sets. When a package is uploaded, LuaRocks.org loads the rockspec with loadstring and runs the resulting function with setfenv pointing at an empty table. That is the sandbox: the code sees no os, no io and no require, so in theory it can only set fields.
In Lua 5.1 and LuaJIT, loadstring accepts two kinds of input. One is Lua source text. The other is precompiled bytecode, the instructions the virtual machine actually executes, which starts with the escape byte \27 (0x1B). The parser only expected source and never told loadstring to refuse bytecode.
That matters because LuaJIT does not verify bytecode at all. Normal source code can only produce well-formed instructions, but a hand-made file can contain instructions that read and write outside the function's own data. In the LuaRocks project's words, that gives the code access to arbitrary memory in the server process, and an empty environment table cannot stop code that reaches around it through memory.
A researcher's write-up of the exploit shows the chain in detail. A constant-loading instruction is patched so it reads beyond the function's constant table into neighbouring heap memory. The script scans that memory for the package.loaded table, which still holds the debug library, then uses debug.getfenv to recover the real global environment. From there os.execute runs shell commands as the web application. The only requirement is an ordinary account that can upload a rockspec.
The same loadstring call is in the LuaRocks client. LuaRocks 3.11.1 and older load rockspecs and manifests this way, so a server or mirror that hands back bytecode could run code on the machine that asked for the package.
What attackers did
The LuaRocks team reconstructed the activity from the server:
- On July 9, shell commands were run on the server through malicious uploads.
- On August 7, shell commands were run again, including what looks like an attempt to open a remote shell. The attacker also published three packages:
bcrcewon,7e0b94029db0and7e0b9402f9c8. All three have been removed. - On August 16 and August 20, several hundred automated exploit attempts came through the upload API, reusing the payloads from the published packages. Once the payloads were public on the registry, anyone could replay them.
The team compared every package file in storage against a mirror taken on July 8 and against its database. It found no evidence that an existing module was modified or replaced. The exposure is in the account data: the items listed above plus session records, account activity logs with IP addresses and browser details, and credentials for third-party services the site used. Those service credentials have been revoked, the 2FA secrets deleted, the GitHub tokens revoked through GitHub, and the site moved to a newly built server.
What to do
- Upgrade the client. Install LuaRocks 3.12 or newer on developer machines, build servers and container base images, with priority on anything running LuaJIT or Lua 5.1 (OpenResty, Neovim and LÖVE setups all use LuaJIT).
luarocks --versionshows what you have. The fix passes the"t"(text only) mode toloadstringand rejects any file starting with byte 27. - Replace your API key. Every old key was revoked. Create a new one on LuaRocks.org, then update wherever it is stored: CI secrets,
~/.luarocks/upload_config.luaor environment variables used byluarocks upload. - Change passwords. Sign in again (all sessions were ended), change your LuaRocks password, and change it anywhere else you used the same one. The hashes are bcrypt, which slows cracking, but weak or reused passwords should be treated as exposed.
- Set up 2FA again. The stored secrets were deleted because they were exposed. Remove the old LuaRocks entry from your authenticator app and enrol a fresh one.
- Look for the three packages. Search lockfiles, rockspec dependencies,
luarocks listoutput and local trees (/usr/local/lib/luarocks/rocks*,~/.luarocks/lib/luarocks/rocks*) forbcrcewon,7e0b94029db0or7e0b9402f9c8. LuaRocks says any machine that installed one should be treated as compromised: rebuild it and rotate the credentials it held. - Check GitHub. If you linked GitHub to LuaRocks, review the authorized OAuth apps under Settings, Applications, and your organization's audit log for unexpected activity since July 9. LuaRocks has revoked the tokens, so you only need to relink if you want the integration back.
If you cannot upgrade a build image today, pin it to install only from a mirror you control and only rockspecs you have reviewed as plain text. That limits exposure to a hostile server until the upgrade lands.
The wider lesson for registries
The sandbox here was the textbook Lua 5.1 pattern, and it held for source code. It failed because the loader accepted a second input format that nobody expected to see. Any service that evaluates user-supplied configuration in a scripting language needs the same check: restrict the loader to text, reject anything with a bytecode signature, and run the parse in a separate, unprivileged process with no access to the account database or service credentials.
For consumers, this is another reason to install from a reviewed internal mirror with pinned versions instead of straight from a public registry in CI. A package such as bcrcewon only reaches your builds if someone adds it to the mirror on purpose.
We test build pipelines, package mirrors and registry credentials as part of red team and code review work, and harden CI and developer environments in safeguarding and hardening. If you want help checking your Lua dependencies or rotating what this incident exposed, open the chat and Yaali, our AI agent, will pass your question to an engineer.
Sources: LuaRocks.org security incident, September 2026, This Week in Security, October 4, 2026, This Week in Package Management, October 3, 2026, Conquering the Moon: luarocks.org remote code execution exploit.