Permissions and security
Admin and moderator groups, everyone, staff and admin levels, ox_target.<key> ACE, custom keys, client-side visibility and the checks really performed by the server.
Goal: decide who sees and who can use each option, knowing what the server really checks.
Files: config.lua (AdminGroups, ModGroups, UseAcePermissions, Permissions) and server.cfg for ACE.
Levels
| Value | Access |
|---|---|
'everyone' |
Everybody. |
'staff' |
Groups from Config.AdminGroups or Config.ModGroups. |
'admin' |
Groups from Config.AdminGroups. |
{ 'group1', 'group2' } |
At least one of the listed groups. An empty list allows nobody. |
false |
Nobody. |
'otherKey' |
Same result as the otherKey key of Config.Permissions (up to 6 hops; beyond that or in a loop: refused). |
Group membership is checked by Bridge.HasGroup:
- ESX:
xPlayer.getGroup()must be exactly equal to the group name. Asuperadminplayer is notadmin: list both if needed. - QBCore:
QBCore.Functions.HasPermission, then thegroup.<name>ACE. - Others (qbx, ox, nd, standalone):
IsPlayerAceAllowed(source, 'group.<name>'). If that test fails on your server, grant that ACE to the group explicitly, for exampleadd_ace group.admin group.admin allow. - The server
HasGroup(source, group)hook replaces all of the above.
Keys
| Setting | Type | Default | Effect |
|---|---|---|---|
Config.AdminGroups | string[] | { 'admin', 'superadmin', 'owner' } | Groups matched by the “admin” level (and “staff”). Read by: protected engine, bridge/server.lua |
Config.ModGroups | string[] | { 'mod' } | Groups added to the “staff” level. Read by: protected engine, bridge/server.lua |
Config.UseAcePermissions | boolean | false | true: the ox_target.<key> ACE also grants the matching permission, before groups. Read by: protected engine |
Config.Permissions | table |
| Each key is everyone, staff, admin, a list of groups, false, or the name of another key. You can add your own keys. Read by: protected engine |
You can add your own keys and use them in the permission field of an option or a server action:
Config.Permissions = {
staffMenu = 'staff',
revive = 'admin',
heal = 'admin',
selfRevive = 'staff',
selfHeal = 'staff',
forceEmote = 'admin',
forceMe = 'admin',
weather = 'admin',
giveKeys = 'admin',
devTools = 'admin',
emsTools = { 'ambulance_chief', 'admin' },
}
Do not remove the ten original keys: a built-in whose key is missing becomes invisible and refused for everyone.
ACE
With Config.UseAcePermissions = true, the ox_target.<key> ACE grants the permission before the group check:
add_ace group.helper ox_target.selfHeal allow
add_ace identifier.license:0123456789abcdef ox_target.devTools allow
The ACE check repeats at every step of a reference: for selfHeal = 'staff', the ox_target.selfHeal ACE is enough, but so is ox_target.staff, which then opens every key set to 'staff'. The ACE does not replace the key: removing selfHeal from Config.Permissions makes the option invisible even with the ACE.
What the client does
The client receives a permission map from the server: every Config.Permissions key, the everyone, staff, admin levels, and the result for each custom server action. It requests it again every Config.Security.permissionRefresh ms and after the character loads. This map is only used to hide options.
Consequences:
- An option whose
permissionis unknown to the map is always hidden. - A group change in game can take up to
permissionRefreshms to show in the menu. - An option’s
jobs,groupsanditemsfields are evaluated client side for display.
What the server checks
Built-ins that go through a server action and your server_action options are checked by the engine, in this order: known action, anti-spam, feature, integration availability, permission, job, arguments, target existence and distance (+ distanceTolerance), item. An abnormally refused request is reported (OnSuspiciousActivity or console).
Options without a server check in the archive: copy/show ID, /me input, anti-punch, Dev Tools copy tools, “take appearance”, appearance restore, local weather tools and invoices (validated by the billing script). The Rock Paper Scissors challenge and the trunk hideout have their own server checks (distance, availability).
Common mistakes
- Staff option visible to everyone: the category has
permission = 'staffMenu'but a child option was moved out of the submenu without a permission. 'superadmin'refused under ESX: add it toConfig.AdminGroups.- ACE without effect:
Config.UseAcePermissionswas left atfalse.