Working on a new leaderstats Security system [Need Help]

Hello, currently I have been working on a leaderstats system that feeds out of a module script.

local hexSecurity = {}

Players = game:WaitForChild(“Players”)
local function leaderstatsData(player)
local leaderstats = Instance.new(“Folder”)
leaderstats.Name = “leaderstats”
leaderstats.Parent = player

local cash = Instance.new("IntValue")
cash.Name = "Cash"
cash.Value = 0
cash.Parent = leaderstats

end

game.Players.PlayerAdded:Connect(leaderstatsData)
return hexSecurity

Now create a script inside of ServerScriptService and name it “leaderstats”.

require(game:WaitForChild(“ServerScriptService”):WaitForChild(“HexSecure”))

Done!

Currently it is just a basic leaderstats but figured if anyone had ideas on how to better improve the security of leaderstats or if having it setup this way is even a good idea. Let alone secure.

It’s 100% secure if you only manage or read it on the server.

2 Likes

If I’m understanding correctly, you are using leaderstats to read and write to data?

I wouldn’t use leaderstates for storing data at all. It’s archaic and vulnerable to security flaws.
Instead, store the data in a server-side ModuleScript through tables – that way, you can easily implement essential methods such as data storage and data reading.

If you want to use leaderstats to display the data itself, that’s fine – just pull the values from where the data is stored and simply update the leaderstats

1 Like

So, would it be best to maybe try more than 2 scripts to hold data? I’ve never used a module script to hold leaderstats information. I want to try to make something as secure as possible, ofc I know about the datasaves and so on. This was just a basic concept before I moved forward!

So the Module Script Holds the leaderstats information and the original “leaderstats” is just to update the server

This won’t change anything. If you have leaderstats read on the server to determine if a team won, for example, it will be secure. Unless you trust the client, the server will always be secure.

As with the module script solution mentioned earlier, it doesn’t make any difference. If you have multiple scripts, it might not be the best solution. However, if you only have one script that manages this data, no harm will be caused if you choose to store the data in the module or leaderstats.

3 Likes

What is unsafe about leaderstats? What security flaws are you talking about?

No.

Here’s a (rough, untested, and probably suboptimal lol) example. You can save player data through their player ID Player.UserId → every individual is guaranteed to have a unique key this way, so there is no chance of data collision.

Also, you’d probably wrap these in pcalls and handle stuff like autosaving on the server and whatnot. Again, this is just an example to nudge you into the right direction.


-- An example of what player data may look like : [UserId] = {Data}
local PlayerData = {
  [12345678] = {Coins = 1674, Gems = 25, Kills = 17}
}

-- Data that new players will have
local NewData = {
  Coins = 100,
  Gems = 3,
  Kills = 0
}

-- Get a player's data
local function PlayerData.GetData(Player)
  if not PlayerData[Player.UserId] then 
    warn(`No player data for {Player} exists!`) 
  end
  return PlayerData[Player.UserId]
end

-- Save a player's data
local function PlayerData.SaveData(Player)
 -- DataStore shenanigans, too tired to look it up
end

-- etc., etc.

return PlayerData
1 Like

Not necessarily unsafe, per se? Poor choice of wording on my part, sorry, haha. Like everything else, they’re safe as long as you don’t allow the client to make any unruly modifications to data. Client changes to leaderstats won’t replicate regardless

They’re a poor choice of data storage (imo) because

  • it is hard to store any form of deep data with only Value objects
  • numerical ValueObjects themselves are limited to 2^63 - 1, which is far below the floating point limit of 2^1024 -1
  • you would have to convert the leaderstat values to a dictionary anyways to save them, so why not skip the hassle and just use a dictionary to store player data?
  • they invite poor data storage practices – inexperienced developers tend to gravitate towards leaderstats due to their simplicity and ease of implementation, but this can very quickly lead to poor programming and data exploitation (which is why I said they were unsafe in the first place)

I have been trying to mess with DataStoreService and such but i’m not the best at that stuff yet.

1 Like

Well, it is something you will have to learn if you ever want to do proper data saving.
This is a fantastic resource on DataStores; there’s examples, detailed explanations, and more

1 Like

I know, i’ve been doing research and trying to learn more and more.

1 Like

1.) Don’t know what you mean by “Deep Data”.

2.) Many games—if not the majority on roblox—don’t need astronomically high numbers stored for players, with the exception of simulators, but even then, many simulators use regular leaderstats, and I’ve personally never seen any glitches or errors with it.

3.) Using values is not “poor data storage”, values were literally designed so that, if convenient, developers wouldn’t need to use dictionaries. On top of that, they’re instances, meaning they have built-in connections and metamethods, such as :GetPropertyChangedSignal() and can be accessed easily from any place in the game. You cannot access dictionaries across scripts, unless you use BindableEvents and/or ModuleScripts. Even if you do use events/modules, it’s still overly-complicated compared to just using instances that are visible in the explorer.

2 Likes

1.) Don’t know what you mean by “Deep Data”.

Nested tables and dictionaries, mainly. Deep data is a data structure that is more than one “layer” deep (mainly nested tables/dictionaries in Lua); in other words, tables that can’t be fully copied with table.copy().

local deepTable = {
  ["Hello"] = {
    ["World"] = "This is a",
    ["ROBLOX"] = "deep table!",
    ["1000"] = "It is a dictionary nested within a dictionary."
  }
}

Such data structures are fairly challenging (but not impossible) to replicate using ValueObjects and can lead to poor design choices that promote lag and instability.

2.) Many games—if not the majority on roblox—don’t need astronomically high numbers stored for players, with the exception of simulators, but even then, many simulators use regular leaderstats, and I’ve personally never seen any glitches or errors with it.

This is true. Again, like any other data structure that involves communicating across the client and the server, good development practices make leaderstats safe. For the games that demand larger values, ValueObjects simply won’t cut it in terms of data storage.

3.) Using values is not “poor data storage”, values were literally designed so that, if convenient, developers wouldn’t need to use dictionaries. On top of that, they’re instances , meaning they have built-in connections and metamethods, such as :GetPropertyChangedSignal() and can be accessed easily from any place in the game. You cannot access dictionaries across scripts, unless you use BindableEvents and/or ModuleScripts. Even if you do use events/modules, it’s still overly-complicated compared to just using instances that are visible in the explorer.

I disgaree. ValueObjects do have some useful events but a BindableEvent can serve that purpose just as easily if you really need to check every time something changes. Dictionaries (imo) are much more convienent and readable; I’d rather look at a dictionary that a messy stack of abstactly-named folders and ValueObjects.

Not to mention relying on listening to a bunch of Events is going to make your script prone to memory leaks if not handled properly. Again, competent developers will account for this, but novices more than often won’t.

You cannot access dictionaries across scripts, unless you use BindableEvents and/or ModuleScripts.

I don’t see why this is an issue in the first place? You pretty much listed the only ways to read dictionaries across scripts, and you can send dictionaries through RemoteEvents and RemoteFunctions as arguments as well (there are limitations, see here). Any comptent developer using dictionaries for data handling will account for this, and the modularity will make it easier to handle in the future instead of having to juggle a whole lot of values.

I think this is a good thread that discusses this issue in a bit more detail. Give it a read!

I see the player Id portion but that’s where I’m stumped, how do I make it save players I’d without me having to put mine in. Idk if that makes any sense

You would have to connect a function to the game.Players.PlayerAdded event. Preferably, do this in a function that also loads existing player data from a data store to kill two birds with one stone: creating new player data and loading existing player data.

local function PlayerData.LoadData(Player)
  -- This is just an example, you need to wrap in a pcall and other important things
  local data = DataStore:GetAsync(Player.UserId)
  if not data then
    return table.copy(NewData)
    -- also maybe save new data to datastore?
  return data
end

If you meant saving data, I left that as an exercise to you – read about this part of the Data Stores guide on the developer documentation page.

1 Like

Actually, despite my earlier reply, I needed to hide some data from the client before. Before I thought of this, I stored it in a folder with value objects. The problem was the client could see their score on the quiz and cheat and pass it. What I did was I made a BindableFunction that can be called to retrieve such stats, which were parented to nil and were not replicated to the player.

This was mainly for compatibility with my older system. I’d recommend you use a table on the server instead.

1 Like