How to find hidden admin backdoors in your game
Free models that give commands to their maker's UserId or group, or add a second handler to your staff remotes. How to find them and remove them.
What it looks like
A script gives commands, tools or staff powers to a UserId, name or group that isn't on the game's own staff list. It's often in a free model, labelled as credits for its maker, or tucked into a second handler on one of the game's own remotes.
Luau
if player.UserId == 31415926 then player.Chatted:Connect(runCommand) end
How to detect it
- List every hardcoded
UserId, player name and group id in server code, including checks throughIsInGroupAsync,GetRankInGroupAsyncandGetRoleInGroupAsync, or the older versions withoutAsync. Ids may be hidden too, built fromstring.charlists,tonumberon joined strings or arithmetic, which is also Hidden or scrambled code. Compare each one with the game's own staff list and the group that owns the game. Any that isn't on it needs a reason. - Look at what each match unlocks: chat commands through
Chatted,TextChatCommandorTextChatServicecallbacks, admin GUIs, tools cloned from storage, currency, kicks or bans, or a place on an admin list. - Look for more than one
OnServerEventconnection on the same remote, especially staff remotes. A second handler with its own permission check is a common way to slip extra users in. - Look for checks against
game.CreatorId. In a group game it's the group's id, not a person's, soplayer.UserId == game.CreatorIdlets in whichever account happens to have that number. Checkgame.CreatorTypefirst, or use the staff list.
What an exploiter could do
Whoever the script lets in can use the powers on any server of the game: kicking or banning players, giving themselves currency, running commands or shutting servers down. Because the check names them on purpose, nothing in the game's own admin system shows them as staff.
How to fix it
- Remove the grant and the script it came in. If it's part of a free model, replace the model.
- Keep one staff list on the server, and give every staff power through it, with one handler per staff remote.
- Search the game for the same ids. Backdoors are often copied into several scripts.
Vulnerable example
Vulnerable
local Players = game:GetService("Players") local modelCredits = 31415926 Players.PlayerAdded:Connect(function(player) if player.UserId ~= modelCredits then return end player.Chatted:Connect(function(message) local name = string.match(message, "^;kick ([%w_]+)$") local target = name and Players:FindFirstChild(name) if target and target:IsA("Player") then target:Kick() end end) end)
Fixed example
Fixed
local Players = game:GetService("Players") local staff: { [number]: boolean } = { [123456789] = true, } local function listen(player: Player) if not staff[player.UserId] then return end player.Chatted:Connect(function(message) local name = string.match(message, "^;kick ([%w_]+)$") local target = name and Players:FindFirstChild(name) if target and target:IsA("Player") and target ~= player then target:Kick("Removed by a moderator.") end end) end Players.PlayerAdded:Connect(listen) for _, player in Players:GetPlayers() do listen(player) end
Common false positives
UserIds or group ranks that match the game's own staff, owner or owning group are normal. Ask the user who their staff are if the code doesn't say.- Ids used only for something cosmetic, like a name tag or a thank-you message, give nothing away. Mention them only if they come from a free model.
- Well-known admin modules read their admin list from settings the game owner fills in. Check those settings rather than reporting the module.