Security: write resources without holes · Lesson 2/3 · 9 min
Server-side hardening and anti-spam
Your own resources must be secure too: validate everything on the server and limit event spam.
It's not enough to avoid other people's backdoors; your scripts have to be cheat-proof. Two pillars: ALWAYS validate on the server, and limit how often an event can be triggered.
Validation checklist (reminder)
- The client only ASKS; the server DECIDES and EXECUTES (money, items, permissions).
- Validate the type, range and ownership of every argument that arrives from the client.
- Check distance: is the player near the ATM/shop they claim to use?
- Never send prices, rewards or sensitive IDs from the client.
Event anti-spam
local ultimo = {} -- [src] = timestamp
RegisterNetEvent('tienda:server:comprar')
AddEventHandler('tienda:server:comprar', function(...)
local src = source
local ahora = GetGameTimer()
if ultimo[src] and (ahora - ultimo[src]) < 500 then return end -- max 1 every 0.5s
ultimo[src] = ahora
-- … validated logic …
end)Per-player rate-limit
A cheater can trigger your event 1000 times per second. The per-src rate-limit cuts those abuses (duplicating items, farming money) at the root.
Practice what you learned
0/3En un sistema seguro, ¿quién DECIDE y EJECUTA el movimiento de dinero o items?
Pista
El cliente está en la máquina del jugador; un tramposo lo controla.
Completa el anti-spam por jugador: máximo un disparo por segundo.
local ultimo = {}RegisterNetEvent('mercado:server:vender')AddEventHandler('mercado:server:vender', function(itemId, cantidad) local src = source local ahora = GetGameTimer() if ultimo[src] and (ahora - ultimo[src]) < then return end ultimo[] = ahora -- … lógica validada …end)Pista
Un segundo en milisegundos; y se guarda el tiempo por jugador (source).
El cliente manda el precio (precioCliente) y el servidor se fía. Blíndalo: ignora ese dato, usa Config.Coste y valida la cantidad con tonumber.
Este código tiene un fallo:
RegisterNetEvent('tienda:server:comprar')AddEventHandler('tienda:server:comprar', function(precioCliente, cantidad) local src = source local xPlayer = ESX.GetPlayerFromId(src) local total = precioCliente * cantidad xPlayer.removeMoney(total)end)Reescríbelo corregido:
Pista
total = Config.Coste * cantidad, con cantidad = tonumber(cantidad) y comprobada > 0.
Challenge: code it yourself
Add anti-spam to a 'sell' event so a player can't trigger it more than once per second.
Write it yourself in your editor (VS Code) and test it on your server. You learn here by doing it, not by copying.
See hint
An ultimo[src] table with GetGameTimer(); if the difference < 1000 ms, return.
Escribe aquí tu solución:
