Lua presents global variables as entries in an ordinary table. The global environment is normally available through _G, so these expressions are equivalent:
1 | answer = 42 |
The table representation makes dynamically named globals straightforward, although an explicit application table is usually clearer than adding more global state.
Detecting undeclared globals
A development-time metatable can catch accidental reads and writes:
1 | local declared = {} |
This is a debugging aid rather than a security boundary, and it relies on debug information.
_ENV in Lua 5.2 and later
Global-looking names are compiled as fields of a special lexical variable named _ENV:
1 | local print = print |
Changing _ENV affects only global-name resolution in its lexical scope; local variables continue to work normally. A private environment can inherit selected host values through __index, but an allowlist is safer for untrusted code than inheriting all of _G.
Modules and explicit environments
Modules are best represented by a local table returned from the file:
1 | local M = {} |
The caller uses local mathx = require("mathx"), avoiding global namespace pollution.
load can compile code with a chosen environment:
1 | local env = { print = print, value = 21 } |
An environment alone is not a complete sandbox. Restrict libraries and resource use as well, and never expose capabilities such as unrestricted io, os, package, load, or debug to untrusted scripts unless deliberately controlled.