This document shows before/after examples of securing events across the codebase.
-- server/resources/[sandbox]/sandbox-vehicles/server/sync.lua
RegisterServerEvent('VehicleSync:Server:BodyRepair', function(vNet, door, loose, instant)
local veh = NetworkGetEntityFromNetworkId(vNet)
local ent = Entity(veh)
if ent and ent.state then
ent.state.Damage = {
Body = 1000.0,
Engine = 1000.0
}
ent.state.RepairKits = 0
ent.state.BlownUp = false
end
TriggerClientEvent('VehicleSync:Client:BodyRepair', -1, vNet)
end)RISK: Executor can trigger unlimited free repairs on any vehicle!
-- server/resources/[sandbox]/sandbox-vehicles/server/sync.lua
exports['sandbox-base']:RegisterSecuredServerEvent('VehicleSync:Server:BodyRepair', function(vNet, door, loose, instant)
local source = source
-- Validate vehicle exists
local veh = NetworkGetEntityFromNetworkId(vNet)
if not veh or not DoesEntityExist(veh) then
return
end
-- Validate player has permission (owns vehicle or is mechanic)
local char = exports['sandbox-characters']:FetchCharacterSource(source)
if not char then return end
local vehState = Entity(veh).state
if not vehState or not vehState.VIN then
return
end
-- Check if player owns vehicle or has mechanic job
local hasPermission = false
if char:GetData('Job') == 'mechanic' and exports['sandbox-jobs']:JobsDutyGet(source) then
hasPermission = true
else
local vehicle = exports['sandbox-vehicles']:OwnedGetActive(vehState.VIN)
if vehicle and vehicle:GetData('Owner').Id == char:GetData('SID') then
hasPermission = true
end
end
if not hasPermission then
exports['sandbox-base']:LoggerWarn("Security",
string.format("Unauthorized repair attempt by %s on vehicle %s", source, vehState.VIN))
return
end
local ent = Entity(veh)
if ent and ent.state then
ent.state.Damage = {
Body = 1000.0,
Engine = 1000.0
}
ent.state.RepairKits = 0
ent.state.BlownUp = false
end
TriggerClientEvent('VehicleSync:Client:BodyRepair', -1, vNet)
end, {
requireToken = true,
checkRate = true
})CLIENT SIDE UPDATE:
-- client/sync.lua
-- Before
TriggerServerEvent('VehicleSync:Server:BodyRepair', vNet)
-- After
exports['sandbox-base']:TriggerSecuredServerEvent('VehicleSync:Server:BodyRepair', vNet)-- server/resources/[sandbox]/sandbox-inventory/server/itemuse.lua
RegisterServerEvent('Inventory:Server:GiveItem', function(targetId, item, count)
local source = source
exports.ox_inventory:AddItem(targetId, item, count)
end)RISK: Executor can spawn unlimited items to anyone!
exports['sandbox-base']:RegisterSecuredServerEvent('Inventory:Server:GiveItem', function(targetId, item, count)
local source = source
-- Validate inputs
if type(targetId) ~= "number" or type(item) ~= "string" or type(count) ~= "number" then
return
end
if count < 1 or count > 100 then
return
end
-- Validate source has the item
local sourceItem = exports.ox_inventory:GetItem(source, item, nil, true)
if not sourceItem or sourceItem < count then
exports['sandbox-base']:LoggerWarn("Security",
string.format("Player %s tried to give item they don't have: %s x%d", source, item, count))
return
end
-- Validate target exists and is nearby
local sourceCoords = GetEntityCoords(GetPlayerPed(source))
local targetCoords = GetEntityCoords(GetPlayerPed(targetId))
if #(sourceCoords - targetCoords) > 5.0 then
return
end
-- Remove from source, add to target
if exports.ox_inventory:RemoveItem(source, item, count) then
exports.ox_inventory:AddItem(targetId, item, count)
end
end, {
requireToken = true,
checkRate = true
})RegisterServerEvent('Admin:BanPlayer', function(targetId, reason, duration)
-- Anyone with executor can ban players!
BanPlayer(targetId, reason, duration)
end)RISK: Executor users can ban everyone on the server!
exports['sandbox-base']:RegisterSecuredServerEvent('Admin:BanPlayer', function(targetId, reason, duration)
local source = source
-- Validate admin permission
local player = exports['sandbox-base']:FetchSource(source)
if not player then return end
local isAdmin = false
for k, v in ipairs(player:GetData("Groups")) do
if v == "admin" or v == "owner" then
isAdmin = true
break
end
end
if not isAdmin then
exports['sandbox-base']:LoggerError("Security",
string.format("Non-admin %s attempted to ban player %s", source, targetId))
exports['sandbox-base']:SecurityBanPlayer(source, "Attempted unauthorized admin action")
return
end
-- Validate inputs
if type(targetId) ~= "number" or type(reason) ~= "string" then
return
end
-- Execute ban
BanPlayer(targetId, reason, duration)
exports['sandbox-base']:LoggerInfo("Admin",
string.format("Admin %s banned player %s: %s",
player:GetData("Name"), targetId, reason))
end, {
requireToken = true,
checkRate = true
})RegisterServerEvent('Economy:DepositMoney', function(amount)
-- No validation!
GivePlayerMoney(source, amount)
end)RISK: Executor can spawn billions of dollars!
exports['sandbox-base']:RegisterSecuredServerEvent('Economy:DepositMoney', function(amount)
local source = source
-- Validate amount
if type(amount) ~= "number" or amount < 1 or amount > 1000000 then
exports['sandbox-base']:LoggerWarn("Security",
string.format("Invalid deposit amount from %s: %s", source, tostring(amount)))
return
end
-- Validate player has cash
local char = exports['sandbox-characters']:FetchCharacterSource(source)
if not char then return end
local cash = char:GetData("Cash")
if cash < amount then
exports['sandbox-base']:LoggerWarn("Security",
string.format("Player %s tried to deposit more than they have: %d (has %d)",
source, amount, cash))
return
end
-- Process deposit (remove cash, add to bank)
char:SetData("Cash", cash - amount)
local account = exports['sandbox-finance']:AccountsGet(char:GetData("BankAccount"))
if account then
exports['sandbox-finance']:BalanceDeposit(account.Account, amount, false, true)
end
end, {
requireToken = true,
checkRate = true
})RegisterServerEvent('Properties:PurchaseProperty', function(propertyId)
GivePlayerProperty(source, propertyId)
end)RISK: Free properties for everyone with executor!
exports['sandbox-base']:RegisterSecuredServerEvent('Properties:PurchaseProperty', function(propertyId)
local source = source
-- Validate input
if type(propertyId) ~= "string" or propertyId == "" then
return
end
local char = exports['sandbox-characters']:FetchCharacterSource(source)
if not char then return end
-- Get property data
local property = exports['sandbox-properties']:FetchProperty(propertyId)
if not property or property.Owned then
return
end
-- Check player location (must be at property)
local playerCoords = GetEntityCoords(GetPlayerPed(source))
local propertyCoords = property.Location
if #(playerCoords - propertyCoords) > 10.0 then
exports['sandbox-base']:LoggerWarn("Security",
string.format("Player %s tried to buy property %s from too far away", source, propertyId))
return
end
-- Check funds
local price = property.Price
local account = exports['sandbox-finance']:AccountsGet(char:GetData("BankAccount"))
if not account or exports['sandbox-finance']:BalanceGet(account.Account) < price then
return
end
-- Process purchase
if exports['sandbox-finance']:BalanceWithdraw(account.Account, price, false, true) then
GivePlayerProperty(source, propertyId)
exports['sandbox-base']:LoggerInfo("Economy",
string.format("Player %s purchased property %s for $%d",
char:GetData("Name"), propertyId, price))
end
end, {
requireToken = true,
checkRate = true
})RegisterServerEvent('Police:ArrestPlayer', function(targetId, time)
ArrestPlayer(targetId, time)
end)RISK: Executor can arrest anyone!
exports['sandbox-base']:RegisterSecuredServerEvent('Police:ArrestPlayer', function(targetId, time)
local source = source
-- Validate inputs
if type(targetId) ~= "number" or type(time) ~= "number" then
return
end
if time < 1 or time > 999 then
return
end
-- Validate police officer
local char = exports['sandbox-characters']:FetchCharacterSource(source)
if not char then return end
if char:GetData('Job') ~= 'police' or not exports['sandbox-jobs']:JobsDutyGet(source) then
exports['sandbox-base']:LoggerWarn("Security",
string.format("Non-police %s tried to arrest player %s", source, targetId))
return
end
-- Validate target nearby
local sourceCoords = GetEntityCoords(GetPlayerPed(source))
local targetCoords = GetEntityCoords(GetPlayerPed(targetId))
if #(sourceCoords - targetCoords) > 5.0 then
return
end
-- Execute arrest
ArrestPlayer(targetId, time)
exports['sandbox-base']:LoggerInfo("Police",
string.format("Officer %s arrested player %s for %d months",
char:GetData("Name"), targetId, time))
end, {
requireToken = true,
checkRate = true
})Use this script to help identify all events that need securing:
-- tools/security_audit.lua
local fs = require('fs')
local path = require('path')
local function findAllEvents(directory)
local events = {}
local files = fs.readdirSync(directory)
for _, file in ipairs(files) do
local fullPath = path.join(directory, file)
local stat = fs.statSync(fullPath)
if stat.isDirectory() then
local subEvents = findAllEvents(fullPath)
for k, v in pairs(subEvents) do
events[k] = v
end
elseif file:match('%.lua$') then
local content = fs.readFileSync(fullPath, 'utf8')
-- Find RegisterServerEvent
for eventName in content:gmatch('RegisterServerEvent%(%s*["\'](.-)["\']]%s*,') do
events[eventName] = events[eventName] or {}
table.insert(events[eventName], fullPath)
end
-- Find RegisterNetEvent
for eventName in content:gmatch('RegisterNetEvent%(%s*["\'](.-)["\']]%s*,') do
events[eventName] = events[eventName] or {}
table.insert(events[eventName], fullPath)
end
end
end
return events
end
print("Scanning for unsecured events...")
local events = findAllEvents('server/resources/[sandbox]')
print(string.format("\nFound %d unique events:", #events))
for eventName, locations in pairs(events) do
print(string.format(" - %s", eventName))
for _, location in ipairs(locations) do
print(string.format(" %s", location))
end
end
print("\n⚠️ SECURITY AUDIT COMPLETE")
print("Review each event and secure with exports['sandbox-base']:RegisterSecuredServerEvent")- All economy/money events
- All item give/remove events
- All admin commands
- All ban/kick events
- All property/vehicle ownership events
- Vehicle repair/modify events
- Job action events (arrest, heal, etc.)
- Weapon/combat events
- Teleport/noclip events
- UI interaction events
- Notification events
- Status update events
- Animation events
- Cosmetic change events
- Read-only query events
- Chat message events
After securing events:
- ✅ Test normal gameplay works
- ✅ Test rate limiting (rapid fire events)
- ✅ Test invalid token (should block)
- ✅ Test executor detection (should auto-ban)
- ✅ Check logs for security events
- ✅ Verify no false positives
Remember:
- Secure ALL events that modify server state
- Always validate inputs on server
- Never trust client data
- Log suspicious activity
- Test thoroughly before production
The goal: Make it impossible for executors to affect your server!