SecureRemote – Guardrails for Roblox Remotes (schema, rate limit, idempotency, dedupe)

What is it:

SecureRemote is a tiny wrapper around RemoteEvent/RemoteFunction that adds the boring but important parts: schema validation, rate limiting with cooldown/backoff, idempotency via __reqId, duplicate-content blocking, minimal interval between accepts, unknown-key rejection, and callback timeout guards.

Why use it:

  • Fewer exploit surfaces: only fields you allow, within sizes you allow.
  • Less spam: per-player and per-action throttling, dedupe, and single-flight.
  • Fewer hangs: server callbacks are wrapped with a timeout.
  • Cleaner code: one place to configure policy, same shape for responses.

Features:

  • Schema validation (type/required/min/max/enum/regex/nested/defaults)
  • Per-player and per-action rate limits with cooldown and optional backoff
  • Idempotency window (__reqId)
  • Duplicate-content dedupe window
  • Minimal interval between accepts (anti auto-click)
  • Optional single-flight lock (rejects overlapping requests)
  • Reject unknown keys (on by default)
  • Callback timeout guard
  • Simple stats: ok/rejected/rateLimited/callbackError
  • Optional permission gate (SetCanInvoke)

Install:

*SecureRemote

  • Require from server script

Example (Server):

local SecureRemote = require(game.ServerScriptService.SecureRemote)

local BuyItem = SecureRemote.new("BuyItem", game.ReplicatedStorage, "Function", {
    minIntervalSec = 0.2,     -- reject ultra-fast repeat clicks
    rateLimit = 5,
    rateWindow = 1,
    cooldownAfterBurst = 2,
    idempotencyWindow = 10,
})

local schema = {
    itemId = { type = "string", required = true, allowed = { "Sword", "Shield", "Potion" } },
    amount = { type = "number", min = 1, max = 5, default = 1 },
    attributes = {
        type = "table",
        schema = {
            rarity = { type = "string", allowed = { "Common", "Rare", "Epic" }, default = "Common" },
            enchanted = { type = "boolean", default = false },
        },
    },
}

BuyItem:SetCanInvoke(function(player, data)
    -- Example: block guests, region, game-mode, etc.
    return true
end)

BuyItem:OnServerFunction(schema, function(player, data, trace)
    -- Do your logic here
    return {
        success = true,
        message = "OK",
        itemBought = data.itemId,
        quantity = data.amount,
        paid = 100,
        trace = trace,
    }
end)

Example (Client):

local HttpService = game:GetService("HttpService")
local BuyItem = game.ReplicatedStorage:WaitForChild("BuyItem")

local req = {
    __reqId = HttpService:GenerateGUID(false), -- idempotency
    itemId = "Sword",
    amount = 1,
    attributes = { rarity = "Epic", enchanted = true },
}

local res = BuyItem:InvokeServer(req)
if res and res.success then
    print("Bought:", res.itemBought)
else
    warn(res and res.code, res and (res.reason or res.message))
end

  • Change what you want. No credit required
  • Created by steakzetaim. If it helped your project or saved you time, a follow is appreciated
7 Likes

Are you artificially intelligent

1 Like

Now watch me securely fire my juice into your client

1 Like

human, unfortunately.
Uploading: image.png…
Uploading: image.png…
Uploading: image.png(1)…
Uploading: image.png(2)…
Uploading: image.png(3)…