Event Guard

Version 1.0.0. The listing, or pick it up in the shop and run it.

Event Guard

Event Guard is a firewall for server events. It checks each call against rate limits and argument rules before your script sees it, scores the players who break them, and kicks or bans once the score gets high enough. For context, mod menus work by firing your server events with fake arguments or hundreds of times a second, for example a payout with a made-up amount, a shop purchase of an item that doesn't exist, or a job event spammed until the money adds up.

It's standalone. No framework needed, and it works the same on ESX, Qbox, QBCore or anything else.

What you get

  • A protection library you add to any Lua resource with one line in its fxmanifest.lua. That resource's code doesn't change.
  • Rate limits per event and player, by exact name or by pattern (qb-shops:server:*).
  • Argument rules: types, number ranges, whole numbers, string lengths and formats, allowed values, table fields, sizes and depth. NaN and infinite numbers are always refused.
  • Built-in size limits on every protected event, so giant strings, 10 000 entry tables and tables nested a thousand deep never reach your code.
  • Flood guard: total calls per player per second, inside each resource and across all of them.
  • Honeypots: fake event names that menus fire. Anyone who fires one is flagged (banned by default).
  • Escalation: a score per player that fades over time. Steps kick or ban, and repeated kicks turn into a ban.
  • Offenders view (/eventguard): live top offenders, recent violations with a safe preview of the arguments, rule hit counts, protected resources and bans.
  • Learning mode: records what real players send for a few hours and writes a rule for each event that you can paste into the config.
  • Violations are saved to your database and deleted after 14 days. There's also an optional Discord webhook for kicks and bans.

What it can and can't block

Read this before you buy, so you know exactly what you're getting.

FiveM gives every resource its own private list of event handlers. One resource can't reach into another one's list, and CancelEvent doesn't stop other resources' handlers from running (Cfx docs, "Canceling Events"). So a separate anticheat resource can't stand in front of another script's events. Event Guard is built around that:

Blocked before the handler runs Seen and punished afterwards
Events of resources that include the library Yes Yes
Events listed in Config.Watch (for scripts you can't edit) No Yes: logged, then kick or ban by score
Honeypot event names Nothing to block, no real script uses them Yes

What Event Guard does not see:

  • Events registered by C# or JavaScript resources. The library is Lua only.
  • Anything that isn't a server event: spawning cars or props, god mode, teleporting, weapon spawns, NUI callbacks, client to client events. Those need a client anticheat and server checks such as entity lockdown.
  • Calls from your own server scripts (TriggerEvent on the server). These are trusted and never limited.
  • A good rule can only refuse what it describes. A payout event that takes no arguments can still be fired at the allowed rate. Rate limits make that slow, and your script still has to check that the player did the job.

Event Guard is the outer layer, and your scripts still need their own checks.

Requirements

  • oxmysql
  • FiveM server with Lua 5.4 resources (lua54 'yes')

Install

  1. Unzip so that resources/event_guard/fxmanifest.lua exists (not resources/event_guard-v1.0.0/event_guard/..., which Windows "Extract All" creates). Keep the folder name event_guard.
  2. Add ensure event_guard to server.cfg, after ensure oxmysql and before the resources you want to protect.
  3. Add the ACE lines below.
  4. Protect your resources (next section).
  5. Start the server. The tables create themselves.

Protecting a resource

Open the resource's fxmanifest.lua and add these two lines. The server_script line has to come before the resource's own server scripts:

dependency 'event_guard'
server_script '@event_guard/lib.lua'

server_scripts {
    -- the resource's own files, unchanged
}

shared_script '@event_guard/lib.lua' works too. On clients the file does nothing.

Restart the resource. The console doesn't print anything when it works. Open /eventguard after a few seconds of play and the resource shows up under Protected resources.

Every server event the resource registers is now checked against your rules. Events with no rule of their own get Config.DefaultRule (100 calls per 5 seconds per player, plus the size limits), so nothing breaks while you write rules.

For a resource you bought with escrow, the fxmanifest.lua is normally still a plain file you can edit. If it won't start after you add the line, take the line out again and list its events in Config.Watch instead.

ACE permissions

# Who can open /eventguard, start learning mode and lift bans
add_ace group.admin eventguard.admin allow

# Optional: never limited, kicked or banned by Event Guard. Keep this group small,
# a stolen staff account with bypass isn't limited either.
add_ace group.admin eventguard.bypass allow

# Your staff, by Rockstar license
add_principal identifier.license:1a2b3c4d5e6f... group.admin

The server checks the ACE on every action, so removing it takes effect straight away.

Writing rules

Rules live in Config.Rules in config.lua, one per event name or pattern:

Config.Rules = {
    ['myshop:server:buy'] = {
        rate = { calls = 6, seconds = 10 },
        args = {
            { type = 'string', maxLen = 32, pattern = '^[%w_]+$' },       -- item name
            { type = 'number', min = 1, max = 50, integer = true },      -- amount
        },
        action = 'drop', points = 3,
    },
    ['myjob:server:payout'] = {
        rate = { calls = 2, seconds = 30 },
        args = { { type = 'number', min = 1, max = 5000, integer = true } },
        action = 'kick', points = 10,
    },
    ['qb-*:server:*'] = { rate = { calls = 20, seconds = 5 }, action = 'drop' },
}
  • An exact name wins over a pattern, and the pattern with the most fixed characters wins over a looser one.
  • action: log lets the call through and records it, drop blocks it, kick and ban block it and remove the player.
  • points go on the player's score (see Escalation below).
  • The comments in config.lua list every argument option, including table fields, lists, either-or types and vector3 coords.

If you don't know what an event takes, use learning mode.

Learning mode

Open /eventguard, go to Learning and press Start learning. For the next 3 hours (Config.Learning.hours), protected resources record the arguments real players send. Learning mode doesn't block anything. Once it's done, the view shows a ready rule for each event: types, number ranges with headroom, string lengths, table fields, and a rate of about twice what the busiest player did. Press Copy rule, paste it into Config.Rules, check that it reads right, and restart Event Guard.

Run it at a busy time so it sees normal play. Anything a player didn't do while it ran (a rare item, a long name) won't be in the rule, so look over the numbers before you put kick or ban on them.

Honeypots

Config.Honeypots.names lists event names that menus fire but no real resource on your server uses (old job scripts, fake admin events). A player who fires one is banned by default (action = 'ban', banDays = 0 for permanent).

A name whose resource is installed is skipped and logged at start, so a real script is never treated as bait. For example esx_society:withdrawMoney is skipped on a server that runs esx_society. Add the names of old scripts you removed, they make good bait. The list lives in config.lua, which only loads on the server, so clients can't read it.

Flood guard

  • Inside each protected resource: Config.Flood.perResource (60 calls per second per player by default). Calls past it are dropped on the spot.
  • Across all protected resources: Config.Flood.total calls per second per player (150). Going over it kicks.

Escalation

Every violation adds the rule's points to the player's score. The score halves every 10 minutes, so a one off mistake fades and a script that keeps firing climbs. With the defaults:

  • 25 points: kicked.
  • 100 points: banned for 7 days.
  • Kicked 3 times inside 24 hours: banned for 3 days.

When several bans apply, the longest one wins. A rule with action = 'ban' and no banDays bans permanently.

Bans

Event Guard keeps its own ban list and checks it when a player connects. Staff lift bans from the Bans tab, or from the server console:

eventguard_unban 12                    # by ban number
eventguard_unban license:1a2b3c...     # every Event Guard ban on that license

If you'd rather send bans to your existing ban script, set Config.Bans.export to a resource and export name. Event Guard calls it as exports[resource][name](src, license, reason, days) and expects true back. If the export errors or returns anything else, Event Guard bans the player itself, so nobody slips through. With Staff Desk 1.0.1 or newer, use { resource = 'staff_desk', name = 'AddBan' } and Event Guard bans land in the Staff Desk ban list.

Commands

Command Who What
/eventguard eventguard.admin Opens the offenders view
eventguard server console One line status: protected resources, violations, kicks, bans
eventguard_unban <id or license> server console Lifts bans

Discord webhook

set event_guard_webhook "https://discord.com/api/webhooks/..."

Use set, not setr (setr sends the value to every client). Kicks, bans and lifted bans get posted.

Exports

-- Report something your own script noticed. Scored like any other violation.
-- action is 'log', 'drop', 'kick' or 'ban'.
exports.event_guard:Flag(src, 'teleported 2 km in one second', 10, 'log')

exports.event_guard:GetScore(src)        -- the player's current score
exports.event_guard:IsBanned(license)    -- true while an Event Guard ban is active

Script authors can also ship a default rule inside a protected resource. A rule with the same name in Event Guard's config.lua replaces it:

EventGuardLib.Rule('myres:server:claim', { rate = { calls = 1, seconds = 60 }, args = { 'number' } })

Performance

The library adds about 1 microsecond per protected call (0.6 to 1.6 µs measured over 200 000 calls in Lua 5.4, for a rate limit plus a two argument rule, and for a table rule with three fields). Violations are collected in each resource and handed to Event Guard once a second, and the database is written in batches every 10 seconds, at most 200 rows per batch.

Escrow

config.lua, server/bridge.lua, sql/install.sql, lib.lua and shared/core.lua stay readable. The last two have to, since other resources load them.

Troubleshooting

  • Couldn't find resource event_guard: the folder is nested. Open resources/ and check that event_guard/fxmanifest.lua sits directly inside it. Windows "Extract All" adds an extra folder named after the zip.
  • could not read event_guard/shared/core.lua in a protected resource's console: the folder isn't named event_guard, or it isn't installed. Rename it, or change the @event_guard/lib.lua line to the folder name.
  • event_guard is not started yet: the protected resource started first. Add dependency 'event_guard' to it and put ensure event_guard above it in server.cfg. Until Event Guard starts, the flood limit still applies.
  • A resource doesn't show under Protected resources: the @event_guard/lib.lua line is missing or sits below the resource's own scripts, or nobody has used its events yet.
  • Real players get dropped: open Violations and look at the rule and the arguments. Loosen that rule, or set its action to log while you find the right numbers. Learning mode helps here.
  • "You don't have access to Event Guard." The player has no eventguard.admin ACE. Run test_ace identifier.license:... eventguard.admin in the server console to see what the server thinks.
  • Database setup failed in the console: oxmysql isn't connected, or your database user can't create tables. Checks and kicks still work, though nothing is saved and bans only last until a restart. Fix the connection or run sql/install.sql by hand.
  • config: rule ... lines in the console: a rule in config.lua has a mistake, and the line says which. The view shows the same list at the top.
  • The webhook posts nothing: check that event_guard_webhook starts with https://discord.com/api/webhooks/.

Support

Open a ticket in our Discord with your server console output from the start, and the rule you're asking about.