Add a similar property to RunContext to ModuleScripts

As a Roblox developer, the current system of organizing content in experiences is both insufficient and insecure for systems that rely on both the client and server.

If you are unaware, the BaseScript.RunContext that was added a few years ago makes it easier to store entire systems under one folder. Here’s an example of a shop system if you’re confused why it is a great feature:

The Issue

ModuleScripts are another great resource to organize your game, but because they can be accessed by both client and server, exploiters can read its contents. In the shop example, what if you had a module dedicated to the server’s handling of purchases including security checks, item granting logic, and other sensitive code? Now either the exploiter can read all of this and easily find vulnerabilities, or you have to place the ModuleScript in ServerScriptService (since it’s secure and not replicated to the client). This makes things unorganized and back to square one:

We need a ModuleScript.RunContext property. The property’s values could be Server, Client, and Both. Modules set to Server will throw an error if the client attempts to read its contents, allowing developers to secure their server code.

Side note: this property would also prevent developers from accidentally using modules from the wrong run context (for modules intended to be used specifically on either the server or client only) :slightly_smiling_face:

17 Likes

A ModuleScript is contextless to its runtime by design (when you think about it, they’re just a cached function). You can’t choose a ‘runtime context’ in this sense, so what your proposal is really asking for is a safety lock to prevent requireing a module from the wrong network side.

You cite preventing exploiters from snooping around server bytecode as a rationale. This obviously can be solved by storing your modules in a nonreplicated container, as you already know.

I’d argue that this solution is better because it shows clear intent (Stuff in ReplicatedStorage is for the client, ServerStorage is for the server). Meanwhile, your solution would require checking the RunContext property of each module is know how it’s supposed to be used.

11 Likes

True, but the easy solution to this is to make server/client modules have different icons from each other. Your argument could also be made for the RunContext property of base scripts, and that is the same solution they ended up using to fix that issue. I think it makes more sense to group specific systems together over grouping everything according to run context.

2 Likes

With all due respect, I really hate this folder structure. Just makes it harder to navigate if you ask me…

Besides, as @index_self said, ModuleScripts are contextless by design. I don’t really see why they shouldn’t be.

Pretty sure RunContext wasn’t designed to put your game’s logic and cie into one single place either way.

5 Likes

It depends on the developer, but in my scenario, my games have a lot of systems that need to be organized in the explorer. I have a mod panel that has tens of thousands of lines of code, about a dozen remotes, and a pretty complex GUI. I want to separate all of these components from the main components of the game so I do not have to work around it every time I work on other systems and so I can easily copy and paste the panel to other games of mine. You would still be able to use the current method as long as it doesn’t cause complexity on Roblox’s end.

Sure you’re 100% right, but isn’t that the case for every other game too? Isn’t that what services are for?

You could use multiple Folders.


But in the end, this is how Roblox games are meant to be organized. ReplicatedStorage for, replicated assets, ServerScriptService for server scripts, etc etc.

The purpose of Script.RunContext was to allow client code to run regardless of what container it existed in + other goodies like plugin contexts. This restriction doesn’t exist for ModuleScripts because, again, their contextless and don’t ‘run’ on their own.

Now, I did realize a case that could be made for a RunContext-like distinction for modules, and that is type-checking. Currently, modules are context-agnostic and thus have no idea if any runtime-specific features like Players.LocalPlayer, RemoteEvent.OnServerEvent or StudioService should be allowed to be used or not. For single-script architectures, this is a bit of a downside.

On merit of that alone, I could support a context-locking feature like this. I just don’t agree with the organizational/security reasons you’ve mentioned.

7 Likes

Since the addition of RunContext, I’ve used ReplicatedStorage similarly, for self contained systems that involve both the server and the client. I’ve even written some ModuleScripts designed to work on both the client and the server


Here, Chests and Settings are client and server sided. Backpack is client sided only
(InventoryModule could just be a folder tbh)

This method has the main advantage of keeping systems contained under a single instance, instead of being spread out into multiple containers, making the organization of a game neater and making it easier to move systems from one game to another (which is great for Resources > Community Resources modules)

The suggestion is interesting, but imo, not necessary. It is easy enough to have a RunService:IsServer() (or IsClient) assert at the top of the script. I am also unsure if the security concern is that big of a deal. Don’t quote me on that, but according to this thread, clients only receive the bytecode rather than the source code, meaning they would have to decompile the bytecode to have something readable (and decompiled code is usually rough). If you don’t have RemoteEvent snooping checks, it might be easier for an exploiter to send garbage data through your remotes to find vulnerabilities instead of decompiling the code and reading it

I personally would be more concerned about exploiters stealing the game’s code than finding vulnerabilities. More of my code is ending up inside of ReplicatedStorage, (even my datastore module is in ReplicatedStorage), meaning an exploiter using a save instance exploit has a much bigger portion of the game

(Don’t ask me how they can save instance scripts if they don’t have access to the source code. Do they really create clone games from decompiled code?)

That would indeed be really useful. I’ve had to do some workarounds because of this

A RunContext property for ModuleScripts could be a nice QoL addition, for explorer icons and making it clear which module can be called by the client/server. Security would be another benefit

although I wouldn’t call it RunContext as module scripts don’t run by themselves, perhaps AllowedContext or something along those lines would be better

1 Like

I really, REALLY, hate how messy having to put server-only modules in ServerStorage away from everything else is.

Maybe not through RunContext but module scripts definitely need selective replication.

6 Likes

I 100% agree with your point about stealing code, but one thing I respectfully disagree with on most replies here is security. I understand that exploiters can only read bytecode, but you would be surprised how often I see exploiters reverse engineering client code in my games just to find out how everything works.

You could argue that you should simply make sure your server code is 100% secure, but everyone is human and it’s never guaranteed you will perform every possible sanity check, patch all bugs, and fix every vulnerability. In some cases, certain exploits can be virtually undetectable no matter how secure your code is. I’d at least like the option to secure some ModuleScripts like server scripts.

2 Likes

To be fair, I don’t have a whole lot of experience working on high ccu games. I have done some mild work on a ~100 ccu game

1 Like

I’ve thought about this problem a bit differently. I don’t know if it’s possible from the technical stand point but I think it would be best if code that should run on the server would simply not replicate to client as it is never supposed to run anyways. Here’s an example:

--Code on the server
local RunService = game:GetService("RunService")
local system = {}

function system.Start()
    if RunService:IsServer() then
        print("Doing some work on the server");
    end
end

return system
--Code replicated to client
local RunService = game:GetService("RunService")
local system = {}

function system.Start()
    if RunService:IsServer() then
    end
end

return system
4 Likes

Everything is possible, but how hard would it be is the real question

Thinking about it, what if, what if module scripts were compiled twice, once where RunService:IsServer() is replaced with true before compilation (for the server), and one where it is replaced with false, for the client

If using --! optimize 2 (which is the default on live games), I would assume the compiler would remove any if statements or conditions in the form of

if false then
  -- Obviously will never run
end

local var = false and "A" or "B" -- Obviously always B

However, this solution might be referred to as a very ugly hack (actually a very very ugly hack… Having to compile scripts twice for something so minor is really bad lol)

But the beauty of it is that it would work without any new property or anything

3 Likes

The solution is to create camera as a container for modulescripts so the camera doesn’t get cloned to the client
image

2 Likes

This is actually a really cool trick. We need a way to do this natively instead of a hacky workaround though, since it isn’t guaranteed this behavior will be the same in the future.

2 Likes

Support! I want to be able to group up relevant scripts like you’ve set it up. It’s hard to do knowing that exploiters can read any module’s bytecode, including ones that are meant for the server only! The current solution is putting those modules in ServerStorage, but that’s more disorganized. We need a way to set modules to be server only, client only, or shared, similar to RunContext for Scripts and LocalScripts.

Assuming you use server only modules, this would reduce the data replicated to the client, would prevent accidental requires that would inevitably fail if they were allowed, and would stop exploiters from dissecting sensitive logic.

4 Likes

Fully agree with this feature, roblox really need add something like this for module scripts, it can really help for code organization