One of many areas of game development whether online or offline is security and this is one area that I see a lot of developers struggling with in scripting support. Along side security, there are also some cases where a developer doesn’t know where to handle the replication which can lead to a security flaw or performance issue.
• What is Replication ?
the action or process of reproducing or duplicating
– Merriam-Webster Dictionary
or simply copying.
In terms of Roblox, this is the process of making the server copy information to the client or vice versa.
• What is the Replication Boundary ?
Similar to the previously known “filtering enabled”, is the boundary which prevents unauthorized client changes to replicate to the server.
Actions made by the client will no longer freely replicate to the server. Instead, RemoteEvents and RemoteFunctions need to be used by the client to ‘request’ the server preforms certain actions on its behalf.
– Developer Hub
Take note of the word “request”. This is a very important concept I will go into detail later.
• What are Remotes ?
This is the bridge between the client and the server or vice versa. This allows you to form mediums of communication between the boundary and formally transmit data.
There are 2 types of Remotes:
- Remote Event
This is the basic bridge which allows basic transmission of data.
From a client’s perspective, you can just send data to the server and expect no formal response.
From a server’s perspective, you can just send data to one, or all clients and expect no formal response.
- Remote Function
This is the slightly advanced bridge which allows you to return the result of the transmission (did the server run into an error during processing? etc.) If you do not need to know whether the server ran into an error, use Remote Events.
From a client’s perspective, you can just send data to the server and expect a response. (Something went wrong, invalid data etc.)
From a server’s perspective, you can just send data to one, or all clients and expect a response.
A network model/diagram is the flow of your data transmissions. This allows you to track how the data reaches the other side. Creating a visual diagram is optional, but it helps developers new to handling replication.
An example of a basic visual network model/diagram can be seen below:
You can use any photo editing program, notepad, notebook or just a simple piece of paper to make your network model.
As previously stated, if you need the result of the process after transmitting data, use a remote function. Remote functions are good for user experience (UX) since you are letting the player know what’s going on in the background.
“Did something even happen?”
“Why is my request taking so long?”
“Did my data surely save?”
But there are cases where you shouldn’t need a result. Let’s say you have a script that moves the player’s head when that player moves their camera. Unless you can implement an anti error spam or fail-safe, if something goes wrong in this system it would surely be a pain to dismiss endless error prompts.
• Transmission Rates
A common misconception is “If I fire a remote every 0.1 second, will it be bad?”
No. Spamming remotes doesn’t cause lag. In fact firing a remote every frame wouldn’t have a significant impact (remotes have a built in cool down anyway) . What you should be concerned about is what the server does after getting the request. If the process is going to take a lot of processing power, that is bad and you should implement a debounce client and server side.
In the first place, there isn’t any documentation about remote data transmission rates.
• What Data Should be Transmitted
I’ll mention this as early as now. As much as possible, make all your systems server sided. Avoid client intervention.
Of course, sending large amounts of data will impact transmission speed. It’s like downloading a file off the internet.
When transmitting data, do not send data that the client or server won’t use at all. Save resources even though it won’t have a significant impact.
Do note that remotes do not like mixed tables. The remote will omit the dictionary and your data will be incomplete.
local Dictionary = {
["Name"] = "wevetments",
["Age"] = 500
}
local Table = {
[1] = "wevetments",
[2] = 500
}
local MixedTable = {
["Name"] = "wevetments", --this is omitted
[2] = 500
}
• The Request Concept
Request is different from Command
Request considers if that request can be fulfilled, a command is something that must be done at all means.
The client only requests the server to do something. This means the client should never have authority over the server.
What is an example of a command?
|Local Script|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("AddMoney")
RemoteEvent:FireServer(999999999)
|Server Script|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RemoteEvent = ReplicatedStorage:WaitForChild("AddMoney")
RemoteEvent.OnServerEvent:Connect(function(Player, Amount)
local Leaderstats = Player.leaderstats
local MoneyStat = Leaderstats.Money
MoneyStat.Value = MoneyStat.Value + Amount
end
Boom you just crashed the market of your own game. Say goodbye to dev-ex-ing your hard work.
What is an example of a request?
|Local Script|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteFunction = ReplicatedStorage:WaitForChild("PurchaseWeapon")
local Request = RemoteFunction:InvokeServer(5)
if Request then
Prompt("You have purchased this weapon.")
else
Prompt("You do not have enough money.")
end
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local RemoteEvent = ReplicatedStorage:WaitForChild("PurchaseWeapon")
local Weapons = ServerStorage.Weapons
local function ProcessRequest(Player, WeaponId)
local Leaderstats = Player.leaderstats
local MoneyStat = Leaderstats.Money
local Backpack = Player.Backpack
local SelectedWeapon = Weapons[WeaponId]
if MoneyStat.Value >= SelectedWeapon.Price then
WeaponClone = SelectedWeapon:Clone()
WeaponClone.Parent = Backpack
return true
else
return false
end
end
PurchaseWeapon.OnServerInvoke = ProcessRequest
Take note that in the event you do rely on client input/data, that should never have a game breaking impact such as completely pasuing the game if a player doesn’t respond.
• Verifying Transmitted Data
There are some cases where you do have to get client input. This is the most important factor when working with the replication boundary, data verification.
Rule of Thumb - Always assume data transmitted from the client has been tampered with.
All data coming from the client must go through sanity checks. Sanity checks are authentication measures to ensure that the data received can be trusted.
An example of a sanity check:
if PlayerMoney >= ItemPrice
This is a very simple example of a sanity check and I won’t be able to cover all the sanity checks needed for every system.
If you cannot verify every bit of client data transmitted, do not let the client transmit data. You are risking security here if you fail to verify all the data.
A more complex example of a sanity check:
local Objects = ServerStorage.Objects
local function ProcessClientInput(Player, PlacementData)
local Leaderstats = Player.leaderstats
local Money = Leaderstats.Money
local HousePlot = workspace.Plots[Player.Name]
if Money.Value >= Objects[PlacementData.ObjectName].Price and HousePlot then
if PlacementData.X <= HousePlot.MaxXPosition.Value and PlacementData.Z <= HousePlot.MaxZPosition.Value and PlacementData.Y <= HousePlot.MaxYPosition.Value and then
PlaceObject(PlacementData.ObjectName, PlacementData.X, PlacementData.Z, PlacementData.Y, PlacementData.RotX, PlacementData.RotZ, PlacementData.RotY)
return true
else
return "You cannot place outside your plot!"
end
else
return "You do not have enough money or do not own a plot!"
end
end
This is a sanity check for a placement system. Yes, your weapon against exploits are if
statements.
• Where to Handle a Request
An issue I’ve seen lately is, while the logic of security is correct, the execution is wrong and may impact performance to a degree.
The Server Handles
- Computation
- Authentication/Security
- Data Saving
- Data Routing
The Client/s Handles
- Local computation
- User Interfaces (UIs)
- Explosions
- Part Physics
- Tweening
- Colors
- and all the alike
An example of bad request handling:
|Server Script|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(Player)
if IsAdmin(Player) then
local PlayerGui = Player:WaitForChild("PlayerGui")
local AdminUI = ServerStorage.AdminPanel:Clone()
AdminUI.Parent = PlayerGui
end
end)
An example of good request handling is to simply put your admin panel in starter gui and let the server tell the client if they are an admin.
|Server Script|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RemoteEvent = ReplicatedStorage.EnableAdminPanel
Players.PlayerAdded:Connect(function(Player)
if IsAdmin(Player) then
RemoteEvent:FireClient(Player)
end
end)
|Local Script|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RemoteEvent = ReplicatedStorage.EnableAdminPanel
local Player = Players.LocalPlayer
local PlayerGui = Player:WaitForChild("PlayerGui")
local AdminPanel = PlayerGui:WaitForChild("AdminPanel")
RemoteEvent.OnClientEvent:Connect(function()
AdminPanel.Enabled = true
end)
“But wait just there wevetments, can’t exploiters enable the admin panel locally since it’s in their player gui?”
Yes, they can enable it, but who said they can use it? Go back to the Request Concept. Never let the client have authority over the server. All admin features still have to go through sanity checks as requests.
• Security vs User Experience
If there is room to improve the experience your players get, then go ahead and do so, but never ever sacrifice security for UX. If the process is slow, that’s not a reason to remove security checks.
You can trick your players into thinking that their request was quickly processed by simulating an estimated outcome locally.
An estimated outcome is a predicted result of what the server will return after transmission. You can see this mostly in building games such as Welcome to Bloxburg. Did you notice how placing walls and furniture happens so quickly? But in the background, this actually takes about a second or more for you to see that wall you tried to place.
Without Estimated Outcome
With Estimated Outcome
The indicator on the left shows when my mouse button was pressed. You can notice a slight delay after I click without the estimated outcome. I slowed the videos down for a better observation.
• Undocumented Replication Flaws
To see a list of undocumented replication behaviors, check out this post.
As early as now, implement ways to combat these behaviors.
Authentication
- A method of running data through a series of checks to determine if the data can be trusted.
Client
- Refers to a player’s machine/device.
Command
- An action that must be executed at all means regardless of authority.
Computation
- The phase where data is being processed.
Data
- Refers to the information used for processing.
Data Saving
- The process where data is saved either externally via HttpService or with DataStoreService.
Data Routing
- The process of determining which client should get which data.
Debounce
- A system that prevents simultaneous/redundant calls.
Dictionary
- A table that makes use of strings as indexes.
Estimated Outcome
- A prediction of what the server is to return as a result to the client.
Exploit
- An injected script that takes advantage of security flaws in order to gain personal benefit or cause system wide malfunction.
Explosion
- The effect caused by the explosion object.
Filtering Enabled
- A discontinued feature that serves no purpose. Previously allowed developers to enable or disable the replication boundary.
Input
- Refers to the information used for processing, particularly from a client.
Local Script
- A code container that can only execute the code it contains on a client machine.
Mixed Table
- A table that makes use of strings and numbers as indexes.
Network
- The infratructure that allows the transmission of data from client to server or vice versa.
Network Model/Diagram
- A visual representation of the flow of data from one machine to another.
Remote
- An object (either RemoteEvent or RemoteFunction) that allows data to be transmitted through the replication boundary.
Replication
- The act/process of transmitting data.
Replication Boundary
- The digital barrier that prevents unauthorized client changes from replicating to the server.
Request
- A call to the server or client which takes authority into account.
Resources
- The available computational power used to either save or process
data.
Sanity Check
- A method of running data through a series of checks to determine if the data can be trusted.
Server
- Refers to the machine that the game instance is being hosted on.
Server Script
- A code container that can only execute the code it contains on a server machine.
Table
- A collection of data that can be formed with curly brackets {}.
Transmission
- The process of transferring data from one machine to another.
Transmission Rates
- The frequency of data transmitted per remote.
Tweening
- A visual effect handled by TweenService.
User Experience
- A design that enables players to have a meaningful and effortless experience.
Verification
- A method of running data through a series of checks to determine if the data can be trusted.
https://developer.roblox.com/en-us/api-reference/class/RemoteEvent
https://developer.roblox.com/en-us/api-reference/class/RemoteFunction
https://developer.roblox.com/en-us/api-reference/property/Workspace/FilteringEnabled
https://devforum.roblox.com/t/working-with-remotes-and-the-client-server-boundary/453080
I’m planning to create place files with examples completely based from this post with more in-depth explanations, I’ll post them if I can somehow get to finish them all