How to validate RemoteEvent arguments in Roblox
Every argument after the player in OnServerEvent comes from the client. How to check types with typeof, use allowlists and reject bad values before they reach saved data.
What it looks like
A server handler uses the arguments from OnServerEvent or OnServerInvoke without checking their type or value first. The arguments go straight into a table key, a property, a saved profile or a function call.
Luau
remote.OnServerEvent:Connect(function(player, settingName, value) profile.Settings[settingName] = value end)
How to detect it
- Find every
OnServerEventconnection and everyOnServerInvokeassignment. The first parameter is the player who fired the remote and is set by Roblox. Every parameter after it comes from the client. - For each client parameter, look for a
typeofcheck before its first use.typeis not enough for Roblox types such asInstanceorCFrame, because it returns"userdata"for all of them. - Type annotations such as
settingName: stringare not checks. Luau doesn't enforce them at runtime, so an annotated parameter can still hold any value. Onlyunknownmakes the type checker insist on a real check. - Check that strings used as keys or names are matched against an allowlist, and that values written to saved data have the type and range the rest of the game expects.
- Treat these uses of an unchecked argument as findings: a table key or value in player data, an
Instanceproperty,SetAttribute,FindFirstChildorWaitForChildon server objects, string formatting into names, or a call into a module that does any of these. - Follow arguments into helper modules. Validation can live in a shared function, and so can the missing validation.
What an exploiter could do
They can send any value in place of each argument: a different type, a huge string, a nested table, an Instance or nothing at all. Depending on where the value lands, they can write keys the game never meant to expose into their saved data, store values of the wrong type that break other scripts reading that data, or bloat a profile until saving it fails.
How to fix it
- Annotate client parameters as
unknownso the type checker makes you narrow them before use. - Check every argument with
typeofand return early if it's wrong. - Match names and ids against an allowlist the server owns, and store a value only if it has the type and range that setting expects. Writing
value >= 0 and value <= 1rather thannot (value < 0)also rejects NaN, since every comparison with NaN is false. - Do the checks before any side effect, so a bad call changes nothing.
Vulnerable example
Vulnerable
local ReplicatedStorage = game:GetService("ReplicatedStorage") local PlayerData = require(script.Parent.PlayerData) ReplicatedStorage.Remotes.SaveSetting.OnServerEvent:Connect(function(player, settingName, value) local profile = PlayerData.get(player) if not profile then return end profile.Settings[settingName] = value end)
Fixed example
Fixed
local ReplicatedStorage = game:GetService("ReplicatedStorage") local PlayerData = require(script.Parent.PlayerData) ReplicatedStorage.Remotes.SaveSetting.OnServerEvent:Connect(function(player: Player, settingName: unknown, value: unknown) local profile = PlayerData.get(player) if not profile then return end if settingName == "MusicVolume" and typeof(value) == "number" and value >= 0 and value <= 1 then profile.Settings.MusicVolume = value elseif settingName == "ShowDamageNumbers" and typeof(value) == "boolean" then profile.Settings.ShowDamageNumbers = value end end)
Common false positives
- A handler with no client parameters, or one that never uses them, has nothing to validate.
- An argument only compared for equality against server-owned values, such as
if choice == "Accept" then, can't do harm on its own. Any other use of it still needs a check. - Validation done in a helper module before use counts. Read the helper before reporting.
OnClientEventhandlers run on the client and are outside this check. Arguments there come from the server.