[Feedback Wanted] Server-Side AntiCheat

[Preview] Server-Sided Anti-Cheat – Feedback Wanted

Hey Devs,

I’m working on an anti-cheat that detects exploiters by relying on server-side logic. Since all processing happens on the server, spoofing or blocking it becomes much trickier.

Its still early in development, so Id love your feedback, feature ideas, or rare edge cases I might have missed. Check out the preview videos!

Share your thoughts!

Preview Video

3 Likes

Seems pretty good from the video, being able to detect walkspeed as low as 20 quickly with no false positives is quite hard but many games have conveyors or parts with velocity or tween parts so you should try those also it looks like fly is only caught when you move forward :thinking:. You should also remove ownership when someone is flagged

Also when you trigger speed you get teleported into the air slightly

1 Like

Yeah, that’s true thanks for the feedback! The fly check has definitely been the most challenging part for me. I’ve been doing my best to reduce false flags while still keeping it accurate.

1 Like

Hello, thank you again for your feedback! I think this version is much better than before I’ve added a few new checks to support fly check

Aye good progress, you should make a public test place, I’d love to try it.
I digged through my old videos and found these:




You could try these tests aswell.

1 Like

Hello, thanks again for your feedback! I’m planning to create a public test area where players can try out the anti-cheat. Right now, I’m still working on it, but once I feel it’s fully ready, I’ll be happy to open it up for everyone to test. Also, I saw your video, Sure thing I tested it out here’s the result:

2 Likes

Crazy that would already support most games on Roblox, if your looking to add any features there are currently exploits that allow you to respawn faster than RespawnTime and infinite death meaning you stay dead longer than RespawnTime. Theres also a exploit that gives players server ownership of their hats when they die so exploiters can use fake limb hats to create fake characters which you can fix by deleting hats when they die and a few other things:

function CharacterHandler:Add(Player, Character)
	local Head = Character:FindFirstChild("Head")
	local Humanoid, HRP = Character:FindFirstChild("Humanoid"), Character:FindFirstChild("HumanoidRootPart")
	if not HRP or not Humanoid then
		return
	end

	local Animator = Humanoid:FindFirstChild("Animator")
	local Data = self.PlayerData[Player]

	if self.LastDeaths[Player] and os.clock() - self.LastDeaths[Player] < (Players.RespawnTime - 0.5) then
		return self:TakeAction(Data, "Fast Death", function()
			Player:Kick("Fast Death")
		end)
	end

	table.insert(Data.Connections, Player.CharacterAppearanceLoaded:Connect(function()
		local Accessories = Humanoid:GetAccessories()
		if #Accessories > 50 then
			return self:TakeAction(Data, "Hat Crash", function()
				Player:Kick("Hat Crash")
			end)
		end

		for _, Accessory in Accessories do
			if Accessory:FindFirstChildWhichIsA("Script", true) then
				return self:TakeAction(Data, "Hat Backdoor", function()
					Player:Kick("Hat Backdoor")
				end)
			end

			for _, Vector in {"X", "Y", "Z"} do
				local Handle = Accessory:FindFirstChild("Handle")
				if Handle and Handle.Size[Vector] >= 5 then
					Accessory:Destroy()
				end
			end
		end
	end))

	table.insert(Data.Connections, Animator.AnimationPlayed:Connect(function(AnimationTrack)
		Data.Tolerance.AnimationPlayed += 1

		if os.clock() - Data.Timing.LastAnimation >= 1 then
			if Data.Tolerance.AnimationPlayed >= 50 then
				self:TakeAction(Data, "Animation Spam", function()
					Player:Kick("Animation Spam")
				end)
			end
			Data.Tolerance.AnimationPlayed = 0
			Data.Timing.LastAnimation = os.clock()
		end

		local AnimationId = string.match(AnimationTrack.Animation.AnimationId, "%d+")
		if not self.Whitelist[AnimationId] then
			self:TakeAction(Data, "Animation", function()
				Player:Kick("Animation")
			end)
		end

		if AnimationTrack.Speed >= 10 then
			self:TakeAction(Data, "Animation Speed", function()
				self:BreakJoints(Data)
			end)
		end
	end))

	table.insert(Data.Connections, Humanoid.StateChanged:Connect(function(Old, New)
		Data.Tolerance.StateChanges += 1

		if os.clock() - Data.Timing.LastState >= 1 then
			if Data.Tolerance.StateChanges >= 50 then
				self:TakeAction(Data, "State Spam", function()
					Player:Kick("State Spam")
				end)
			end

			Data.Tolerance.StateChanges = 0
			Data.Timing.LastState = os.clock()
		end

		if Data and Old == Enum.HumanoidStateType.Dead and New ~= Enum.HumanoidStateType.Dead then
			self:TakeAction(Data, "God", function()
				self:BreakJoints(Data)
			end)

		elseif Old ~= Enum.HumanoidStateType.Dead and New == Enum.HumanoidStateType.Dead then
			if Data and Players.CharacterAutoLoads then
				task.delay(Players.RespawnTime + 0.5, function()
					if Player.Character == Character then
						self:TakeAction(Data, "Respawn Time Exceeded", function()
							Player:Kick("Respawn Time Exceeded")
						end)
					end
				end)
			end
		end
	end))

	table.insert(Data.Connections, Humanoid.PlatformStanding:Connect(function()
		self:TakeAction(Data, "Platform Standing", function()
			Player:Kick("Humanoid Platform Standing")
		end)
	end))

	table.insert(Data.Connections, Humanoid.AncestryChanged:Connect(function(_, Parent)
		if game:IsAncestorOf(Character) then
			if Character.PrimaryPart and Character:IsAncestorOf(Character.PrimaryPart) then
				if not Parent or not game:IsAncestorOf(Humanoid) then
					self:TakeAction(Data, "God", function()
						self:BreakJoints(Data)
					end)
				end
			end
		end
	end))

	table.insert(Data.Connections, Character.ChildAdded:Connect(function(Child)
		if Child:IsA("BasePart") then 
			Child.CollisionGroup = "Characters"

		elseif Child:IsA("Tool") then
			local Count = 0
			for _, Tool in Character:GetChildren() do
				if Tool:IsA("Tool") then
					Count += 1
				end
			end

			if Count > 1 then
				self:TakeAction(Data, "Multiple Tools", function()
					self:BreakJoints(Data)
				end)
			end
		end
	end))

	table.insert(Data.Connections, Humanoid.Died:Connect(function()
		task.delay(1, function()
			if Player.Character == Character then
				for _, Accessory in Humanoid:GetAccessories() do 
					Accessory:Destroy()
				end
			end
		end)

		for _, Descendant in Character:GetDescendants() do
			if Descendant:IsA("BasePart") then
				if Descendant:CanSetNetworkOwnership() then
					Descendant:SetNetworkOwner(nil)
				end
				Descendant.AssemblyLinearVelocity = Vector3.zero
			elseif Descendant:IsA("Script") then -- Health script, doesnt have a .died check so it will regen health with inf death making you unkillable
				Descendant:Destroy()
			end
		end
		self.LastDeaths[Player] = os.clock()
	end))
end
1 Like

This looks pretty decent so far.
I think personally that it’s always good to reload the character as an extra measure of security as it pretty much resets the character to their default state unless it gets updated to a new state permanently ingame until otherwise (This is more tied in with saved data & databases though.). You can also save their Vector3 position such as via part cloning for example so you can instantly teleport them back and then remove that part.
If you get any syntax errors (which can lead to other errors like semantic or runtime) trying to do this, like HRP returning nil, I suggest using module scripts as they’re a more decent solution for this kinda work and a good workaround, especially with said syntax errors. :+1: