How to stop players filling the server with objects

A cooldown limits how fast players create things, not how many. How to cap objects per player and clean them up.

warningLuaudit check: unbounded-player-creations

What it looks like

Players can create things in the shared world: placed blocks, spawned vehicles, pets, drawings, dropped items or projectiles. Each request has a cooldown, but nothing limits how many a player can have at once, or removes them when they leave.

Luau
remotes.PlaceBlock.OnServerEvent:Connect(function(player, position)
	if onCooldown(player) then return end
	makeBlock(position).Parent = workspace
end)

How to detect it

  • Find every handler that creates instances in a replicated place, such as Workspace or a player's plot, because of a player's request.
  • Roblox's security tactics guide asks, for every feature, how much it could use in the worst case. A cooldown limits the rate, not the total. One request a second for an hour is 3,600 objects from one player.
  • Look for a per-player cap, such as a count of what they own that's checked before creating more, and a server-wide cap.
  • Check that objects are removed when the player leaves, when they despawn, or when a newer one replaces them, such as one vehicle per player.
  • Check what each object costs. Unanchored parts with physics, lights, particles and sounds cost far more than a few anchored parts.
  • If one request can create many objects, report Client decides how much work a call does instead.

What an exploiter could do

They keep sending requests at the allowed rate until the server holds thousands of their objects. Every player's game slows down, physics stutters, and joining players take longer to load, until the server crashes or everyone leaves. Several accounts together reach the limit faster.

How to fix it

  • Keep a count per player and refuse new objects at the limit, or remove the oldest one.
  • Set a server-wide limit for anything with physics.
  • Remove a player's objects when they leave, and give temporary objects a lifetime.

Vulnerable example

Vulnerable
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local cooldown = 0.5
local lastPlaced: { [Player]: number } = {}

ReplicatedStorage.Remotes.PlaceBlock.OnServerEvent:Connect(function(player: Player, position: unknown)
	if typeof(position) ~= "Vector3" or position.X ~= position.X or position.Y ~= position.Y or position.Z ~= position.Z then
		return
	end
	local now = os.clock()
	if now - (lastPlaced[player] or 0) < cooldown then
		return
	end
	lastPlaced[player] = now
	local block = Instance.new("Part")
	block.Size = Vector3.new(4, 4, 4)
	block.Position = position
	block.Anchored = true
	block.Parent = Workspace
end)

Players.PlayerRemoving:Connect(function(player)
	lastPlaced[player] = nil
end)

Fixed example

Fixed
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local cooldown = 0.5
local maxBlocks = 200
local lastPlaced: { [Player]: number } = {}
local blocks: { [Player]: { BasePart } } = {}

ReplicatedStorage.Remotes.PlaceBlock.OnServerEvent:Connect(function(player: Player, position: unknown)
	if typeof(position) ~= "Vector3" or position.X ~= position.X or position.Y ~= position.Y or position.Z ~= position.Z then
		return
	end
	local now = os.clock()
	if now - (lastPlaced[player] or 0) < cooldown then
		return
	end
	lastPlaced[player] = now
	local owned = blocks[player] or {}
	blocks[player] = owned
	if #owned >= maxBlocks then
		local oldest = table.remove(owned, 1)
		if oldest then
			oldest:Destroy()
		end
	end
	local block = Instance.new("Part")
	block.Size = Vector3.new(4, 4, 4)
	block.Position = position
	block.Anchored = true
	block.Parent = Workspace
	table.insert(owned, block)
end)

Players.PlayerRemoving:Connect(function(player)
	lastPlaced[player] = nil
	local owned = blocks[player]
	blocks[player] = nil
	if owned then
		for _, block in owned do
			block:Destroy()
		end
	end
end)

Common false positives

  • Objects inside a player's own plot with a building limit the server enforces are already capped.
  • Objects the server removes after a short lifetime, such as projectiles with Debris or a task.delay destroy, can't pile up faster than the cooldown allows.
  • Where placement is also unchecked, such as blocks placed anywhere on the map, report Placement the server doesn't keep inside the player's area as well.