Remote Objects Parented To Nil Never Fire

Repro: Remote Objects Need Parent Repro.rbxl (11.1 KB)

This is a bit of an interesting bug. I wanted to try to parent the RemoteEvents and RemoteFunctions in my game to nil to prevent people finding them and sending false requests to the server easily, or serialize them with a geometry stealing exploit. But… they don’t fire to the server if they aren’t parented.


I am not sure if this is a bug or intended to happen. I assume it is a bug, but I doubt this type of set-up is/will be supported.

2 Likes

This is because instances parented to nil don’t replicate. If you want the client to be able to communicate with the server but not risk exploiters fudging data, the only thing you can do are server-side sanity checks.

1 Like

As long as you can reference the RemoteEvents/Functions from LocalScripts you can set their parent to nil in the client as long as they aren’t parented to nil when you fire or invoke them.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RemoteEvent = ReplicatedStorage.RemoteEvent

local function foo(data)
	RemoteEvent.Parent = ReplicatedStorage
	RemoteEvent:FireServer(data)
	RemoteEvent.Parent = nil
end

RemoteEvent.Parent = nil

I’m not sure if there are exploits to retrieve stuff from nil but otherwise it doesn’t hurt to ‘cover up’ Remotes or LocalScripts.

7 Likes

I’ve run into a weird bug where the behavior is inconsistent between studio and live games. I made a network module that finds a remote event on the client and parents it to nil, and every time it fires the remote, it is parented it to the game the instant before.
This helps to prevent exploiters from accessing the remote since it only ever exists in the worldmodel for microseconds at a time. Exploiters can still access the reference of course through script injection, but this dissuades at least some of the bad actors out there.

RemoteReference.Parent = game
RemoteReference:FireServer()
RemoteReference.Parent = nil

This works 100% of the time in studio, I’m assuming due to the server and client both being run locally. However, it only works 9/10 times in a live game.

I was testing my game and on occasion, the server wouldn’t receive any signals from the client, and the game would break since the server doesn’t load the game until it receives that initial signal. I put print statements everywhere and it shows that the server sided callback is never being invoked.

I thought I was going crazy at first so I rapidly left and rejoined the game, and I managed to reproduce the bug like 3 times out of 30 joins. This bug has never occurred even once in studio, after at least 2000 test plays.

The implementations for remote events probably have a line like this:

function RemoteEvent:FireServer()
  if self.Parent == nil then return end
  ...
end

However, if that was the case, setting the parent to the game immediately before firing should 100% update the Parent property and allow it to fire. However, I suspect there is some internal “disabled” flag that gets affected when the Parent property changes.

self.AncestryChanged:Connect(function(c, p)
  if p == nil then self.disabled = true else self.disabled = false end
end)

Possibly then due to deferred events, the FireServer() call is being invoked while the “disabled” flag is still true, even though the parent has changed, since the AncestryChanged callback doesn’t run until AFTER FireServer().
However, I have events deferred on both studio and live games, so I honestly do not understand why there’s a difference in behavior. I’ve never experienced this a single time in studio, and its so hard to reproduce in a live server since sometimes it just works??? and sometimes it doesn’t??

UPDATE:
After some more digging I think I’ve found the culprit:
task scheduler remote event wait
When the remote events work, they always work. When they don’t, they never do. When the client FireServer(), if the call is made at the end of a frame, it immediately fires, then the parent is set back to Nil. The calling thread seems to be permanently staggered by some offset time to the firing due to the task scheduler cycle, so if the offset causes the actual firing to occur a frame too late, the parent of the remote has already been set back to nil, so the request is dropped. I’m guessing setting the .Parent isn’t synchronous either for some reason??? The ordering seems to determine whether or not the remote firing is dropped

1 Like