How to check a FiveM resource for a backdoor before you install it

Published Updated 10 min readTitan Software z.s.

In short

To check a FiveM resource for a backdoor, review it before it ever reaches your server: unzip the archive somewhere isolated, read fxmanifest.lua to learn which files actually load and on which side, then search every loaded file for the four things a backdoor needs — an outbound network call, a decoding step, a dynamic code loader such as load or assert(load(...)), and an identity or permission grant. A resource that combines a network fetch with load() is remote code execution by design, no matter how the seller describes it. Automated static analysis such as FXScan performs the same review in seconds and shows the data-flow path with file names and line numbers, but a clean report is evidence, not a guarantee.

Key takeaways

  • Read fxmanifest.lua first — a file that is never loaded cannot hurt you, and a file loaded server-side has full access to your database credentials.
  • The dangerous combination is network fetch → decode → load(). Any two of those are worth a look; all three together is a remote code execution channel.
  • Obfuscation is not a finding on its own, but obfuscation inside a resource that also opens sockets is a reason to stop.
  • Escrow does not mean reviewed. It means you cannot read the code — which removes your ability to review it at all.
  • Automate the mechanical part of the review so your attention is left for the parts a scanner cannot judge.

Almost every compromised FiveM server we hear about was compromised by code its owner installed deliberately. Not an exploit against the FiveM runtime, not a stolen server key — a resource downloaded from a marketplace, a Discord, or a leak site, dropped into resources/ and started. The attacker's only real work was getting you to install it.

That is genuinely good news, because it means the decisive control is yours: a review that happens before the resource ever runs. This article is the review process we use, written so you can run it by hand. It takes ten to twenty minutes for a typical resource, and it catches the overwhelming majority of what is actually circulating.

Before you start: work somewhere the resource cannot run

Unpack the archive on a machine that is not your game server, and do not let anything inside it execute. You are reading files, not testing behaviour. That distinction matters: a resource that is malicious on start does not need you to be careless for long.

  • Extract to a temporary directory outside your server tree, so a stray copy cannot end up in resources/.
  • Do not open a .exe, .dll, .bat, .ps1 or .sh that shipped inside a supposedly Lua-only resource — their presence is already a finding.
  • Watch for path traversal in the archive itself (entries starting with ../). A well-behaved resource archive contains only its own folder.
  • Keep the original archive. If you find something, the unmodified file is what you send to a scanner or a disclosure contact.

Step 1 — Read fxmanifest.lua and build a map

fxmanifest.lua declares what FiveM actually loads and where it runs. It is the only file that tells you which of the 200 files in the archive matter. Read it first, every time, and write down three lists: server scripts, client scripts, and everything else.

lua
fx_version 'cerulean'
game 'gta5'

shared_scripts { 'config.lua' }

client_scripts {
  'client/main.lua',
  'client/ui.lua',
}

server_scripts {
  '@oxmysql/lib/MySQL.lua',
  'server/main.lua',
}

files { 'html/index.html', 'html/app.js' }
ui_page 'html/index.html'
A normal manifest — everything declared, nothing dynamic

The side a file loads on decides what it can reach. A client script runs on players' machines and can lie to you but cannot read your database. A server script runs in your process with your credentials, your MySQL connection and your console — a backdoor there owns everything the server owns. When you are short on time, read the server scripts.

Step 2 — Search for the four ingredients of a backdoor

A backdoor that gives an attacker ongoing control needs some combination of four capabilities. Individually they are all legitimate; the risk lives in how they connect.

CapabilityWhat it looks like in LuaWhy it matters
Outbound networkPerformHttpRequest, fetch, socket, curl, wget, os.executeThe channel a payload arrives on and stolen data leaves on.
Decodingbase64, string.char, \x escapes, gsub-based unpackers, bit ops on stringsTurns a blob into code so a plain-text search misses it.
Dynamic loadingload, loadstring, assert(load(...)), load(...)()Converts data into executable code — the actual execution step.
Identity / permissionadd_ace, add_principal, ExecuteCommand, hardcoded licence or Steam identifiersGrants the attacker in-game or console authority that survives a restart.
What to grep for, and why each one matters
bash
# Dynamic code loading — read every hit
grep -rn --include='*.lua' -E '\b(load|loadstring)\s*\(' .

# Outbound network calls
grep -rn --include='*.lua' -E 'PerformHttpRequest|os\.execute|io\.popen|socket' .

# Permission and identity grants
grep -rn -E 'add_ace|add_principal|ExecuteCommand' .

# Long encoded-looking blobs
grep -rnE '[A-Za-z0-9+/]{120,}={0,2}' --include='*.lua' .
A first pass over an extracted resource

Hits are not verdicts. A shop resource calling PerformHttpRequest to a payment provider is doing its job. What you are looking for is a path: data comes in from the network, gets decoded, and ends up as an argument to load. If you can draw that line through the file, you are looking at remote code execution, and the operator of that URL decides what runs on your server tomorrow.

Step 3 — Follow the data, not the keywords

Serious backdoors do not put the whole chain on one line. They spread it: a URL assembled from three string fragments in a config file, a response stored in a table, a helper function in a second file that decodes it, and the call to load() somewhere that reads like initialisation code. Keyword search finds the pieces; only following the values finds the chain.

  1. 1

    Start at every load() and work backwards

    For each dynamic-load call, ask where its argument came from. A literal string in the same file is fine. A variable is a question. A variable that at any point held an HTTP response is an answer.

  2. 2

    Start at every network call and work forwards

    Where does the response go? If it is only compared, logged or displayed, fine. If it is decoded and passed on, keep following it.

  3. 3

    Check what the resource does on start

    Look at top-level code and the first CreateThread. Backdoors want to run without waiting for a player to trigger anything.

  4. 4

    Read the config file properly

    Configs are the favourite hiding place, because reviewers skim them and operators edit them. A base64 blob or a URL in a config deserves the same scrutiny as one in server/main.lua.

Step 4 — Judge obfuscation in context

Obfuscated Lua is common in paid resources and is not by itself evidence of malice — sellers use it to make copying harder. But obfuscation destroys your ability to review, so it changes what a clean review is worth. Treat it as a risk multiplier rather than a finding: obfuscation in an otherwise inert UI script is one thing; obfuscation in a server script that also opens a socket is a reason to stop and ask the seller for readable code.

Step 5 — Automate the mechanical part

Steps 1 to 3 are exactly the kind of work a machine does better than a tired human at midnight: parse the manifest, build the file graph, trace values from network sources to execution sinks, and report the path. That is what we built FXScan to do. You upload the archive, it never executes anything inside it, and it reports the chain with file names and line numbers so you can verify the finding yourself rather than trusting a score.

Scan a resource with FXScanFree static analysis for FiveM resource archives — three scans a month at no cost, no server access required.

Use the tool for coverage and your own reading for judgement. A scanner is very good at 'this value reaches load() through these four hops' and has no opinion at all about whether a resource that phones home to the seller's licence server is acceptable to you. That second question is yours.

What a clean result does and does not mean

Static analysis reasons about code without running it. That is its strength — nothing malicious executes during the review — and also its limit. A resource can pull benign-looking content today and something else next month. Code can be constructed at runtime in ways no static tool resolves. And a resource can be entirely honest while still being badly written enough to hand a player admin.

  • Clean means: no known-bad pattern and no traceable path from a network source to code execution in the code as shipped.
  • Clean does not mean: safe forever, well written, free of logic bugs, or trustworthy in what it does with data it legitimately collects.
  • Re-scan on every update. The version you reviewed is not the version an auto-updater installed last night.

That last point is why one-off scanning is only half the answer for a production server. Continuous monitoring — re-checking every resource as it changes and alerting when something new appears — is what Titan Security Cloud is for. The one-time check tells you what you are installing; the recurring check tells you what changed.

A short checklist to keep

  1. 1Extract outside the server tree; never execute anything from the archive.
  2. 2Read fxmanifest.lua; list what loads server-side.
  3. 3Grep for load/loadstring, network calls, ace/principal grants and long encoded blobs.
  4. 4Follow each hit forwards and backwards until you know where the value came from and where it goes.
  5. 5Judge obfuscation by what surrounds it, and refuse escrow you are not willing to trust blindly.
  6. 6Run an automated scan for coverage, and read the evidence rather than the verdict.
  7. 7Record what version you approved, and re-review on every update.

None of this requires being a security researcher. It requires treating installation as a decision rather than a formality — and doing the ten minutes of reading before the resource has your database credentials, instead of after.

Frequently asked questions

How do I know if a FiveM resource has a backdoor?

Unpack the archive without running it, read fxmanifest.lua to see which files load and on which side, then search the loaded files for dynamic code loading (load, loadstring), outbound network calls (PerformHttpRequest, sockets), decoding routines and permission grants (add_ace, add_principal). A traceable path from a network response through a decoder into load() is a backdoor regardless of how the resource is marketed. Automated static analysis such as FXScan performs the same trace and reports the path with file names and line numbers.

Is a FiveM resource with obfuscated code always malicious?

No. Obfuscation is widely used by paid resource sellers to make copying harder, and it is not evidence of malice on its own. It does mean nobody — including you — can review the code, so it removes your ability to verify anything. Treat obfuscation as a reason to demand more trust in the source, and treat obfuscation combined with network access in a server script as a reason to stop.

Can a scanner guarantee a FiveM resource is safe?

No, and any tool that claims otherwise is overselling. Static analysis reasons about code as shipped; it cannot know what a remote server will return next month, and it cannot resolve every runtime-constructed string. A clean report is evidence that no known-bad pattern and no traceable execution path was found — useful evidence, but not a guarantee. Re-scan on every update.

Which files should I review first in a FiveM resource?

fxmanifest.lua first, because it tells you which files actually load. Then everything listed under server_scripts, because server-side code runs with your database credentials and console access. Client scripts come next, and files that the manifest never loads can usually be deprioritised entirely — though their presence in an archive can itself be a signal worth noting.

Does scanning a resource require access to my FiveM server?

No. Static analysis works on the resource archive itself, which is the point: you review the code before it ever reaches your server. FXScan takes the uploaded archive, validates and extracts it without executing anything, and analyses the files. Your server is never connected to and never needs to be.

Related projects

Read next

6 min read

Reading fxmanifest.lua like a security reviewer

A field guide to the FiveM resource manifest: what each directive grants, which entries change the blast radius, and the red flags worth stopping on.

EngineeringFiveM securityServer operations
Read article