Why loadstring is a backdoor risk in Roblox
Running code from strings with loadstring lets whoever controls the string run code on your server. How to remove it and use data instead.
What it looks like
The server runs code it builds or receives as a string, with loadstring, usually after turning on ServerScriptService.LoadStringEnabled. The string comes from a remote, a web request, a data store or a value object.
Luau
loadstring(source)()
How to detect it
- Search for
loadstringin every server script and module, and checkServerScriptService.LoadStringEnabledin the project file or in Studio.loadstringonly works on the server when that property is on. - For each call, trace where the string comes from. A remote argument,
HttpService:GetAsyncorRequestAsync, a data store, an attribute or aStringValueis a finding: whoever controls that source controls the server. - Look for wrappers that do the same thing, such as a ModuleScript that takes a string and runs it, or a third-party "interpreter" module.
LoadStringEnabledturned on with noloadstringin the code you can see is worth reporting as needs a closer look, since something may be relying on it.
What an exploiter could do
Anyone who can change the string can run any code on the server: give themselves currency or admin, delete the map, kick everyone or change saved data. A web address or a data store that leaks, or a remote that passes a string straight in, hands that power to an outsider.
How to fix it
- Remove the
loadstringcall and turnLoadStringEnabledoff. - Replace remote configuration with data: fetch settings as JSON with
HttpService:JSONDecode, check each value's type and range, and use them as values, never as code. - Ship code as ModuleScripts in the game so it's reviewed and published with each update.
Vulnerable example
Vulnerable
local ReplicatedStorage = game:GetService("ReplicatedStorage") local developerId = 123456789 ReplicatedStorage.Remotes.DevConsole.OnServerEvent:Connect(function(player: Player, source: unknown) if player.UserId ~= developerId or typeof(source) ~= "string" then return end local run = loadstring(source) if run then run() end end)
Fixed example
Fixed
local ReplicatedStorage = game:GetService("ReplicatedStorage") local PlayerData = require(script.Parent.PlayerData) local developerId = 123456789 ReplicatedStorage.Remotes.DevSetCoins.OnServerEvent:Connect(function(player: Player, target: unknown, amount: unknown) if player.UserId ~= developerId then return end if typeof(target) ~= "Instance" or not target:IsA("Player") then return end if typeof(amount) ~= "number" or not (amount >= 0 and amount <= 1e6 and amount % 1 == 0) then return end local profile = PlayerData.get(target) if profile then profile.Coins = amount end end)
Common false positives
- A string that's only ever a constant written in the script itself, run once at start-up, is odd but not controlled by anyone outside. Recommend replacing it with normal code.
- Admin modules that include a sandboxed script runner for staff are still this finding if they use
loadstring. Say what the module is and that it should be checked.