Roblox security utility
The RemoteEvent security checklist to run before every release
A client request is never proof that a player owns an item, can afford a purchase, is close enough to interact, or waited for a cooldown. Treat every client-to-server remote as an untrusted request.
The rule: the client may describe intent. The server validates the player, types, values, permissions, current state and request rate before changing anything valuable.
Seven checks for every server handler
- Type: use
typeof()and reject unexpected Instances, tables, NaN and infinity. - Shape and size: cap string length, table depth, entry count and numeric ranges before expensive work.
- Permission: derive ownership, role and entitlement from server state—not a boolean or price supplied by the client.
- Context: verify distance, alive state, equipped item, round phase and every other condition the action requires.
- Rate: enforce the limit on the server per player and per action; a client cooldown is only interface feedback.
- Effect: calculate rewards, damage, prices and inventory changes on the server and make repeated requests safe.
- Failure: reject cheaply, avoid leaking sensitive state, and log aggregated abuse signals without storing raw payloads.
A safer purchase boundary
The insecure version accepts the price and item from the buyer. The safer version accepts only an item identifier and derives the authoritative object, price and balance on the server.
local MAX_REQUESTS = 4
local WINDOW_SECONDS = 2
local buckets = {}
local function allowed(player)
local now = os.clock()
local bucket = buckets[player.UserId]
if not bucket or now - bucket.started >= WINDOW_SECONDS then
buckets[player.UserId] = { started = now, count = 1 }
return true
end
if bucket.count >= MAX_REQUESTS then
return false
end
bucket.count += 1
return true
end
BuyItem.OnServerEvent:Connect(function(player, itemId)
if typeof(itemId) ~= "string" or #itemId > 48 then return end
if not allowed(player) then return end
local item = Catalog[itemId]
local profile = Profiles[player]
if not item or not profile then return end
if profile.Coins < item.Price then return end
if profile.Inventory[itemId] then return end
profile.Coins -= item.Price
profile.Inventory[itemId] = true
end)
Exploit tests worth running
- Send the wrong type, missing arguments, huge strings, deep tables, NaN and infinity.
- Fire the request faster than a real player could and from two remotes at once.
- Request an item the player cannot see, own or afford.
- Trigger the action dead, across the map, during the wrong round phase and after leaving the relevant zone.
- Repeat the same request and reconnect during the operation; currency and inventory must remain consistent.
- Watch the server Output and DataStore budget while the invalid traffic runs.
Roblox's own guidance likewise requires server-side validation of client data and server-enforced rate limits. See the official documentation for securing the client-server boundary and remote events and callbacks.
Find the first risky handler locally
Baseplate Verify scans Luau locally, orders the next release blocker, and keeps a static result separate from real Studio evidence. The core scan is free.
Run Baseplate Verify free