How to stop players getting others kicked or banned
Report buttons, vote-kicks and client anti-cheat that let one client punish another. How to keep punishment decisions on the server or with moderators.
What it looks like
A remote lets a client name a player, and the server kicks, bans, kills, freezes or flags that player without checking anything itself. It's often part of a report button, a vote-kick, or anti-cheat that runs on each client and reports other players it thinks are cheating.
Luau
remote.OnServerEvent:Connect(function(player, cheater) cheater:Kick("Exploiting") end)
How to detect it
- Find every server call to
Kick, every write to a ban list or ban data store, and every punishment such as killing, freezing, muting or resetting data. - For each one, trace who the target is. If a client argument picks the target, check what the server verifies before acting.
- A report that only gets logged for a human moderator is fine. A report that acts straight away on one client's word is a finding.
- For vote-kicks, check that the server counts votes per voter, needs a real share of the server and limits how often a vote can start.
What an exploiter could do
They can kick, ban or punish any player they like, as often as they like, including the game's own staff. On a game with a saved ban list, that harm lasts until someone notices and undoes it by hand.
How to fix it
- Never punish on one client's word. Decide on the server, from what the server itself can see.
- Turn reports into a log entry or a message for moderators, with a per-player cooldown, and leave the decision to a person or to server-side checks.
- If you keep vote-kicks, count one vote per player, require a majority of the server, add a cooldown and don't let a vote ban anyone permanently.
Vulnerable example
Vulnerable
local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") ReplicatedStorage.Remotes.ReportCheater.OnServerEvent:Connect(function(player: Player, suspect: unknown) if typeof(suspect) ~= "Instance" or not suspect:IsA("Player") or suspect == player then return end if suspect.Parent == Players then suspect:Kick("You were reported for exploiting.") end end)
Fixed example
Fixed
local HttpService = game:GetService("HttpService") local Players = game:GetService("Players") local ReplicatedStorage = game:GetService("ReplicatedStorage") local cooldown = 60 local lastReport: { [Player]: number } = {} ReplicatedStorage.Remotes.ReportCheater.OnServerEvent:Connect(function(player: Player, suspect: unknown) if typeof(suspect) ~= "Instance" or not suspect:IsA("Player") or suspect == player then return end local now = os.clock() if now - (lastReport[player] or 0) < cooldown then return end lastReport[player] = now print(HttpService:JSONEncode({ reporter = player.UserId, suspect = suspect.UserId, time = os.time(), })) end) Players.PlayerRemoving:Connect(function(player) lastReport[player] = nil end)
Common false positives
- Kicks the server decides on its own, such as a server-side speed check, aren't client-triggered.
- Admin commands are covered by Admin powers not checked on the server. Report a moderation remote there if it's meant for staff only.
- A client kicking itself, with
LocalPlayer:Kick(), only affects that player.