关于 roblox-security
面向 Roblox 开发者的安全技能,介绍服务端验证与反作弊实践,帮助将关键游戏逻辑保留在服务端并减少信任客户端带来的漏洞。
主要功能
- 01服务端权威游戏逻辑
- 02RemoteEvent 参数验证
- 03按玩家设置冷却与速率限制
- 04速度与数值合理性检查
- 05安全放置 ModuleScript
适用场景
- 01验证伤害、货币与物品购买
- 02检查远程交互和能力使用
- 03审查 Roblox 游戏的客户端与服务端边界
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sentinelcore/ roblox-skills --skill roblox-security安装后在 Claude Code 中输入/roblox-security确认已加载。
详细介绍
Roblox 安全:反外挂与服务端验证
核心原则
永远不要信任客户端。 每个 LocalScript 都在玩家的机器上运行,并且可以被修改。所有权威逻辑——伤害、货币、属性、位置变更——都必须放在服务器上。
现代 Roblox 始终启用 FilteringEnabled。除非服务器明确应用客户端所做的更改,否则这些更改不会复制到服务器或其他客户端。
安全与不安全模式
| 模式 | 不安全 | 安全 |
|---|---|---|
| 造成伤害 | LocalScript 设置 Humanoid.Health | 服务器验证后降低生命值 |
| 发放货币 | LocalScript 增加 leaderstats | 服务器验证操作后再增加 |
| Leaderstats 所有权 | LocalScript 拥有 IntValue | 服务器创建并拥有所有 leaderstats |
| 位置变更 | LocalScript 传送角色 | 服务器验证并移动角色 |
| 使用工具 | 客户端命中时触发伤害 | 服务器执行射线检测并造成伤害 |
| 冷却时间 | 客户端在本地跟踪冷却时间 | 服务器按玩家跟踪冷却时间 |
安全的 Leaderstats 设置
-- Script in ServerScriptService — never LocalScript
game.Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = leaderstats
end)
服务端合理性检查
距离检查
local MAX_INTERACT_DISTANCE = 10
InteractRemote.OnServerEvent:Connect(function(player, targetPart)
if typeof(targetPart) ~= "Instance" or not targetPart:IsA("BasePart") then return end
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if not root then return end
if (root.Position - targetPart.Position).Magnitude > MAX_INTERACT_DISTANCE then
warn(player.Name .. " sent interaction from invalid distance")
return
end
processInteraction(player, targetPart)
end)
冷却时间验证
local ABILITY_COOLDOWN = 5
local lastUsed = {}
UseAbilityRemote.OnServerEvent:Connect(function(player)
local now = os.clock()
if now - (lastUsed[player] or 0) < ABILITY_COOLDOWN then return end
lastUsed[player] = now
applyAbility(player)
end)
game.Players.PlayerRemoving:Connect(function(player)
lastUsed[player] = nil
end)
属性范围检查
local MAX_QUANTITY = 99
local ITEM_COST = 50
BuyItemRemote.OnServerEvent:Connect(function(player, quantity)
if type(quantity) ~= "number" then return end
quantity = math.clamp(math.floor(quantity), 1, MAX_QUANTITY)
local coins = player.leaderstats.Coins
if coins.Value < ITEM_COST * quantity then return end
coins.Value = coins.Value - (ITEM_COST * quantity)
-- award items server-side
end)
速率限制
local RATE_LIMIT = 10 -- max calls
local RATE_WINDOW = 1 -- per second
local callLog = {}
local function isRateLimited(player)
local now = os.clock()
local log = callLog[player] or {}
local pruned = {}
for _, t in ipairs(log) do
if now - t < RATE_WINDOW then table.insert(pruned, t) end
end
if #pruned >= RATE_LIMIT then
callLog[player] = pruned
return true
end
table.insert(pruned, now)
callLog[player] = pruned
return false
end
ActionRemote.OnServerEvent:Connect(function(player)
if isRateLimited(player) then return end
handleAction(player)
end)
game.Players.PlayerRemoving:Connect(function(player)
callLog[player] = nil
end)
参数验证工具
-- ServerScriptService/Modules/Validate.lua
local Validate = {}
function Validate.number(value, min, max)
if type(value) ~= "number" then return false end
if value ~= value then return false end -- NaN check
if min and value < min then return false end
if max and value > max then return false end
return true
end
function Validate.instance(value, className)
if typeof(value) ~= "Instance" then return false end
if className and not value:IsA(className) then return false end
return true
end
function Validate.string(value, maxLength)
if type(value) ~= "string" then return false end
if maxLength and #value > maxLength then return false end
return true
end
return Validate
-- Usage
local Validate = require(script.Parent.Modules.Validate)
remote.OnServerEvent:Connect(function(player, amount, targetPart)
if not Validate.number(amount, 1, 100) then return end
if not Validate.instance(targetPart, "BasePart") then return end
-- safe to proceed
end)
速度/反作弊检测
local SPEED_LIMIT = 32
local violations = {}
task.spawn(function()
while true do
task.wait(2)
for _, player in ipairs(game.Players:GetPlayers()) do
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if root and root.AssemblyLinearVelocity.Magnitude > SPEED_LIMIT then
violations[player] = (violations[player] or 0) + 1
if violations[player] >= 3 then
player:Kick("Cheating detected.")
end
else
violations[player] = math.max(0, (violations[player] or 0) - 1)
end
end
end
end)
ModuleScript 的放置位置
ServerScriptService/
Modules/
DamageCalculator.lua -- server-only, never exposed to client
EconomyManager.lua -- server-only
ReplicatedStorage/
Remotes/ -- RemoteEvent/RemoteFunction instances only
SharedModules/ -- non-sensitive utilities only
绝不要将货币、伤害或 DataStore 逻辑放在 ReplicatedStorage 模块中——客户端可以 require() 这些模块。
常见错误
| 错误 | 可被利用的原因 | 修复方法 |
|---|---|---|
使用 FireServer(damage) 且服务器信任该值 | 客户端可以发送任意值 | 服务器根据自己的工具数据计算伤害 |
| 在 LocalScript 变量中保存货币 | 客户端可以修改内存 | 仅由服务器拥有 |
| 触发前在客户端检查距离 | 该检查可以被绕过 | 服务器收到事件后重新检查 |
| RemoteEvent 处理程序没有冷却时间 | 垃圾请求 = 无限资源 | 服务器为每位玩家设置冷却时间 |
信任由客户端设置的 WalkSpeed | 客户端可以将其设为任意高的值 | 由服务器控制并限制 WalkSpeed |
| 在 ReplicatedStorage 模块中放置敏感逻辑 | 客户端可以 require 该模块 | 移至 ServerScriptService |
评论
支持 Markdown