Any kind of check (sanity, security, âŚ) for a remote should be added write below the event listener. Use conditional statements; if a condition doesnât pass, then donât do anything (effectively saying that the server rejects the clientâs request and will not budge).
Remote.OnServerEvent:Connect(function (player, ...)
if conditionHere or conditionThere and conditionPossiblyHere then
whatToDoWhenOkay()
end
end)
In your case, you could add a debounce variable to your remote function, and reject any request that comes too quickly after the one before.
Using colbert2677âs implementation:
local debounce = -math.huge
Remote.OnServerEvent:Connect(function(player,...)
local now = os.clock()
if now - debounce <= 5 then return end
debounce = now
--do stuff
end)
The problem with that code is that it doesnât account for other players.
It would require a lot of code I believe to make a system where it accounts for other players and has a way to determine if a remote event is actually be spammed.
For example, I might have a remote event that sends my mouse position every .5 seconds to the server and I might have a remote event I send every 10 seconds. Youâll need a way to set a definition for what counts as spamming for each remote event.
You could use a table of debounces for each player using the remote event/function. Of course, you would have to keep up with players joining and removing with this table, and then it would get messy, but it can be done:
local playerDebounces = {}
Players.PlayerAdded:Connect(function(player)
playerDebounces[player] = -math.huge
end)
Players.PlayerRemoving:Connect(function(player)
playerDebounces[player] = nil
end)
Remote.OnServerEvent:Connect(function(player, ...)
local now = os.clock()
if now - playerDebounces[player] <= 5 then return end
playerDebounces[player] = now
--do stuff
end)
You could also organize player-specific data in a folder instance in serverstorage, or things of that nature.
Imagine you have an FPS game in which you can fire a gun at only a set rate per second. Naturally, to make the guns FE compatible, you need to fire a remote event for when the user fires the gun. But, you have limited the fire rate on the client so you can give feedback.
However, an exploiter could spam the event over the amount and have an unfair advantage over other players.
So, if you should always double check that checks done on the client weâre done and done correctly, especially on the other end of a remote event.
Exploiters can do whatever a localscript can, so that includes firing remote events.
Itâs good to assume your game is under attack, rather than leave it alone. Sanity checks should be pretty much everywhere.
I already do Sanity Checks on all my Remotes, op wants a Sanity check to figure out if his remotes are being spammed extremely fast, thereâs a difference.
You can always use a variable and set it to true and have a check to see if itâs true set the variable to false wait until the remote function or event is done and once the remote event or function is done set it back to true
I was merely clarifying why that type of check is needed, as the user above asked why this kind of check would be needed.
I would disagree. I would consider this a kind of sanity check, as it checks the reliability of client behaviour / information on the server. This seems to me to be (pretty much) the definition of a sanity check.
However, if you want a solution, you could try having a dictionary where each key is a playerâs userID or name (it doesnât matter which). Then, store the denounce in there. To check for the denounce, simply use the player argument provided in a remote event handler and access the name.
EDIT: Sorry, I just realised that this is suggested already above:
An easy way is to use a Debounce.
It may be simple, but it helps to wait again to run the remote.
local Debounce = false
Remote.OnServerEvent:Connect(function(Player)
if not Debounce then
Debounce = true
--~ Function
Debounce = false
end
end)
But in cases of remote protection, you can check if the argument is wrong. If you have a wrong argument you can do a :Kick(), also because it is on the Server, it would be impossible for the exploit to bypass an argument error. But in some cases it may not be a good idea.
local Debounce = false
Remote.OnServerEvent:Connect(function(Player, Argument)
if not Debounce then
Debounce = true
if Argument == 'Reload' then
--~ Function
else
Player:Kick()
end
Debounce = false
end
end)
In almost all cases, kicking someone for failing a sanity check is a terrible choice. False positives exist and players should not be kicked for the faults of developers. Simply rejecting the request is sufficient enough (having the server not do anything if the client fails to pass the checks).
In his case kicking the player has no consequences, since the remote has only one function, and he wrote the game code to ONLY send the 1 correct argument. Under these circumstances, there can be no false positives and any incorrect arguments can be assumed malicious.
But yes you are correct in saying that kicking usually isnât the right choice
local CanShoot = true
local Cooldown = 0.3
ShootRemote.OnServerEvent(connect(function(arguments)
if CanShoot == true then -- if client tries to fire during the cooldown period (while the CanShoot value is false), the request is ignored
CanShoot = false -- disable ability to shoot
-- fire your gun
wait(Cooldown)
CanShoot = true
end
end)
This is an effective way to implement Cooldown, however it can suffer from server lag. One way to fix this is to implement the cooldown on both the client and server, the client having a slightly longer cooldown to account for server lag.
Kicking the player always has consequences. The code provided was merely an example, production variants of remotes can get larger. Thereâs absolutely no reason why the player should be kicked for failing a sanity check. Like I said, it can produce false positives or a developer fault can kick innocent players. Even then, thereâs no reason to kick the player. Just reject the request.
This code wouldnât work, as it assumes there is only one player. In this code, everybody will get the effect of the denounce one player has initiated, because there is only one debounce variable. This is why suggestions such as this were suggested above:
@ndrwcswll
You seem to be suggesting the same debounce mechanism which wouldnât work.
Also, if there is only one value a remote event should hold, what is the point of having the argument? It just over complicates things. An argument is for use when you need to provide information. In this case, you arenât doing this at all.
As @colbert2677 said previously, band-aid fixes are bad and will nearly never work. In this case, all the exploiter needs to do is find a single value and they can bypass this. Also, for this to work, the value has to appear somewhere in a local-script, which an exploiter can easily view using a Lua decompiler. This would make this long, fruitless effort go to waste, because they can then share the value. In short, this solution isnât very good and can be bypassed very easily.
Finally, Iâm not really sure why you need this at all. The debounce checks are done on the server, so why do you need to check if an exploiter is firing the remote event, or a local-script? They would both, essentially, have the same behaviour.
The code you provided would not work. It would debounce all players at the same time. Iâm not sure what you mean by âsome remote that are only run in certain casesâ, as a remote event will be being used by all players at the same time. There will be, pretty much, no situation in which a remote is only being used by a single client.
However, the code you provided as an alternative would work, but with certain drawbacks. Take a look at @goldenstein64âs solution above.
One thing to keep in mind with this debounce system is that if the function ever errors, your debounce will be stuck there forever.
Youâre probably going to want to separate all your events so they each have their own debounce that expires after a certain amount of time(or perhaps it can handle back-to-back fires until it starts blocking you)
Building on @goldenstein64 answer, have you considered using a count when implementing sanity checks?
This enables a remove event/function to be called multiple times in a short period of time (e.g. if firing bullets from an automatic weapon) without triggering as many false positives or a lengthy cooldown.
For example:
--Variables
local requestsLimit = 20
local sanityChecks = {}
--Refresh Sanity Checks
coroutine.wrap(function()
while wait(5) do
sanityChecks = {}
end
end)()
--Main
Remote.OnServerEvent:Connect(function(player,...)
--Sanity Checks
local remoteName = Remote.Name
if not sanityChecks[player] then
sanityChecks[player] = {}
end
local requests = sanityChecks[player][remoteName]
if not requests then
requests = 1
sanityChecks[player][remoteName] = requests
else
sanityChecks[player][remoteName] = requests + 1
end
if requests > requestsLimit then
--Warn user is making too many requests
wait(1)
end
--Do your stuff
end)
This code is very well written and seems to get the job done really well. I like the idea of having a coroutine to handle refreshing of the debounces.
However, I would question if itâs a good idea to store the player object themself in the sanity array. Would it not be better to store player.UserId instead?
The only caveat with not using the playerâs UserId is that if the sanity check table doesnât refresh, then a reference to the player will be kept which will prevent their object from being garbage collected should they disconnect from the game. Otherwise, thereâs no real difference between using it or not.