RemoteEvents are one of the most important communication mechanisms in a multiplayer Roblox game. They allow a client to send a request to the server and allow the server to communicate information back to clients. This makes them extremely useful for weapons, shops, inventories, abilities, trading, quests, UI interactions, vehicles, purchases, and many other systems. At the same time, any RemoteEvent that allows a client to trigger server-side behavior creates a potential security boundary that must be protected.
The most important security principle is that the client should never be treated as an authority. A LocalScript can request an action, but the server should decide whether that action is valid. If a client sends a request claiming that the player owns an item, has enough currency, hit another player, completed a quest, or should receive a reward, the server must independently verify the claim before changing the game state.
This distinction becomes particularly important because an exploiter does not have to use your game’s intended interface. Your normal LocalScript might only send a RemoteEvent when a legitimate player clicks a button, but a malicious client can attempt to invoke the same exposed RemoteEvent directly and construct arguments that your normal interface would never produce. Consequently, security cannot depend on the assumption that players will only use the intended UI or LocalScripts.
What Is a Spoofed RemoteEvent Request?
A spoofed request is essentially a client-generated request containing information that the server should not automatically trust.
Imagine a shop with this client code:
BuyItemEvent:FireServer("GoldenSword", 100)
A normal player might use the game’s shop interface to purchase the sword for 100 coins. An exploiter could attempt to send:
BuyItemEvent:FireServer("GoldenSword", 0)
or:
BuyItemEvent:FireServer("GoldenSword", -100000)
or perhaps:
BuyItemEvent:FireServer("AdminSword", 1)
The server should not accept any of these values merely because they arrived through the correct RemoteEvent. The server needs to obtain the authoritative price and item definition itself and determine whether the player can actually perform the purchase.
The same principle applies to combat. A client should not be trusted when it says, “I hit this player for 500 damage.” The server should validate the weapon, ammunition, timing, origin, target, range, and other relevant conditions before applying damage.
The Client Is a Requester, Not the Authority
A secure multiplayer architecture can be visualized as:
PLAYER
↓
CLIENT
↓
RemoteEvent
↓
SERVER VALIDATION
↓
GAME RULES
↓
AUTHORITATIVE STATE
The client is responsible for communicating player intent. The server is responsible for determining whether that intent is legitimate.
For example:
PurchaseEvent:FireServer("HealthPotion")
should mean:
“I would like to purchase a health potion.”
It should not mean:
“Give me a health potion.”
The difference is extremely important.
The server should inspect its own inventory and currency records and decide whether the requested purchase is permitted. RemoteEvents are specifically designed for client-server communication, and server verification is an intended use case for them.
Why RemoteEvents Cannot Be Made Secret
A common misconception is that a RemoteEvent is secure if its name is difficult to guess.
For example:
RemoteEvent
└── X7_GiveReward_918273
Changing the name does not create meaningful authorization.
A security design should assume that a malicious client can discover client-visible RemoteEvents and attempt to invoke them with unexpected arguments. The important protection is therefore not hiding the event name but validating every request on the server.
RemoteEvents normally need to be accessible to both sides of the experience, commonly through a replicated location. That means the event itself should not be considered a secret security mechanism.
The Five Main Layers of Remote Security
A strong RemoteEvent handler generally considers several layers:
- Type validation
- Structure validation
- Value validation
- Context and permission validation
- Rate limiting
For more complicated systems, you can add ownership checks, state validation, distance validation, server-side calculations, replay protection, and transaction safeguards.
The exact checks depend on what the RemoteEvent does. A harmless cosmetic request does not require the same validation as a request that modifies currency, inventory, player health, or progression.
Layer 1: Type Validation
Suppose your event expects a string:
BuyItemEvent.OnServerEvent:Connect(function(player, itemId)
Do not assume itemId is a string.
Validate it:
if typeof(itemId) ~= "string" then
return
end
If the event expects a number:
if typeof(amount) ~= "number" then
return
end
For a Vector3:
if typeof(position) ~= "Vector3" then
return
end
For an Instance:
if typeof(target) ~= "Instance" then
return
end
Type validation is the first line of defense, but it is not enough by itself. A malicious client can send the correct type with an invalid value. Official security guidance specifically recommends validating both types and values.
Layer 2: Structure Validation
Tables require additional care.
Consider:
TradeEvent:FireServer({
ItemId = "Sword",
Quantity = 1
})
You should not assume that the table has the expected structure.
Validate:
if typeof(data) ~= "table" then
return
end
if typeof(data.ItemId) ~= "string" then
return
end
if typeof(data.Quantity) ~= "number" then
return
end
You should also consider whether unexpected fields should be rejected.
For example, an exploiter might attempt:
{
ItemId = "Sword",
Quantity = 1,
GiveAdmin = true
}
If your server processes arbitrary table fields, unexpected data can become dangerous.
The security documentation specifically warns that clients can send technically valid types that are malformed, excessively large, or structured in ways the server did not anticipate.
Never Trust Client-Supplied Prices
One of the most common mistakes in shop systems is:
BuyEvent.OnServerEvent:Connect(function(player, itemName, price)
if player.leaderstats.Coins.Value >= price then
player.leaderstats.Coins.Value -= price
end
end)
The client should not determine the price.
Instead:
local prices = {
Sword = 500,
Shield = 300,
Potion = 100,
}
The server receives:
itemName
and looks up the price itself:
local price = prices[itemName]
if not price then
return
end
Then:
if playerCoins < price then
return
end
This prevents the client from changing the economic rules.
Never Trust Client-Supplied Damage
A vulnerable weapon event might look like:
WeaponEvent.OnServerEvent:Connect(function(
player,
target,
damage
)
target:TakeDamage(damage)
end)
This is fundamentally unsafe because the client controls damage.
A malicious client could request:
damage = 999999999
Instead, the server should know the weapon’s damage:
local weaponDamage = WeaponDefinitions.Rifle.Damage
and calculate the final damage itself.
The client should communicate an attack request, not the final outcome.
Secure Weapon RemoteEvents
A stronger pattern is:
WeaponEvent.OnServerEvent:Connect(function(
player,
origin,
direction
)
-- Validate request
end)
The server can check:
- Is the player alive?
- Does the player actually possess the weapon?
- Is the weapon equipped?
- Is the player currently reloading?
- Has enough time passed since the last shot?
- Is the origin close to the player’s server-side character?
- Is the direction valid?
- Is the target within weapon range?
- Is there an obstruction?
- Is the target a valid enemy?
These are examples of contextual checks recommended for combat systems.
Server-Side Raycasting
For a hitscan weapon, the server can independently perform a raycast.
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {
player.Character
}
local result = workspace:Raycast(
origin,
direction * weapon.Range,
params
)
The important principle is that the server determines the authoritative result rather than accepting a client statement such as:
"I hit Player2."
The official security guidance specifically recommends validating reported shot origins, hit positions, obstructions, firing rate, ammunition, teams, health, and weapon state for combat systems.
Validate the Origin
Suppose the client sends:
origin
The server should compare that position with the player’s actual character.
local root = character:FindFirstChild(
"HumanoidRootPart"
)
if not root then
return
end
if (origin - root.Position).Magnitude > 10 then
return
end
The tolerance should account for latency and animation.
Do not necessarily require mathematical equality.
Instead, establish a reasonable server-side boundary.
Validate the Direction
The server should ensure:
if typeof(direction) ~= "Vector3" then
return
end
if direction.Magnitude < 0.001 then
return
end
direction = direction.Unit
The server can then use the normalized direction for its own raycast.
Validate the Target
If a client sends a target Instance:
if typeof(target) ~= "Instance" then
return
end
that still does not mean the target is valid.
You might check:
local character = target:FindFirstAncestorOfClass("Model")
if not character then
return
end
local humanoid =
character:FindFirstChildOfClass("Humanoid")
if not humanoid then
return
end
You can then perform additional ownership, distance, team, and state checks.
An Instance reference should never be treated as permission to modify arbitrary objects. Security guidance recommends checking not only the type of an Instance supplied by the client, but also its class and expected location or structure in the DataModel.
Do Not Let Clients Modify Arbitrary Instances
A dangerous remote might look like:
EditEvent:FireServer(
workspace.SomePart,
"Transparency",
1
)
If the server blindly executes:
instance[property] = value
the client has effectively gained arbitrary control over server-side objects.
This is a poor design.
Instead, expose specific actions:
DoorEvent:FireServer("Open")
and let the server determine which door the player is allowed to interact with.
The narrower the RemoteEvent’s authority, the easier it is to secure. Official guidance warns against remotes that allow clients to specify arbitrary paths or instances for server modification.
Rate Limiting
Even correctly validated requests can become dangerous when fired thousands of times.
For example:
ClaimRewardEvent:FireServer()
might be legitimate once every five minutes.
A malicious client could attempt to fire it repeatedly.
The server should enforce a cooldown.
local lastClaim = {}
ClaimRewardEvent.OnServerEvent:Connect(function(player)
local now = os.clock()
local previous = lastClaim[player]
if previous and now - previous < 300 then
return
end
lastClaim[player] = now
-- Process reward
end)
Client-side cooldowns can improve responsiveness, but they must never be the only protection. Official security guidance recommends server-side rate limiting for client-triggered operations.
Token Bucket Rate Limiting
For systems that need controlled bursts, a token bucket is more flexible than a simple cooldown.
Conceptually:
Bucket
Capacity: 5
Request → spend token
Request → spend token
Request → spend token
Time passes
Tokens regenerate
This allows a small burst but prevents sustained abuse.
A token bucket implementation is particularly useful for chat, abilities, repeated interactions, or other actions where a player legitimately needs occasional bursts but should not be able to spam indefinitely. The official security documentation provides token-bucket guidance for this purpose.
Validate Numeric Values
Never assume numbers are safe simply because:
typeof(value) == "number"
Numbers can include invalid values such as NaN and infinity.
Use:
if not math.isfinite(value) then
return
end
Then apply meaningful boundaries:
if value < 0 or value > 100 then
return
end
This matters for:
- Currency
- Damage
- Position components
- Quantities
- Prices
- Movement values
- Trade amounts
- Experience
- Timers
Official security guidance specifically recommends handling NaN and infinite values with math.isfinite().
Why NaN Is Dangerous
NaN is unusual because normal comparisons can behave unexpectedly.
For example, calculations involving NaN can cause checks that appear reasonable to fail in unexpected ways.
Therefore, this:
if value < 0 then
return
end
is not sufficient by itself.
Use:
if typeof(value) ~= "number" then
return
end
if not math.isfinite(value) then
return
end
Then validate the permitted range.
Validate Strings
Strings should have maximum lengths.
Instead of accepting:
message
with unlimited content, establish a boundary.
For example:
if typeof(message) ~= "string" then
return
end
if #message > 200 then
return
end
For systems involving persistent storage or user-generated content, additional validation and sanitization may be necessary. Official guidance specifically recommends controlling arbitrary string sizes and validating UTF-8 where persistent data is involved.
Validate Tables Recursively Where Necessary
A table may contain:
{
[1] = "Sword",
[2] = "Shield",
[3] = {
Quantity = 5
}
}
Do not simply check:
typeof(data) == "table"
For sensitive systems, validate:
- Number of entries
- Key types
- Value types
- Maximum nesting
- Maximum string lengths
- Numeric ranges
- Allowed fields
- Duplicate or conflicting information
This is particularly important for trading, inventory, crafting, and DataStore-related operations.
Server-Owned Currency
A secure economy follows:
CLIENT
"I want to buy Sword"
↓
SERVER
"What does Sword cost?"
↓
SERVER
"Does player have enough currency?"
↓
SERVER
"Remove currency"
↓
SERVER
"Give Sword"
It should not follow:
CLIENT
"I have 1,000,000 coins."
↓
SERVER
"Okay."
The server should own the authoritative balance.
Server-Owned Inventory
Likewise, do not let the client define its own inventory.
A dangerous remote:
InventoryEvent:FireServer(
"AddItem",
"RareSword"
)
should not automatically result in:
inventory:Add("RareSword")
The server needs to determine why the item should be awarded.
Possible valid sources include:
- A completed quest
- A successful purchase
- A legitimate reward
- A server-controlled drop
- A validated trade
Trading Systems Need Extra Protection
Trading is especially sensitive because two players’ inventories can change during one operation.
A secure trade should validate:
- Both players exist.
- Both players are still participating.
- Both players own the offered items.
- The offered quantities are valid.
- Neither item has already been consumed.
- Both players have sufficient inventory space if required.
- The trade has not expired.
- The server performs the transfer atomically.
Do not let a client send arbitrary final inventories.
RemoteEvent Relay Vulnerability
Another common mistake occurs when the server simply receives data and broadcasts it.
For example:
EffectEvent.OnServerEvent:Connect(function(
player,
position
)
EffectEvent:FireAllClients(position)
end)
This makes the server a relay rather than a gatekeeper.
A malicious player could spam the event and potentially force all clients to process expensive effects.
The recommended architecture is:
Client request
↓
Server validation
↓
Server rate limit
↓
Server authorization
↓
Broadcast
The server should validate requests before relaying them to other clients. Official guidance specifically highlights this class of vulnerability.
Do Not Trust Client-Supplied Permissions
Never accept:
AdminEvent:FireServer(true)
and then interpret the Boolean as proof of permission.
Instead:
if not player:GetAttribute("IsAdmin") then
return
end
Better yet, maintain authorization on the server through a trusted system.
The client should request an administrative action; it should not declare itself authorized.
Validate Distance
Suppose a player interacts with a chest.
A weak remote:
ChestEvent:FireServer(chest)
The server should determine whether the player is close enough.
local root = character:FindFirstChild(
"HumanoidRootPart"
)
local distance =
(root.Position - chest.Position).Magnitude
if distance > 12 then
return
end
This prevents a client from interacting with objects across the map.
Context and permission validation—including proximity where relevant—is explicitly recommended for server-triggered actions.
But Distance Checks Are Not Always Enough
For unanchored physics objects, network ownership can complicate security.
A client with network ownership of a physics assembly may influence its movement. This means a system should not blindly assume that an object’s client-reported position proves legitimate physical interaction.
Critical interactions should use server-side validation and appropriate network ownership or anchoring strategies.
Validate Player State
A RemoteEvent should consider whether the player is currently allowed to perform the requested action.
For example:
if character:GetAttribute("Stunned") then
return
end
or:
if weapon.IsReloading then
return
end
or:
if humanoid.Health <= 0 then
return
end
Context validation is particularly important for actions affecting progression, shared state, or other players.
Do Not Create Universal RemoteEvents
Avoid one giant event such as:
GameEvent
that accepts:
"Buy"
"Sell"
"Give"
"Teleport"
"Damage"
"Admin"
"Trade"
"Spawn"
This makes the security boundary harder to reason about.
Prefer narrowly scoped remotes:
PurchaseItem
FireWeapon
OpenDoor
SubmitTrade
UseAbility
Each can have a clearly defined contract.
RemoteEvent Contracts
For every remote, document:
Name:
Purpose:
Client arguments:
Expected types:
Allowed values:
Required player state:
Rate limit:
Server-side action:
For example:
FireWeapon
Arguments:
origin: Vector3
direction: Vector3
Validation:
- player alive
- weapon equipped
- origin near character
- direction finite
- fire-rate check
- ammunition check
Server action:
- perform raycast
- determine target
- apply damage
This turns security from an afterthought into part of the API design.
Security Checklist
Before shipping a RemoteEvent, ask:
Input
- Is every argument type checked?
- Are tables validated?
- Are strings length-limited?
- Are numbers finite?
- Are numeric ranges enforced?
Authority
- Does the server own the important state?
- Does the client merely request actions?
- Does the server calculate prices and damage?
Context
- Is the player authorized?
- Is the player alive?
- Is the requested object valid?
- Is the player close enough?
- Is the action currently permitted?
Rate limiting
- Can the event be spammed?
- Is the server enforcing a limit?
- Is per-player tracking cleaned up?
Scope
- Can the client modify arbitrary Instances?
- Can the client select arbitrary objects?
- Can one remote trigger unrelated systems?
Multiplayer
- Can the request affect another player?
- Is the target independently validated?
- Is the server performing the authoritative action?
Frequently Asked Questions
Can exploiters fire RemoteEvents directly?
A secure game should assume that a malicious client can attempt to invoke client-accessible RemoteEvents and manipulate their arguments. The correct defense is server-side validation rather than relying on the intended LocalScript behavior.
Can I hide a RemoteEvent from exploiters?
Do not rely on hiding the RemoteEvent name or location as your primary security mechanism. Security should come from server authorization and validation.
Should I validate every RemoteEvent?
Yes, particularly every RemoteEvent that can change game state, progression, economy, inventory, combat, or other players. The amount of validation should correspond to the potential impact of the action.
Is checking typeof() enough?
No. Type validation is only one layer. You also need value, structure, context, permission, ownership, and rate validation when appropriate.
Can exploiters send NaN?
Systems should assume malicious numeric values may include NaN or infinite values. Use math.isfinite() before performing important calculations.
Should the client send damage?
No for an authoritative combat system. The server should determine damage from its own weapon configuration and validated combat state.
Should the client send the target?
It may send information about what it believes it hit, but the server should independently validate the target and attack context.
Should the server perform raycasts?
For authoritative hitscan combat, server-side raycasting is a strong approach because it allows the server to independently evaluate the attack.
Are client cooldowns useful?
Yes, for responsiveness and reducing unnecessary requests. They should not be the security mechanism. Server-side rate limiting remains necessary.
Does Roblox already rate-limit RemoteEvents?
RemoteEvents have platform-level throttling, including an approximate client-to-server request limit, but that is not a substitute for gameplay-specific server validation and rate limiting.
Can I trust an Instance sent through a RemoteEvent?
No. Validate its type, expected class, ownership/context, and location or ancestry before using it for sensitive operations.
Should one RemoteEvent handle the whole game?
It is usually easier to secure narrowly scoped RemoteEvents with explicit contracts than one universal event accepting arbitrary action names and data.
How do I protect a shop?
The client sends an item identifier. The server looks up the authoritative price, verifies the player has enough currency, validates the player’s state, deducts the correct amount, and grants the item.
How do I protect a reward system?
Never let the client specify the reward amount. The server determines why the reward is being granted and calculates the amount itself.
How do I protect trading?
Keep authoritative inventories on the server, validate ownership and quantities, validate both participants, and perform the exchange as one controlled server-side transaction.
Does RemoteFunction need the same security?
Yes. RemoteFunctions also cross the client-server boundary and require validation. In addition, server-side InvokeClient() has special reliability risks because the client can error, disconnect, or fail to return.
Is client-side anti-cheat enough?
No. A client-controlled anti-cheat cannot be the foundation of server security. The server must validate important actions.
Can a secure RemoteEvent prevent every exploit?
No security design can guarantee that every possible exploit disappears. The goal is to ensure that malicious client input cannot directly become authoritative game state.
Conclusion
Securing RemoteEvents is fundamentally about establishing a strict boundary between what the client requests and what the server allows.
The most important pattern is:
CLIENT
"I want to perform this action."
↓
REMOTE EVENT
↓
SERVER
"Is this request valid?"
↓
TYPE CHECK
STRUCTURE CHECK
VALUE CHECK
PERMISSION CHECK
CONTEXT CHECK
OWNERSHIP CHECK
DISTANCE CHECK
STATE CHECK
RATE LIMIT
↓
SERVER
"Approved."
↓
AUTHORITATIVE GAME STATE
Do not attempt to solve RemoteEvent security primarily by hiding event names, adding client-side cooldowns, or trusting values because they came from your own LocalScript. The official security model emphasizes that client-supplied data must be treated as untrusted and validated according to the action being requested.
A well-designed RemoteEvent is therefore not a command such as:
"Give me 1,000 coins."
It is closer to:
"I am requesting this action."
The server then decides what actually happens.
That principle applies equally to weapons, shops, currencies, inventories, trading, quests, abilities, vehicles, teleports, rewards, purchases, and multiplayer interactions. Once the server becomes the authoritative source of truth and every remote request is validated according to its context, spoofed RemoteEvent data becomes substantially less capable of changing the game in unintended ways.