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

lua
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/3
Test

En 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.

Rellena los huecos

Completa el anti-spam por jugador: máximo un disparo por segundo.

1local ultimo = {}
2RegisterNetEvent('mercado:server:vender')
3AddEventHandler('mercado:server:vender', function(itemId, cantidad)
4 local src = source
5 local ahora = GetGameTimer()
6 if ultimo[src] and (ahora - ultimo[src]) < then return end
7 ultimo[] = ahora
8 -- … lógica validada …
9end)
Pista

Un segundo en milisegundos; y se guarda el tiempo por jugador (source).

Corrige el error

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:

1RegisterNetEvent('tienda:server:comprar')
2AddEventHandler('tienda:server:comprar', function(precioCliente, cantidad)
3 local src = source
4 local xPlayer = ESX.GetPlayerFromId(src)
5 local total = precioCliente * cantidad
6 xPlayer.removeMoney(total)
7end)

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:

How was this lesson?