LuaRocks Security Incident September 2026

22 points by derp_alert 8 hours ago on lobsters | 5 comments

technomancy | 4 hours ago

Rockspecs are Lua files. When a rockspec is uploaded, LuaRocks.org runs it in a restricted environment to read the package’s name and version. The function used to load it accepted precompiled LuaJIT bytecode as well as Lua source.

It feels like 60-70% of the time I read about a security problem in a Lua program, this is the root cause. It's very frustrating because the load function is used to load textual code 99% of the time, but then occasionally people want to use it for bytecode as well. Loading untrusted text is safe and works great, but loading bytecode from an untrusted source is not safe.

The default "mode" argument for load is "bt", meaning "accept bytecode or text". If the default mode were changed to "t" and so it would accept only text unless the caller went out of their way to specify they also want bytecode would make so many of these problems disappear. It's extra frustrating because of how simple it would be to fix the root cause in Lua itself.

mdaniel | 2 hours ago

loading bytecode from an untrusted source is not safe.

I am a lua outsider but the fine article made it sound like it was just a "couldn't be bothered" that the sandbox checks the sourcecode but then is all "welcome, friend" to any bytecode that shows up. I think you and I are pointing out similar laziness but unless I am misunderstanding the situation they do expend the energy to check, but only in half of the cases. If both paths were treated equally untrusted then I would guess your t / bt distinction would be less important

technomancy | 2 hours ago

unless I am misunderstanding the situation they do expend the energy to check, but only in half of the cases.

I wouldn't put it this way. In both cases the sandbox successfully blocks access to standard Lua globals, but that only stops you from doing malicious things with normal textual code; bytecode can still do anything it wants because it can make arbitrary access and doesn't need to go thru the globals table.

If both paths were treated equally untrusted then I would guess your t / bt distinction would be less important

Technically true, but sacrificing the ability to load untrusted text code would ruin one of the greatest strengths of the language. It's not difficult to fix this problem without making such a huge sacrifice. You just need to either fix the defaults, or move the unsafe operation to a different function.

If both paths were treated equally untrusted then I would guess your t / bt distinction would be less important

The problem is lower-level than that. Lua bytecode is “not safe” in the C sense: there were some attempts in Lua 5.1 to verify that bytecode is safe in a similar manner to Java or Wasm, but the checks were repeatedly found to be buggy so they dropped the bytecode verifier in Lua 5.2. As the article mentions, LuaJIT also lacks a bytecode verifier.

A Lua sandbox uses capability-safety to prevent code from doing anything unwanted, and Lua’s compiler ensures that textual source code cannot violate the memory safety of the interpreter. There’s no need for a sandbox to implement any extra checking beyond normal parsing and interpretation; instead you create a sandbox by deleting unwanted modules from the standard library. However, raw bytecode can break memory safety, which is why it is unsafe to load. The bytecode is still running in the same sandbox, but unlike source code, bytecode can break out.

goldstein | an hour ago

even ignoring this bug, I think generally relying on language-level isolation is ill-advised. you’re extending your attack surface to the interpreter and the standard library, and lose any protections provided by virtual memory (e.g. become vulnerable to spectre). the successful attack will also have a huge impact area, as seen in this case.

running untrusted code in bwrap is easy, not that slow, and protects you both against bugs like this, and against attacks on the language environment. (you can even do the setfenv thing inside the container for defence in depth or whatever)