How to find backdoor remotes hidden in models
Some free models create their own remotes that let anyone change or delete your game. How to spot them and where remotes should live.
What it looks like
A script inside a model, plugin leftover or unrelated system creates its own RemoteEvent or RemoteFunction while the game runs, often with a name that looks like part of Roblox or the game. Its handler does something powerful for whoever fires it: changes any property by path, deletes instances, gives tools or runs code.
local remote = Instance.new("RemoteEvent") remote.Name = "DefaultChatEvents" remote.Parent = game:GetService("ReplicatedStorage") remote.OnServerEvent:Connect(function(player, path, property, value) ... end)
How to detect it
- Search every server script for
Instance.new("RemoteEvent"),Instance.new("RemoteFunction")andInstance.new("UnreliableRemoteEvent"), and for remotes cloned from somewhere unexpected. - Compare them with how the game normally sets up remotes, such as one folder in
ReplicatedStoragedefined in the project. A remote made by a script inside a prop, vehicle or free model, or by a script whose name has nothing to do with it, needs a close look. - Roblox's guide on third-party vulnerabilities says backdoors in models give their creators server-side access and can wait for a particular command. A hidden remote is how that command arrives.
- Read the handler. Roblox's security guide warns against remotes that let a client name an arbitrary path or instance for the server to change or delete, and against loading assets or running code from remote arguments.
- Names copied from Roblox systems, such as chat or player list names, random strings and names that change each run are strong signs.
What an exploiter could do
Whoever knows the remote is there fires it with any path, property and value. They can delete the map, change other players' characters, give themselves tools, or turn off the game's own checks, and they can share the remote's name with anyone else. If the handler runs code or requires an asset, they get full control of the server.
How to fix it
- Delete the script and the model it came with, then search the rest of the game for copies.
- Define the game's remotes in one known place, and treat any remote created elsewhere as suspect.
- Never write handlers that set properties or delete instances by a path the client sends. Give each action its own remote with its own checks.
Vulnerable example
local ReplicatedStorage = game:GetService("ReplicatedStorage") local remote = Instance.new("RemoteFunction") remote.Name = "PlayerListRemote" remote.Parent = ReplicatedStorage remote.OnServerInvoke = function(player: Player, path: unknown, property: unknown, value: unknown) if typeof(path) ~= "string" or typeof(property) ~= "string" then return false end local target: Instance? = game for name in string.gmatch(path, "[^%.]+") do target = target and target:FindFirstChild(name) end if target then return pcall(function() (target :: any)[property] = value end) end return false end
Fixed example
local ReplicatedStorage = game:GetService("ReplicatedStorage") local Workspace = game:GetService("Workspace") local lamp = Workspace:WaitForChild("Lobby"):WaitForChild("Lamp"):WaitForChild("PointLight") :: PointLight local cooldown = 1 local lastToggle: { [Player]: number } = {} ReplicatedStorage.Remotes.ToggleLamp.OnServerEvent:Connect(function(player: Player) local now = os.clock() if now - (lastToggle[player] or 0) < cooldown then return end lastToggle[player] = now lamp.Enabled = not lamp.Enabled end)
The remote is defined with the game's others in ReplicatedStorage.Remotes, and it does one thing.
Common false positives
- Games that create all their remotes from one setup script, with clear names and their own checks, are fine. Judge the handlers with the remote-events checks.
- Frameworks and libraries the developer chose, such as networking modules, create remotes on purpose. Check where they came from and that their handlers are the game's own.
- A remote made at runtime by the game's own system, with one narrow job and the usual checks, isn't this finding just because it's created in code.