A Guide to Making Proper Anti-Exploits

Hello, I am Reapimus! Today I am making this guide to the proper way of making an anti-exploit, because far too often on the developer forum I am seeing so many people giving poor anti-exploit advice or trying to make ones that simply won’t work, and I am hoping that by making this guide, more people will learn and know how to make an effective, and proper anti-exploit for their games.

So with that introduction out of the way, lets start with this guide!

What is an Exploit?

To start off with, it’s important that you have an understanding as to what exactly an exploit is. An exploit is code that the player injects into their client in order to allow them to perform actions that would normally not be possible or allowed by the game and exploit it.

What Can Exploits Access?

That explanation of what an exploit is may have been obvious to many, however, what many don’t know is what exactly an exploit is able to access. Exploits are only capable of accessing anything that their client can see, meaning they do not have access to the source code of any server-side scripts.

They do, however, have full access to all client-side code you have for your game, as well as can see anything that your code is doing, one example being what remotes you are firing and what arguments you are providing to them.

Exploits are furthermore, capable of modifying any information that the client is sending through a remote to the server, which effectively rules out any chances of trying to create an anti-exploit where the server asks the client if it is cheating or not.

For further, more-detailed information on what exploits are, you can check out this post made by Autterfly:

What Should an Anti-Exploit Do?

There are also many misconceptions as to what exactly an anti-cheat should be capable of doing, so in the simplest terms. Yes, an anti-exploit should be checking for if a player is cheating or not and appropriately punishing them for doing so, no, an anti-cheat should not be able to immediately kick or ban a player if it detects they are exploiting.

Why is immediately kicking or banning a player it thinks is exploiting an immediate no? Because your anti-cheat will never be flawless, it is bound to make mistakes and give out false positives, which will be gone over later in this guide. You don’t want to have players complaining that they were incorrectly banned by your anti-cheat due to a false positive!

Your anti-cheat should not be capable of completely ruining a player’s experience at all, it should only be slightly inconveniencing them in the event they do accidentally trigger one of your anti-exploits checks.

False Positives

Now, naturally your anti-cheat will run into false positives due to circumstances outside of your control that cannot be avoided or fixed. Examples of this could be due to something like a player with a bad ping (lagging) seemingly going faster than should be allowed or this lag causing them to appear to be teleporting around to the server.

How can we get around this issue and reduce the number of false positives? The simple answer is to add a reasonable margin of error for each check you implement into your anti-exploit. For example: if you’re making a check to prevent speed or teleport exploits, you’re gonna want to add a margin of error so that laggy players are less likely to accidentally trigger the check, an example of this being if the maximum walk speed is 16, your margin of error should make the maximum speed the anti-exploit checks for something higher such as 24-28.

In short, each check in your anti-exploit should account for scenarios that a normal player may encounter that accidentally trigger the check, allowing them to have as little inconvenience as possible.

What Relies on the Client/what should I not use?

As I’ve seen recently from a few devforum posts, it should be gone over exactly what relies on the client for information as well as what should not be used to check for exploits, since there are many things in Roblox which will rely on information given from the client or replicate from client to server.

PlatformStanding

While it is common that most fly exploits use PlatformStanding somehow, it isn’t a good idea to check for that being enabled as this would make the assumption that all fly exploits both rely on default humanoid states to function properly and the assumption that none of your code or any developers’ code who uses your anti-exploit will ever use PlatformStanding, which isn’t a good thing.

The final notes on this one is that the client may even prevent the state of PlatformStanding replicating to the server to begin with in order to fully bypass such a check!

Humanoid.Running/Humanoid States

While Humanoid.Running can get you the client’s current WalkSpeed from the server’s end, it shouldn’t be relied upon because of the sole fact that it gets this information from the client, as should be expected. The same applies to humanoid states, both of these are determined for the most part, by the client, meaning that they can be easily spoofed and there’s nothing that can be done to stop this.

Replication exceptions

As documented by Roblox on this dev hub page, there are a few exceptions that allow the client to replicate some changes to the server, such as the Jump and Sit properties of their own character’s humanoid or the TimePosition and Playing properties of an existing sound object on the server.

They can also control the position and rotation of any unanchored parts that their client currently owns/simulates the physics for.

Finally, it isn’t documented by Roblox but it appears that the client is able to delete anything inside of their own character and have the deletion of that instance replicate to the server, which allows them to perform other kinds of exploits such as god mode by deleting their humanoid and creating a client-side only version of it.

Anti-Exploit Checking Methods

So you now fully understand how false positives should be dealt with, what relies on the client, and what an anti-exploit should be doing, now all that’s left to do is actually implementing the checks to prevent these various exploits.

The first thing I am going to mention before I start explaining some of these checks is: absolutely DO NOT trust the client or create a completely client-sided anti-exploit, they do not work as the exploiter can disable and trick them into believing everything is working as it should.

For any pieces of example code in the below sections, playerState should be a variable referencing a table where you store information about a player’s state that persists across each check, obtaining such a table could look something like this:

local PlayerStates = {}

function getPlayerState(player)
    if PlayerStates[player] == nil then
        PlayerStates[player] = {}
    end
    return PlayerStates[player]
end

local playerState = getPlayerState(targetPlayer)

It should be noted as well that you would want to clean up/remove the player state for any players that leave.

Speed/Teleport Checks

One of the common types of exploits, is the speed or teleport exploit (these two can be a single check because they function practically identically in theory). With this one, it isn’t possible to simply check the Humanoid’s WalkSpeed nor can we check the current Velocity of the player’s character, instead, we must make use of some form of loop on the server (preferably, you’d be using RunService to handle this loop).

Then, the question comes as to how exactly do you check what a player’s speed is if we cannot look at the WalkSpeed of the Humanoid or the Velocity of the character? The simple answer is, we do a little bit of math to determine their speed based on the distance they traveled since our last check, and the time since our last check.

This would give us something like: speed = distance/time, and an implementation of this may look something like this:

local distance = ((playerState.LastPosition - character.PrimaryPart.Position) * Vector3.new(1, 0, 1)).Magnitude
local elapsed = os.clock() - playerState.LastCheck
local speed = distance/elapsed

In this example piece of code, you may have also noticed that I am removing the Y axis of distance, this is because the Y axis can open up the check to more false positives because players can move up/down objects like ladders faster than they can walk, and they can fall much faster than they can walk as well.

With the issue of getting the player’s speed out of the way, the next part comes in determining if they are surpassing the maximum walk speed, and how to appropriately handle this. To determine if they are surpassing it, you would check if their speed is greater than the maximum speed with whatever your threshold is set to added onto it.

For example:

if speed > character.Humanoid.WalkSpeed + THRESHOLD then
    print("The player triggered the check!")
end

In this example case, the maximum walking speed has simply been determined by whatever the server sees the player’s WalkSpeed as. Now the next part would be appropriately punishing the player. For a check against teleporting and speed exploits, your best choice would be to reset the players position back to where they were during the previous check.

Which, extending the previous example, would look something like this:

if speed > character.Humanoid.WalkSpeed + THRESHOLD then
    character:SetPrimaryPartCFrame(CFrame.new(playerState.LastPosition))
end

Noclip Checks

The next type of exploit that you may want to check for is noclipping, which is a somewhat tricky one to check for. For those of you who don’t know, noclipping is an exploit where a player walks through solid walls that they should not be capable of walking through.

So, how do we check for such an exploit? We cast a ray from the player’s position during the last check, toward the player’s current position, and then we cast one from the player’s current position, toward the player’s previous position. If the first ray hits something solid, then we can assume that they might be noclipping.

If the second one doesn’t hit anything at all but the first did, we can assume the player has already noclipped and is inside of the object they are noclipping through. If the second does hit something solid, we can then get the distance between the first hit and the second hit to determine how much through the object the player possibly noclipped.

Using this information, we can only consider the player to be noclipping if they are either:

  • Inside of a solid object, or
  • They noclipped through a large portion of a solid object

Now the second condition may sound a little bit confusing, so here is some example code of how this check may look:

local cast1 = workspace:Raycast(playerState.LastPosition, CFrame.lookAt(playerState.LastPosition, character.PrimaryPart.Position).LookVector, playerRaycastParams)
local cast2 = workspace:Raycast(character.PrimaryPart.Position, CFrame.lookAt(character.PrimaryPart.Position, playerState.LastPosition).LookVector, playerRaycastParams)

if cast1.Instance and cast2.Instance then
    local depth = (cast1.Position - cast2.Position).Magnitude
    if depth > THRESHOLD then
        print("The player triggered the check!")
    end
elseif cast1.Instance and not cast2.Instance then
    print("The player is likely inside of the object!")
end

And finally, the appropriate method to punish players who trigger this check would be the same way speed/teleport exploiters are punished: sending them back to wherever they were during the last check.

Fly Checks

As was stated previously, while most fly exploits make use of PlatformStanding, it isn’t a good idea to check specifically for this property being enabled on the humanoid to detect fly exploits because it makes the assumption that the client is even accurately replicating it and that their exploit uses it and not a custom system.

So then what do we do instead to check if the player is using a fly exploit? Simple. We check if they have been off the ground for an extended period of time, however, this alone can trigger false positives during perfectly normal situations such as falling.

Which means we also need to check if the player has been off the ground for an extended period of time, and that they aren’t falling. So now with that in mind, we can use a system similar to our anti speed/teleport exploit check to identify if they are falling or not.

The first step in checking for this exploit would be to check if they are on the ground, which would also need to account for the possibility that the player’s character might be of a none-standard height (such as is the case with R15 characters), which would be done by using the size of the root part on the Y axis and the humanoid’s HipHeight if it is R15, but if it isn’t R15, we know it has a static height and can return that instead, which would look like so:

local function getRayHeight(character)
    local R15 = character.Humanoid.RigType == Enum.HumanoidRigType.R15
    return R15 and character.Humanoid.HipHeight + character.Humanoid.RootPart.Size.Y/2 + 1 or 3.5
end

Now that we know what the height of the player’s character is, we can use it to cast a ray toward the ground to check if the player is on it or not, which would look like so:

local cast = workspace:Raycast(character.PrimaryPart.Position, -getRayHeight(character), playerRaycastParams)

local isOnGround = cast and cast.Instance.CanCollide

Note: While I am keeping this code example here because it might be useful to someone, there was an update that made it reliable to check the humanoid.FloorMaterial because the property can no longer be manipulated by the client, that code would look like:

local isOnGround = humanoid.FloorMaterial ~= Enum.Material.Air

Since we now know that the player is on the ground or isn’t on the ground, we can save that they are either grounded, or if they aren’t on the ground, we save that they are not on the ground, with a timestamp referring to the last time they were on the ground.

So now with that information, we need to have a loop somewhere that is checking with this information whether or not the player is flying, and also that they aren’t falling, which would look something like this:

local distance = ((playerState.LastPosition - character.PrimaryPart.Position) * Vector3.new(0, 1, 0)).Magnitude

if distance >= 0 and os.clock() - playerState.lastGrounded > 5 then
    print("The player triggered the anti-fly check!")
end

With this we now know whether or not the player may be flying, and can punish them by either sending them back to their last known position, or teleporting them to a ground level. It may also be noted that you could want to check how fast they are falling because it might just be them occasionally flying downwards a bit in order to trick the check.

Other Checks

Now, the kinds of checks I have listed before are the more common kinds of checks that can apply to just about any game on the Roblox platform, and even any multiplayer game in general. You’re always gonna encounter issues that are specific to your game because not every game is the same. So I will go over some example scenarios of how you might approach an anti-exploit for a game-specific issue.

Melee Weapons

Let’s say you have a neat sword-fighting game, in order for players to have fast-paced sword combat you’re very likely going to want to handle hit detection on the client side and have it tell the server that they hit a player, but this then comes with the issue that you’re trusting the client.

To work around this issue, you implement server-side checks whenever the client reports that they hit another player. You’d want to:

  • Verify the player is within reasonable distance of their target
  • Verify the cooldown for their sword (so they cannot spam the requests and immediately kill any player within range)
  • Verify that the player is reasonably in front and facing their target unless the sword uses an AoE (Area of Effect) attack like a spinning attack

In order to verify the distance, you’d simply check the result of (playerPosition - targetPosition).Magnitude is within a reasonable attacking range, with a small threshold to account for a player lagging. To verify the cooldown, you’d simply store somewhere when the last time the player attacked was, and check if os.clock() - lastAttack is greater than or equal to the cooldown period.

To check if the player was reasonably in front of their target, you’d need to use some vector math to figure out the angle between the player and their target, which is excellently explained in this devforum post

An example of this may look like:

if lastAttack - os.clock() >= COOLDOWN then
    if (player.Character.PrimaryPart.Position-target.Character.PrimaryPart.Position).Magnitude <= RANGE then
        local relative =  (targetPosition-playerPosition)
        local forward = player.Character.PrimaryPart.CFrame.LookVector 
        local side = relative.Unit
        local theta = math.deg(math.acos(forward:Dot(side)))
        
        if theta <= MAX_ANGLE then
            print("The player successfully hit their target!")
        end
    end
end

Do note that in this example you may be able to use os.time() instead of os.clock() if you do not require precision that goes under a single second (ex: needing a cooldown of 0.5 or 1.5 would be better to use os.clock() for, otherwise os.time() can work perfectly fine here.

Securing your Remotes

While it isn’t directly associated with anti-exploits, securing your remotes is still an important aspect of stopping exploiters in your games! If you have an insecure remote that an exploiter can abuse to do something they shouldn’t be able to – like give themselves unlimited amounts of cash, then that’s an exploit caused by a flaw in your own code that shouldn’t have happened and should be fixed.

The easiest and most simple way to explain how to secure your remotes is by going over cases where one solution is correct and the other is wrong, and explaining exactly why one of those is more correct than the other one.

In general however, you should be doing server-side checks to validate any request coming from the client via a remote, and making sure that it is all true and that the player should actually be able to make this request instead of simply blindly trusting all the information the server is receiving from the client.

Case 1: Purchase Item Remote

You may have a shop system in your game that allows players to buy items from it using an in-game currency, which is perfectly fine, but needs to be made sure that it is secure so that the player cannot simply get free stuff by using an exploit on it.

Solution 1

buyItem.OnServerEvent:Connect(function(player, item)
    playerData[player].Money -= prices[item]
    table.insert(playerData[player].Inventory, item)
    updatePlayerData(player)
end)

Solution 2

buyItem.OnServerEvent:Connect(function(player, item)
    if playerData[player].Money >= prices[item] then
        playerData[player].Money -= prices[item]
        table.insert(playerData[player].Inventory, item)
        updatePlayerData(player)
    end
end)

In these two solutions, Solution 2 is the correct solution here because it validates that the player actually has enough money to purchase the item they want, and will only go through with the purchase if they pass this check, whereas in the previous one, there is no checks to see if they have enough.

Case 2: Donation Remote

Your game might have a system that allows a player to donate their in-game money to another player which works using a remote to do so. You would need to make sure that the player cannot simply fire it with any value and exploit it to get free money or give free money.

Solution 1

donate.OnServerEvent:Connect(function(player, target, amount)
    if target and playerData[player].Money >= amount and amount > 0 then
        playerData[player].Money -= amount
        playerData[target].Money += amount
        updatePlayerData(player)
        updatePlayerData(target)
    end
end)

Solution 2

donate.OnServerEvent:Connect(function(player, target, amount)
    playerData[player].Money -= amount
    playerData[target].Money += amount
    updatePlayerData(player)
    updatePlayerData(target)
end)

In these solutions, Solution 1 would be the correct solution because it checks to make sure the player has specified a valid target, checks that the player has enough money to give to this player, and checks that they didn’t specify a negative amount.

In this case, checking if the player specified a negative amount is really important, without this check, the player could give themselves free money and, by extension, would also result in the target player losing money, and going into the negatives potentially.

Which means that player would practically be stealing money from another player simply by abusing a fault in your system that was caused by poorly securing your remotes and validating/sanitizing information and data!

The Anti-Exploit’s Interactions with your Game Mechanics

Another vital thing to consider when coding your anti-exploit is how it will interact with your game’s mechanics. One example being, if you have a sprint feature or there’s a way for a player’s walk speed to be changed, you are going to need to design your speed and teleport checks to account for this mechanic.

Now accounting for game mechanics in anti-exploit checks can certainly be tricky, which is why you should determine whether or not a certain check is worth it, and if it is even possible to implement said check given your game mechanics, before you decide to start implementing it.

It is incredibly important that you choose these well and make them play well with your game mechanics, there have been various examples of anti-cheats completely destroying a game’s playerbase due to being poorly implemented or just outright bad.

Conclusion

This has been one big guide on making proper anti-exploits, and I hope that any of you who have fully read through this guide will now make some anti-exploits the right way and correctly implement them into your games with this new knowledge. That’s all for this guide!

115 Likes

This was indeed a poor anti-exploit method on my part years ago that you linked.

Security through obscurity is not security.

A quick note of advice, don’t use os.clock() for finding time deltas. tick() will be a better use case.

3 Likes

idea:
have a script that loops every second and puts a localscript inside of anywhere in the player character.

The localscript instantly checks for any bodymovers and fires a remoteevent to the server.

have like 30 different localscripts each with their own codes and ids and arrangements etc.

A new remoteevent id is created every instant so…
a remoteevent may be called 1239213921 and then a new one is created called 934192382181 and the order of the arguments that need to be fired is also rearranged.
the localscript checks the id.

so in 1 localscript it needs to fire a set of numbers etc.

the exploiter won’t be able to do anything because the localscript deletes instantly.

Now if the localscript doesn’t fire within 1 second then that means it has been deleted. So ban.

also number obfuscation by shifting all numbers by 4 integers to the left

1 Like

I do hope that your entire post is satire and nothing more than a joke

I mentioned os.clock() because I’ve heard that there are plans to deprecate tick() in favour of using it

3 Likes

Interesting, this is the first I’ve heard of this :thinking:

Good to know! However, I mentioned using tick since os.clock is intended for benchmarking.

If tick has plans for deprecation, then replace os.clock with os.time. Clock is used for benchmarking because it’s extremely precise, but with sacrifice to performance. os.time will function quite well for your needs!

1 Like

os.time returns an integer whereas os.clock does not. When precision is needed, what shall I use when tick is deprecated?

out of personal preference os.clock has generally been my go-to since os.time doesn’t have the precision I desired to make cooldown periods like 0.5 but it is good to only use either dependent on the level of precision required.

I also found the post where tick's deprecation was mentioned:

  • tick() sounds perfect - it has a high resolution (usually around 1 microsecond), and a well-defined baseline - it counts since UNIX epoch! Or, well, it actually doesn’t. On Windows, it returns you a variant of the UNIX timestamp in local time zone. In addition, it can be off by 1 second from the actual, real UNIX timestamp, and might have other idiosyncrasies on non-Windows platforms. We’re going to deprecate this in the future.
4 Likes

This is a bad and incorrect advise, os.time should not be used for calculating delta times as it is only accurate up to an integer.

@Reapimus time() should be used in favor of tick(). It is constantly updated per physics simulation frame as it is based on physics simulation. So if physics slow down in your game, time will also update slowly which is ideal for calculating delta times as you’ll be taking in account of low physics frame rate when calculating delta time and thus helping out a bit in preventing false positives.

Here is a chart for better understanding:

10 Likes

in the case of the speed check, yes that would certainly be a better choice, I definitely believe that os.clock is the better choice in the case of a cooldown period for an attack or skill usage of some form because I don’t believe it is slowed down by performance issues.

Also that chart looks useful, where is it from?

1 Like

Your usecases are correct and the chart is from here.

3 Likes

Thanks for this chart. I’ll use this in the future!

Does anti-cheats can be tested in Studio if I change something on client?

1 Like

I made this one and it’s about anti-speed/teleport (and noclip). Though the code is messy, it achieves what it promises to do. An unbypassable, high accuracy detection for games that do not have anything that pushes the player (other than walkspeed).

Emphasis on unbypassable because it’s probably the reason why it’s listed. Though yes, the script uses data given by the client, if you understand how it works, you will say otherwise. Every data from the client is validated on the server at some point, they can’t be modified, which also means, it’s unbypassable. Other than this, I can’t seem to think why it was listed.

1 Like

It has it’s own problems because of the client. If your game starts getting frame drops, I noticed that you had a higher chance of getting kicked, also that when there was network lag it kept spamming “Detected” in the output.

2 Likes

the problem still lies that you are relying purely on the client to provide this data, the client is able to manipulate any and all data it sends, it doesn’t matter if it is being validated on the server, the fact that the server is trusting that the data sent from the client is the issue.

This was a fact I specifically called out in my explanation of what exploits can access. Don’t underestimate the power exploits wield over the data on their clients.

1 Like

In this case, they are not relying/trusting on the client.
Using information≠Trusting information.

Sending data through remotes is no different than having it directly replicate via physics, it doesn’t matter how the data is replicated, it matters how it’s handled.

This \ / ignores basically everything about security, you have to always use some information from the client, if you don’t then its impossible to connect to a server.

Sending the data through a remote is no different than sending it directly through physics, if we use your argument here, then all server sided anti-cheats would also by “bypassable”, because they “rely” on the Roblox physics data which is sent from the client.

If we take this argument to the extreme, then a player wouldn’t be able to even connect to a Roblox server because apparently “it doesn’t matter if it is being validated on the server”.

His anti-cheat is no different than any other “fully serversided” anti cheat, because both use the same physics data, other is replicated directly via physics, other via remotes. Both verify the data. (It doesn’t matter how the the data is replicated, what matters is how its handled.)

2 Likes

While this is a good post and I very much admire it.

This one doesn’t really have that much problems. And it definetly doesn’t have a core issue. Yes it did have some bypasses but those were patched, and it also sometimes false detects.

However I think you are sort of misunderstanding what trusting the client really means (or you didn’t check out his anti cheat thoroughly).

1 Like

I suppose that is so but my point may have been misunderstood just a small bit, the issue I see with his is that the server is trusting the client to send a legitimate character position, and on top of that, trusting that the client’s connection is near flawless and otherwise causes too many false positives if they are lagging as was pointed out by @Judgy_Oreo in his reply

The thing about purely checking from the server-side and relying on the position of the player’s character and information only the server controls is that it leaves no room for the client to lie and no room for the client lagging to cause that many false positives.

As far as I’ve seen, looking through the code for their anti-cheat, is that it never validates the truthfulness of the position the client is providing, meaning that all the exploiter would need to do to effectively bypass it is start making the client-sided ended of it send only the last position it was at, effectively tricking the server into believing they just aren’t moving at all

Would it be a good idea to have certain anti-exploits in important localscripts, so if they were to disable/delete it, it would be game breaking?

I mean if cheaters can access all local scripts code they can just erase anti-cheat part of code.

1 Like