FiveM Server-Side Validation: Why You Should Never Trust the Client (and How to Enforce It)

If there is one rule that separates a stable, exploit-resistant FiveM server from one that gets drained by the first script kiddie who joins, it is this: fivem server-side validation is mandatory for every action that changes game state. The client is not your friend, it is not under your control, and it is not trustworthy. Anything that touches money, inventory, jobs, vehicles, or persistent data has to be decided by the server, because the player’s machine can lie about all of it.

Why the Client Is Hostile by Default

When a player connects, your client-side resources run on their hardware. That means a determined player can dump and decompile resource files, read your Lua, and watch every event your client fires. Tools to inject Lua into a running game session are common, and the network layer can be replayed and forged.

Concretely, an attacker can:

  • Decompile resource .lua (and bundled fxmanifest.lua) to learn your event names and payload shapes.
  • Call TriggerServerEvent directly from an injected script with whatever arguments they want.
  • Spoof entity state, teleport, and report fake coordinates back to the server.

The takeaway: never treat data arriving from the client as a fact. Treat it as a request that your server must independently verify.

The Golden Rule: Validate Every Consequential Action

Any event handler registered with RegisterNetEvent can be triggered by any connected player at any time, with any arguments. So the question for every handler is simple: “If a player called this with hostile input, what is the worst that happens?” If the answer involves free money, duplicated items, or someone else’s property, the logic belongs entirely on the server.

The server already knows the caller’s identity through the source value inside a net event. Use it. Look up that player’s framework object (for example QBCore.Functions.GetPlayer(src) or ESX.GetPlayerFromId(src)) and derive everything from server-held state rather than from arguments the client sent.

The Classic Exploitable Pattern

Here is the mistake that shows up in countless leaked scripts: the client tells the server how much money to add. The wrong way looks like this.

-- client: RegisterNetEvent + TriggerServerEvent('shop:buy', itemName, price, amount)

-- server: Player.Functions.AddMoney('cash', price * amount)

The server trusted price and amount from the client, so a player just sends a negative price or an absurd amount and prints money. The server-authoritative fix computes everything itself:

  • Ignore any client-supplied price. Look the item up in a server-side table and read the real cost.
  • Sanitize amount: confirm it is a positive integer within a sane maximum before any math.
  • Check the player can actually afford it with GetMoney before calling RemoveMoney, then grant the item.
  • Verify ownership and proximity: does this player own the vehicle or property they are acting on, and are they near the shop blip server-side?

In other words, the client may say “I want to buy item X.” The server decides whether that is possible, what it costs, and whether it happens at all.

NUI Callbacks Are Client Input Too

It is easy to forget that a slick HTML/JS interface is still running on the player’s machine. A RegisterNUICallback handler hands data straight from the browser context to your Lua, and the player can open dev tools or post to that callback directly. NUI data deserves the same suspicion as any net event.

Never let a NUI callback be the final authority on a purchase, a transfer, or an admin action. Have the callback fire a server event, and let the server re-validate identity, permissions, and values before committing anything.

Rate-Limiting, Distance, and Ownership Checks

Even correct logic can be abused through sheer volume. If a handler is cheap to call, an attacker will spam it thousands of times a second to lag your server or race a duplication bug. Add server-side debouncing.

  • Track a per-player timestamp with GetGameTimer and reject calls that arrive faster than a sensible cooldown.
  • For distance, read the real position with GetEntityCoords(GetPlayerPed(src)) on the server and compare it to the target, rather than trusting coordinates the client passed in.
  • For ownership, confirm the entity or vehicle plate belongs to that player in your database before acting, and validate networked entities with NetworkGetEntityFromNetworkId plus DoesEntityExist.

Log Suspicious Calls for Review

When validation fails, do not silently return. A negative amount, an unaffordable purchase, or a player acting from 800 metres away is a signal. Log the source, the identifiers, the event name, and the offending payload. Patterns in those logs tell you which resource is leaking and who is probing it, and they give you evidence before you ban. Cheap defensive logging at every rejection point is one of the highest-value habits you can build.

Where to Go From Here

Server-side validation is the foundation, but it pairs well with a dedicated detection layer like the one at https://febex.io to catch injection and tampering that good coding discipline alone cannot see. If you build on QBCore and want scripts that already follow this server-authoritative pattern, browse https://qb-tebex.io, and for a broader set of vetted, review-checked FiveM resources take a look at https://marketplace-tebex.io before you drop unknown code into production.

None of this requires a paid anticheat to get right. It requires a mindset: the client asks, the server decides. Audit your RegisterNetEvent handlers one by one, assume each will be called with hostile input, and move every consequential decision to the server. Do that consistently and the most common FiveM economy exploits simply stop working.