Why server logic in ReplicatedStorage modules is exposed

Any module that reaches the client can be decompiled, even if it never runs there. How to keep anti-cheat rules and unreleased content on the server.

warningLuaudit check: server-code-in-shared-modules

What it looks like

A ModuleScript in ReplicatedStorage, or anywhere else that replicates, holds server-only logic: a branch behind RunService:IsServer(), or functions that only server scripts call. It might hold anti-cheat limits, reward rules, hidden admin checks or content that isn't released yet. The developer assumes clients never run that code, so they can't see it.

Luau
if RunService:IsServer() then
	function Quests.claim(player, questId)
		...
	end
end

How to detect it

  • List every ModuleScript in ReplicatedStorage, ReplicatedFirst, StarterPlayer, StarterGui, StarterPack and Workspace.
  • Roblox's access control guide says any ModuleScript that replicates can be decompiled by an exploiter, even if it is never required on the client. It warns against modules that mix server and client code, because the server logic is exposed.
  • Look for RunService:IsServer() or IsClient() branches, OnServerEvent connections, DataStoreService, MessagingService or HttpService calls, and functions with names like grant, claim, ban or validate in those modules.
  • Look for detection thresholds, secret quest or code lists, release dates and unreleased items in replicated data.
  • Secrets such as API keys and webhooks are API keys or webhooks that clients can read, and values sent to clients are Hidden information sent to every client. This check is about logic and rules.

What an exploiter could do

They decompile the module and read exactly how the server checks them. Anti-cheat limits tell them how fast they can move or fire without being caught. Reward rules show which checks are missing. Unreleased content and hidden quests can be leaked or claimed before launch. None of this is a bug on its own, but it makes finding and using real bugs much faster.

How to fix it

  • Move server-only code into ModuleScripts in ServerScriptService or ServerStorage, which never replicate.
  • Keep only what the client genuinely needs, such as display names and prices, in shared modules.
  • Don't ship unreleased content in replicated places until it's ready to be seen.

Vulnerable example

Vulnerable
local RunService = game:GetService("RunService")

local Movement = {
	walkSpeed = 16,
}

if RunService:IsServer() then
	Movement.maxSpeed = 24
	Movement.strikesBeforeKick = 5

	function Movement.tooFast(distance: number, elapsed: number): boolean
		return elapsed > 0 and distance / elapsed > Movement.maxSpeed
	end
end

return Movement

Fixed example

Fixed
return {
	walkSpeed = 16,
}
Fixed
local Shared = require(game:GetService("ReplicatedStorage").Shared.Movement)

local SpeedRules = {
	maxSpeed = Shared.walkSpeed * 1.5,
	strikesBeforeKick = 5,
}

function SpeedRules.tooFast(distance: number, elapsed: number): boolean
	return elapsed > 0 and distance / elapsed > SpeedRules.maxSpeed
end

return SpeedRules

The first module stays in ReplicatedStorage. The second lives in ServerScriptService.

Common false positives

  • Shared modules with only data the client already sees, such as item names, prices shown in the shop or tween settings, are fine.
  • Validation the client runs only to give quick feedback, which the server repeats, exposes nothing the client couldn't work out by trying.
  • Well-known open-source frameworks whose code is public anyway expose nothing new. Judge the game's own logic inside them.