How to stop players dragging other players' things
DragDetectors let everybody drag by default. How to use a Scriptable permission policy that checks ownership on the server.
What it looks like
A DragDetector lets players move something that belongs to someone or matters to the game, such as furniture on a plot, a chess piece, a puzzle block or a shop display. Its PermissionPolicy is left at the default, Everybody, so any player can drag it.
Luau
local detector = Instance.new("DragDetector")
detector.Parent = furnitureHow to detect it
- Find every
DragDetector, in scripts withInstance.new("DragDetector")and in the project or place if they're visible, and work out what each one moves. - Roblox's drag detector guide says
PermissionPolicydefaults toEverybody.Scriptableuses a function registered withSetPermissionPolicyFunctionto decide per player, and with no function, or an invalid result, nobody can drag. - Roblox's security guide says that for drag events the server checks only
EnabledandPermissionPolicy. It doesn't check distance or any other property, soMaxActivationDistancedoesn't stop a client dragging from anywhere. - For player-owned objects, check that the permission function compares the player with the owner the server recorded, not an attribute or name the client could influence.
- For turn-based or timed objects, check the permission function reads server-side state, like whose turn it is.
- With
RunLocallyset totrue, nothing is sent to the server, so the game must use remotes to save the result. Judge those remotes with the usual checks, such as Placement the server doesn't keep inside the player's area. - Watch for
DragDetectoron unanchored parts. Players can also move those through network ownership. See Rewards decided by parts a client simulates.
What an exploiter could do
They drag other players' furniture off their plots, move pieces in a game they're not playing, solve puzzles for others or shift shop displays, from anywhere on the map. Where positions are saved, the damage stays after they leave.
How to fix it
- Set
PermissionPolicytoScriptableand register a function that checks the player against server-side state, such as the plot's recorded owner or whose turn it is. - Set
Enabledtofalseon the server when an object shouldn't be dragged at all. - When the dragged position is saved, check the final position on the server before saving it.
Vulnerable example
Vulnerable
local Workspace = game:GetService("Workspace") local plots = Workspace:WaitForChild("Plots") local function makeDraggable(item: Model) local detector = Instance.new("DragDetector") detector.DragStyle = Enum.DragDetectorDragStyle.TranslatePlane detector.MaxActivationDistance = 20 detector.Parent = item end for _, plot in plots:GetChildren() do plot.ChildAdded:Connect(function(child) if child:IsA("Model") then makeDraggable(child) end end) end
Fixed example
Fixed
local Workspace = game:GetService("Workspace") local plots = Workspace:WaitForChild("Plots") local owners: { [Instance]: Player } = {} local function makeDraggable(item: Model, plot: Instance) local detector = Instance.new("DragDetector") detector.DragStyle = Enum.DragDetectorDragStyle.TranslatePlane detector.MaxActivationDistance = 20 detector.PermissionPolicy = Enum.DragDetectorPermissionPolicy.Scriptable detector:SetPermissionPolicyFunction(function(player: Player) return owners[plot] == player end) detector.Parent = item end for _, plot in plots:GetChildren() do plot.ChildAdded:Connect(function(child) if child:IsA("Model") then makeDraggable(child, plot) end end) end
The owners table is filled by the server code that hands out plots.
Common false positives
- Toys and props anyone is meant to play with, such as a ball in a lobby, can use
Everybody. - Drag detectors used only for local interface or previews, with
RunLocallyset and nothing saved, don't affect other players. Scriptablewith a function that checks server-side ownership, like the fixed example, is the fix.