What private server owners should be able to change

Private server commands that give coins, pets or levels let anyone with a server farm saved progress. How to keep owner commands to things that end with the server.

criticalLuaudit check: private-server-commands

What it looks like

Private server owners get commands in their own server, and some of those commands give something that's saved: currency, items, levels, wins or leaderboard stats. Or the check for "this is a private server" lets in more people than the owner.

Luau
if player.UserId == game.PrivateServerOwnerId then
	profile.Coins += amount
end

How to detect it

  • Find every use of game.PrivateServerOwnerId and game.PrivateServerId, and every command system with a "VIP server" or "private server" mode.
  • For each command those players get, ask whether its effect outlives the server. Anything written to a profile, data store, ordered data store or badge does.
  • Look for indirect routes: a server-wide multiplier, event or drop-rate setting that a private server owner can switch on, and that boosts rewards which are then saved.
  • Check how ownership is decided. PrivateServerId is also set in reserved servers made with ReserveServerAsync, so game.PrivateServerId ~= "" on its own gives the commands to everyone in every reserved server. Use PrivateServerOwnerId, which is 0 in standard and reserved servers.

What an exploiter could do

Private servers can be free, depending on the game's settings, and a paid one is open to anyone willing to pay. Anyone who owns one can farm currency, items or levels with no one to stop them, then take them into public servers, trade them where the game allows it, or climb the leaderboards.

How to fix it

  • Keep private server commands to things that end with the server: time of day, gravity, music, picking the map, or kicking players from that server.
  • If a command must touch progress, such as a practice mode, don't save what's earned there, or save it separately from the main profile.
  • Decide ownership with player.UserId == game.PrivateServerOwnerId, checked on the server.

Vulnerable example

Vulnerable
local Lighting = game:GetService("Lighting")
local Players = game:GetService("Players")
local PlayerData = require(script.Parent.PlayerData)

local function onChatted(player: Player, message: string)
	if game.PrivateServerOwnerId == 0 or player.UserId ~= game.PrivateServerOwnerId then
		return
	end
	if message == ";night" then
		Lighting.ClockTime = 0
	elseif message == ";coins" then
		local profile = PlayerData.get(player)
		if profile then
			profile.Coins += 1000
		end
	end
end

Players.PlayerAdded:Connect(function(player)
	player.Chatted:Connect(function(message)
		onChatted(player, message)
	end)
end)

Fixed example

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

local function onChatted(player: Player, message: string)
	if game.PrivateServerOwnerId == 0 or player.UserId ~= game.PrivateServerOwnerId then
		return
	end
	if message == ";night" then
		Lighting.ClockTime = 0
	elseif message == ";day" then
		Lighting.ClockTime = 12
	end
end

Players.PlayerAdded:Connect(function(player)
	player.Chatted:Connect(function(message)
		onChatted(player, message)
	end)
end)

Common false positives

  • Commands that only change the server itself, like lighting, music or which map loads next, are fine for private server owners.
  • Kicking players from the owner's own server is normal. Banning them from the whole game is not.
  • Games that turn saving off in private servers, or save private server progress separately, can give owners anything. Check that the save code really does skip it.