False positives
An anti-cheat that only finds true positives is easy. Hard is the false-positive rate. This page describes how we handle it — and where we got it wrong before we got it right.
Three filtering layers
Detection passes through three layers before a finding reaches you. Each layer is independently auditable, and a finding has to survive all three to surface as actionable.
Deterministic pattern detection
Runs on the FiveM server itself. Fast, cheap, and by nature noisy: the same constructs framework code uses legitimately can also appear in malicious code. We name the class of pattern (code execution, obfuscation, exfiltration endpoints, privilege escalation, anti-AC tampering, hardcoded network targets) — never the exact strings, since publishing them would just be a tuning sheet for the attackers we're trying to detect.
Whitelist-first filter
Hits inside known-clean framework resources (ESX, QBCore, ox-family, qb-family, pma- and similar standard add-ons) and inside configuration files are filtered as expected behavior — except when the pattern belongs to the "always-suspicious" class. Code execution, obfuscation, anti-AC tampering, and hardcoded exfiltration endpoints survive the whitelist; they're suspicious anywhere. This split is what made false positives go from a daily annoyance to a non-issue (see iteration history below).
In-house AI severity grading
Our own AI systems, running on a dedicated EU GPU server, classify the surviving findings into low / medium / high severity. Repeated snippets from standard frameworks get cached, so an ESX or QBCore snippet is graded once and reused across customers — the second customer to hit the same snippet pays nothing for the grade. Outage of the grading system is fail-silent: findings still reach the dashboard ungraded, with a "grading…" badge, and you can act on them without waiting.
We don't publish which models we run. The reason is the same as for pattern strings: a public model identifier is a target. If it changes, you don't have to care; the contract is the verdict, not the implementation.
Anonymized snippet-hash corpus
We do not retain customer resource source code. What we keep, in a separate database in the EU, is a SHA-256 hash of each suspicious snippet alongside the assigned severity, the verdict, a short reason, and a counter of how often we've observed the same hash across all customers.
That corpus is doing four jobs at once:
- Warm cache — repeated snippets resolve from memory, not from a fresh model call.
- Cross-customer pattern signal — a snippet that 20 customers see is very likely a standard framework snippet; one that one customer sees, with a high severity, is more likely an isolated incident worth attention.
- Manual promotion — we can mark a hash as a confirmed-bad ground truth and feed it into future tuning passes.
- Refinement audit — when we change the pattern set or the grading model, we re-evaluate the corpus and can quantify the FP delta before shipping.
Hashes are one-way. We can't reconstruct the original snippet. The corpus is documented as a separate processing purpose in our Privacy Policy §9 and Data Processing Addendum.
Iteration history (May 2026)
We didn't get this right on the first attempt. The history matters because it's what makes the current numbers credible. Same 59-resource representative test server across all three iterations.
Bar width is proportional to false-positive count. The current iteration produces zero findings on a clean server while still cleanly detecting a planted backdoor.
First version: plain pattern matches
Patterns ran against every started resource. On a typical roleplay server with a modern framework stack, that produced near-100% false positives — frameworks call the same APIs the malicious code calls, just legitimately. Lesson: pattern matching without resource context is unusable on FiveM.
Second version: framework whitelist + pattern refinement
A curated framework list filtered the obvious noise; broad-fire patterns were tightened. Result on a representative pilot: ~20 findings per scan job, all of them helper or asset patterns inside framework resources or config files (imports, file I/O, base64-embedded assets). Lesson: a whitelist alone isn't enough. Some patterns are suspicious anywhere; some are only suspicious outside frameworks and configs. The split has to be aware of both axes.
Current version: always-suspicious whitelist + config-file filter + AI grading
The pattern set is split into "always suspicious" (code execution, obfuscation, hardcoded exfiltration, anti-AC tampering) and "context-dependent" (helper APIs). The first survives the framework filter; the second doesn't. Configuration files get an additional filter for hardcoded endpoints (a webhook URL inside a Config.lua is configuration, not exfiltration). On top of that, the AI severity grading classifies the surviving findings.
Result: 0 findings on a representative test server with 59 started resources. With a real backdoor planted in a framework resource as a validation, the same pipeline cleanly detected it at high severity. True-positive rate held; false-positive load went to zero.
Who decides the action
Every finding — including high severity — is an alert in the dashboard, not an auto-disable. The operator reviews, acts (whitelist with audit-logged reason, remove the resource, or keep monitoring), and the audit log records what happened.
We do not auto-disable resources today. There is a deliberate single opt-in path on the roadmap for operators who want a review queue with auto-action under a strict threshold, but it's out of scope for current pilot servers.
Player appeals
If a player is denied a join because of a hoaxeye verdict and believes it's a false positive, the appeal address is [email protected]. We process those under the DSA's notice-and-action framework (Articles 16, 17, 21) and reply in writing. The full procedure is on the abuse appeal page.
On the operator side, see the FAQ for what to do before appealing and how the per-server whitelist interacts with our review.