How to check Instances sent through RemoteEvents

Checking an Instance's class doesn't prove the player may use it. How to check a tool, part or model sent by the client belongs to that player.

criticalLuaudit check: unverified-instances

What it looks like

The client sends an Instance, such as a tool, a part, a model or a player, and the server acts on it after checking only its type or class. Nothing checks that the player is allowed to touch that particular instance.

Luau
remote.OnServerEvent:Connect(function(player, tool)
	if typeof(tool) == "Instance" and tool:IsA("Tool") then
		tool.Parent = workspace
	end
end)

How to detect it

  • Find client arguments that are used as Instances: properties set on them, methods called on them (Destroy, Clone, PivotTo, SetAttribute), or reparenting.
  • Check for typeof(x) == "Instance" first, then IsA for the expected class, then an ownership or reach check: the instance is inside the player's own Backpack or Character, belongs to their plot, or is within a sensible distance.
  • IsA alone is not ownership. Every tool in the game passes IsA("Tool").
  • A Player argument is an Instance too. When a handler takes a target player, check IsA("Player") and decide whether the player may act on that target, including target ~= player where acting on yourself makes no sense.

What an exploiter could do

The client can send a reference to any instance it can see that also exists on the server, including other players' characters and the tools they're holding, plus parts of the map. If the server moves, deletes, clones or edits whatever it's given, the exploiter can do the same to things that aren't theirs: take tools out of other players' hands, delete map parts, or duplicate items.

How to fix it

  • Check typeof and IsA first, then check ownership against something the server controls, such as tool.Parent == player:FindFirstChildOfClass("Backpack") or tool:IsDescendantOf(player.Character).
  • Where you can, have the client send a name or id instead and find the instance on the server inside the player's own containers.
  • If any player may use the instance but only when close to it, such as a chest or a door, the check you need is distance. That's World interaction with no distance check in server validation.

Vulnerable example

Vulnerable
local ReplicatedStorage = game:GetService("ReplicatedStorage")

ReplicatedStorage.Remotes.DropTool.OnServerEvent:Connect(function(player: Player, tool: unknown)
	if typeof(tool) ~= "Instance" or not tool:IsA("Tool") then
		return
	end
	tool.Parent = workspace
end)

Fixed example

Fixed
local ReplicatedStorage = game:GetService("ReplicatedStorage")

ReplicatedStorage.Remotes.DropTool.OnServerEvent:Connect(function(player: Player, tool: unknown)
	if typeof(tool) ~= "Instance" or not tool:IsA("Tool") then
		return
	end
	local character = player.Character
	if not character or tool.Parent ~= character then
		return
	end
	tool.Parent = workspace
end)

Common false positives

  • Instances the server finds itself, such as player.Character or player:FindFirstChildOfClass("Backpack"), are not client input.
  • An Instance argument that is only compared, like if part == door then, and never acted on is safe.
  • Instances that only exist on the client arrive on the server as nil, so a handler that returns early on nil handles them.