Why server errors shouldn't be sent to players
Raw errors and web replies tell exploiters how your server works. How to log the details and send players a short message instead.
What it looks like
When something fails on the server, the raw error message or the web service's reply goes back to the player: returned from a RemoteFunction, sent with FireClient, or shown in a message. It's meant to help with debugging, but every player can read it.
Luau
local ok, err = pcall(callService, player, code) if not ok then return false, err end
How to detect it
- Find
pcallandxpcallresults,debug.traceback()output, and HTTP reply fields (Body,StatusMessage,Headers) that are returned fromOnServerInvokeor passed toFireClient,FireAllClients, attributes or GUI text. - Luau error messages name the script and line that failed, such as
ServerScriptService.Shop:42. Errors from data store and HTTP calls describe what went wrong, and a web service's error reply can contain anything the service chose to put in it, including internal details or other users' data. - Roblox's access control guide says anything that reaches a client can be read by an exploiter, and that exposed server logic makes it faster and easier to find vulnerabilities.
- Look for "debug mode" switches that send full errors only in some cases, and check they can't be turned on by the client.
What an exploiter could do
They send bad input on purpose until something fails, then read the errors. They learn the names of server scripts and modules, data store names and keys, web addresses and how the service answers, which helps them find the next weakness. If the web service's reply ever includes tokens or other players' data, they get those too.
How to fix it
- Log the full error on the server with
warn, and send the client a short, fixed message or code, such as"failed"or"try_again". - Never pass a web service's reply body or headers to a client. Pick out only the fields the client needs, after checking them.
- Use the same message whether the failure came from bad input or a server problem, unless the player needs to fix their input.
Vulnerable example
Vulnerable
local DataStoreService = game:GetService("DataStoreService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local saves = DataStoreService:GetDataStore("BuildSaves") ReplicatedStorage.Remotes.LoadBuild.OnServerInvoke = function(player: Player, slot: unknown) if typeof(slot) ~= "number" or slot ~= math.floor(slot) or slot < 1 or slot > 3 then return false, "Invalid slot" end local ok, result = pcall(saves.GetAsync, saves, `User_{player.UserId}/Slot{slot}`) if not ok then return false, debug.traceback(tostring(result)) end return true, result end
Fixed example
Fixed
local DataStoreService = game:GetService("DataStoreService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local saves = DataStoreService:GetDataStore("BuildSaves") ReplicatedStorage.Remotes.LoadBuild.OnServerInvoke = function(player: Player, slot: unknown) if typeof(slot) ~= "number" or slot ~= math.floor(slot) or slot < 1 or slot > 3 then return false, "invalid_slot" end local ok, result = pcall(saves.GetAsync, saves, `User_{player.UserId}/Slot{slot}`) if not ok then warn(`Build load failed for {player.UserId}: {result}`) return false, "try_again" end return true, result end
Common false positives
- Fixed messages written by the developer, such as "Not enough coins", are fine.
- Errors shown only in Studio, behind
RunService:IsStudio(), don't reach live players. - Roblox's developer console guide says only the game owner, or group members with edit permission, can see server output. Printing errors with
warnis the fix, not a finding.