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.

criticalLuaudit check: unvalidated-arguments

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 OnServerEvent connection and every OnServerInvoke assignment. 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 typeof check before its first use. type is not enough for Roblox types such as Instance or CFrame, because it returns "userdata" for all of them.
  • Type annotations such as settingName: string are not checks. Luau doesn't enforce them at runtime, so an annotated parameter can still hold any value. Only unknown makes 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 Instance property, SetAttribute, FindFirstChild or WaitForChild on 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 unknown so the type checker makes you narrow them before use.
  • Check every argument with typeof and 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 <= 1 rather than not (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.
  • OnClientEvent handlers run on the client and are outside this check. Arguments there come from the server.