Why checking WalkSpeed on the server catches nobody

Exploiters change WalkSpeed and JumpPower on their own client, so the server never sees it. How to measure movement on the server instead.

warningLuaudit check: server-checks-unreplicated-properties

What it looks like

A server script looks for cheating by reading a property: the humanoid's WalkSpeed, JumpPower, JumpHeight or HipHeight, Workspace.Gravity, or CanCollide on the character's parts. An exploiter changes those values on their own client, where the server never sees the change, so the check always passes.

Luau
if humanoid.WalkSpeed > 16 then
	player:Kick("Speed hacking")
end

How to detect it

  • Find server checks that compare a character or world property with a limit and then kick, ban, rubber-band or flag.
  • Roblox's access control guide says instance properties are server-authoritative: most must be changed on the server to be seen by everyone. A speed or jump value set by the client stays on the client. The server reads whatever it last set.
  • Its network ownership guide lists what a client controls instead: its character's position and movement, its physics, and animations. Exploiters set WalkSpeed locally, and the result shows up only as the character moving faster.
  • Checks that measure the effect, such as distance moved over time, height reached or passing through walls, see what the client did. Checks that read the setting don't.
  • The same applies to a client-side check moved to the server unchanged. Look for GetPropertyChangedSignal("WalkSpeed") and similar in server scripts.

What an exploiter could do

They raise their speed, jump height or gravity and turn off collisions on their client. The server's check reads the values it set and finds nothing wrong, so they move freely while the game reports that it has an anti-cheat.

How to fix it

  • Measure what the character does on the server: its position over time, averaged to allow for latency, as Movement checks that punish on one reading describes.
  • Check the result where it matters, such as reach before an interaction or the time taken to finish a course.
  • Keep property reads only for values the server itself changes, such as making sure a speed boost the server applied has ended.

Vulnerable example

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

local maxSpeed = 16
local maxJump = 50

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:WaitForChild("Humanoid") :: Humanoid
		humanoid:GetPropertyChangedSignal("WalkSpeed"):Connect(function()
			if humanoid.WalkSpeed > maxSpeed or humanoid.JumpPower > maxJump then
				player:Kick("Movement values changed.")
			end
		end)
	end)
end)

Fixed example

Fixed
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local maxSpeed = 16
local allowance = 1.5
local window = 2
local strikesBeforeFlag = 3
local strikes: { [Player]: number } = {}
local lastSeen: { [Player]: { position: Vector3, at: number } } = {}

RunService.Heartbeat:Connect(function()
	local now = os.clock()
	for _, player in Players:GetPlayers() do
		local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
		if not root or not root:IsA("BasePart") then
			lastSeen[player] = nil
			continue
		end
		local previous = lastSeen[player]
		if not previous then
			lastSeen[player] = { position = root.Position, at = now }
		elseif now - previous.at >= window then
			local flat = (root.Position - previous.position) * Vector3.new(1, 0, 1)
			if flat.Magnitude > maxSpeed * allowance * (now - previous.at) then
				strikes[player] = (strikes[player] or 0) + 1
				if strikes[player] >= strikesBeforeFlag then
					warn(`Movement flagged for review: {player.UserId}`)
					strikes[player] = 0
				end
			end
			lastSeen[player] = { position = root.Position, at = now }
		end
	end
end)

Players.PlayerRemoving:Connect(function(player)
	strikes[player] = nil
	lastSeen[player] = nil
end)

Common false positives

  • Reading WalkSpeed on the server to apply or remove the game's own boosts isn't a cheat check.
  • Checks on properties the server alone controls and replicates, such as a tool's server-set attributes, are fine.
  • Movement checks that measure position over time, like the fixed example, are the fix. Judge how they punish with Movement checks that punish on one reading.