Skip to main content

InstanceSandboxer

A class for wrapping and unwrapping Instances and RBXScriptSignals.

Types

AnyFn

type AnyFn = (...any) → ...any

Function type that all functions can be reduced to.

MetamethodHook

type MetamethodHook = (
instany,
fn(
...any
) → ...any,
...any
) → ...any

Type for metamethod hook functions. First argument is the original metamethod function that can be called. This function expects an unwrapped Instance as its first argument.

Second argument is the wrapped Instance. Remaining arguments are the arguments passed to the metamethod at invocation.

Properties

Instances

This item is read only and cannot be modified. Read Only
InstanceSandboxer.Instances: {[Instance]true}

A set of all Instances that have been accessed in the sandbox, including those that were created by the sandbox and those that were not. This is used for tracking all Instances that may have been modified by sandboxed code, so that they can be cleaned up or handled differently if necessary.

When Config.TrackInstances is enabled, any Instance that is accessed in the sandbox will be added here. Keep in mind newly-created Instances will be added here as well, so this will also include all Instances in InstanceSandboxer.NewInstances. New Instances that were created and were found to be forbidden via InstanceList.ForbiddenClasses will not be added to this set.

To iterate through these Instances and clear them, you can do something like this:

for inst in InstanceSandboxer.Instances do
	-- Your cleanup code here, such as:
	inst:Destroy()
end

-- Instances get GC'd once destroyed, so this 
-- is sufficient for cleanup in most cases, 
-- but you can also do:
table.clear(InstanceSandboxer.Instances)

NewInstances

This item is read only and cannot be modified. Read Only
InstanceSandboxer.NewInstances: {[Instance]true}

A set of Instances that have been created in the sandbox. This is used for tracking Instances that were created by sandboxed code, so that they can be cleaned up or handled differently if necessary.

When Config.TrackInstances is enabled, any Instance that is created will be added here. Keep in mind these will also be added to InstanceSandboxer.Instances, since they are also accessed in the sandbox.

To iterate through these Instances and clear them, you can do something like this:

for inst in InstanceSandboxer.NewInstances do
	-- Your cleanup code here, such as:
	inst:Destroy()
end

-- Instances get GC'd once destroyed, so this 
-- is sufficient for cleanup in most cases, 
-- but you can also do:
table.clear(InstanceSandboxer.NewInstances)

RBXScriptConnections

This item is read only and cannot be modified. Read Only
InstanceSandboxer.RBXScriptConnections: {[RBXScriptConnection]true}

A set of RBXScriptConnections that have been created by the sandbox. This is used for tracking connections so that they can be cleaned up when necessary. This will only be populated if and only if Config.TrackRBXScriptConnections is enabled.

To iterate through these connections and clear them, you can do something like this:

for conn in InstanceSandboxer.RBXScriptConnections do
	conn:Disconnect()
end

-- RBXScriptConnections get GC'd once disconnected,
-- so this is sufficient for cleanup in most cases, 
-- but you can also do:
table.clear(InstanceSandboxer.RBXScriptConnections)

Functions

deepWrap

InstanceSandboxer.deepWrap(
vany,--

The value to deeply wrap.

instanceInstance?,--

The Instance if wrapping one of its properties, methods, or events.

keystring?,--

The key of the property, method, or event being wrapped, if applicable.

freezeboolean?,
pvisited{[any]any}?
) → any

Deeply wraps v if it is a table, wrapping Instances and RBXScriptSignals as needed. If v is an Instance or RBXScriptSignal, it will be wrapped and returned.

deepUnwrap

InstanceSandboxer.deepUnwrap(
vany,--

The value to deeply unwrap.

freezeboolean?,--

Whether to freeze tables.

pvisited{[any]any}?
) → any

Deeply unwraps v if it is a table, unwrapping Instances and RBXScriptSignals as needed. If v is a wrapped Instance or RBXScriptSignal, it will be unwrapped and returned.

unwrapArgs

InstanceSandboxer.unwrapArgs(...any) → ...any

Deeply unwraps all arguments passed to the function (usually from inside the sandbox), making them ready to be used in a method call on an Instance.

wrapArgs

InstanceSandboxer.wrapArgs(...any) → ...any

Deeply wraps all arguments passed to the function, making them ready to be returned to the sandboxed environment.

wrapFn

InstanceSandboxer.wrapFn(
fnAnyFn,--

The function to wrap.

passthroughInboolean,--

Whether to passthrough arguments untouched.

passthroughOutboolean--

Whether to passthrough return values untouched.

) → AnyFn--

The wrapped function.

Wraps a function in a proxy that allows it to be safely used in the sandbox. The wrapped function will return wrapped arguments when called.

reverseWrapFn

InstanceSandboxer.reverseWrapFn(
fnAnyFn--

The function to wrap.

) → AnyFn--

The wrapped function.

Wraps a function in a proxy that allows it to be safely used outside the sandbox. This is mainly used for callbacks being added for things like BindableFunctions.

wrapEvent

InstanceSandboxer.wrapEvent(
signalRBXScriptSignal,--

The RBXScriptSignal to wrap.

namestring--

The name of the signal, used for debugging purposes and printing out to console.

) → any--

The wrapped RBXScriptSignal.

Wraps an RBXScriptSignal in a proxy that allows it to be safely used in the sandbox. The proxy will have the same methods as an RBXScriptSignal, but will return wrapped arguments when the signal is fired.

wrapInstance

InstanceSandboxer.wrapInstance(
instanceInstance,--

The Instance to wrap.

fromInstanceConstructorboolean?
) → any--

The wrapped Instance.

Wraps an Instance in a proxy that allows it to be safely used in the sandbox. The proxy will have the same properties, methods, and events as the Instance, but will return wrapped arguments when accessed.

hookMetamethod

InstanceSandboxer.hookMetamethod(
instInstance,--

Unwrapped Instance to hook.

nameMetamethod,--

Metamethod name (with or without __ prefix) to hook.

hookMetamethodHook?--

Callback invoked when the metamethod fires.

) → ()

Hooks a specific metamethod on a wrapped Instance. Allows for hooking functions on Instances, modifying behaviors, or changing properties within sandboxed code.

Safety Guarantees

  • self received by the hook is always the wrapped proxy; it is never auto-unwrapped.
  • Every additional argument reaches the hook untouched; no wrapping, unwrapping, or copying occurs.
  • Return values flow straight back to the sandbox exactly as yielded by the hook; no automatic wrapping occurs.
  • The hook therefore must explicitly call InstanceSandboxer.unwrap, deepUnwrap, wrapInstance, deepWrap, or wrapArgs whenever data crosses the sandbox boundary. It is the caller's responsibility to ensure that this occurs correctly.

Behavior

  • The hook completely replaces the default handling of the selected metamethod for inst.
    • The option to revert to conditionally default behavior is available by calling the first argument, which is the original metamethod function. This function expects an unwrapped Instance as its first argument, followed by any additional arguments passed to the metamethod. It is highly recommended to passthrough arguments untouched to avoid unexpected behavior.
  • If the hook does not call the original metamethod function, the default behavior is skipped.
  • Hooks do not chain; setting a hook for a metamethod that already has one overwrites the previous hook.
  • Hooks are per-instance, not inherited.

unwrap

InstanceSandboxer.unwrap(
aany--

The wrapped Instance or RBXScriptSignal to unwrap.

) → (Instance | RBXScriptSignal)?

Unwraps a wrapped Instance or RBXScriptSignal. If the Instance or RBXScriptSignal is not wrapped, it will return nil.

isWrapped

InstanceSandboxer.isWrapped(aany) → boolean

Checks if a is a wrapped Instance.

isWrappedSignal

InstanceSandboxer.isWrappedSignal(aany) → boolean

Checks if a is a wrapped RBXScriptSignal.

Show raw api
{
    "functions": [
        {
            "name": "deepWrap",
            "desc": "Deeply wraps `v` if it is a table, wrapping `Instance`s and `RBXScriptSignal`s as needed.\nIf `v` is an `Instance` or `RBXScriptSignal`, it will be wrapped and returned.",
            "params": [
                {
                    "name": "v",
                    "desc": "The value to deeply wrap.",
                    "lua_type": "any"
                },
                {
                    "name": "instance",
                    "desc": "The `Instance` if wrapping one of its properties, methods, or events.",
                    "lua_type": "Instance?"
                },
                {
                    "name": "key",
                    "desc": "The key of the property, method, or event being wrapped, if applicable.",
                    "lua_type": "string?"
                },
                {
                    "name": "freeze",
                    "desc": "",
                    "lua_type": "boolean?"
                },
                {
                    "name": "pvisited",
                    "desc": "",
                    "lua_type": "{ [any]: any }?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "any\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 191,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "deepUnwrap",
            "desc": "Deeply unwraps `v` if it is a table, unwrapping `Instance`s and `RBXScriptSignal`s as needed.\nIf `v` is a wrapped `Instance` or `RBXScriptSignal`, it will be unwrapped and returned.",
            "params": [
                {
                    "name": "v",
                    "desc": "The value to deeply unwrap.",
                    "lua_type": "any"
                },
                {
                    "name": "freeze",
                    "desc": "Whether to freeze tables.",
                    "lua_type": "boolean?"
                },
                {
                    "name": "pvisited",
                    "desc": "",
                    "lua_type": "{ [any]: any }?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "any\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 254,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "unwrapArgs",
            "desc": "Deeply unwraps all arguments passed to the function (usually\nfrom inside the sandbox), making them ready to be used in a\nmethod call on an `Instance`.",
            "params": [
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "...any\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 309,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapArgs",
            "desc": "Deeply wraps all arguments passed to the function, making\nthem ready to be returned to the sandboxed environment.",
            "params": [
                {
                    "name": "...",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "...any\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 327,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapFn",
            "desc": "Wraps a function in a proxy that allows it to be safely used in the sandbox.\nThe wrapped function will return wrapped arguments when called.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to wrap.",
                    "lua_type": "AnyFn"
                },
                {
                    "name": "passthroughIn",
                    "desc": "Whether to passthrough arguments untouched.",
                    "lua_type": "boolean"
                },
                {
                    "name": "passthroughOut",
                    "desc": "Whether to passthrough return values untouched.",
                    "lua_type": "boolean"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped function.",
                    "lua_type": "AnyFn"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 350,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "reverseWrapFn",
            "desc": "Wraps a function in a proxy that allows it to be safely used outside the sandbox.\nThis is mainly used for callbacks being added for things like BindableFunctions.",
            "params": [
                {
                    "name": "fn",
                    "desc": "The function to wrap.",
                    "lua_type": "AnyFn"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped function.",
                    "lua_type": "AnyFn"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 386,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapEvent",
            "desc": "Wraps an `RBXScriptSignal` in a proxy that allows it to be safely used in \nthe sandbox. The proxy will have the same methods as an `RBXScriptSignal`, \nbut will return wrapped arguments when the signal is fired.",
            "params": [
                {
                    "name": "signal",
                    "desc": "The `RBXScriptSignal` to wrap.",
                    "lua_type": "RBXScriptSignal"
                },
                {
                    "name": "name",
                    "desc": "The name of the signal, used for debugging purposes and printing out to console.",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped `RBXScriptSignal`.",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 684,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "wrapInstance",
            "desc": "Wraps an `Instance` in a proxy that allows it to be safely used in the sandbox.\nThe proxy will have the same properties, methods, and events as the `Instance`, \nbut will return wrapped arguments when accessed.",
            "params": [
                {
                    "name": "instance",
                    "desc": "The `Instance` to wrap.",
                    "lua_type": "Instance"
                },
                {
                    "name": "fromInstanceConstructor",
                    "desc": "",
                    "lua_type": "boolean?"
                }
            ],
            "returns": [
                {
                    "desc": "The wrapped `Instance`.",
                    "lua_type": "any"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 723,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "hookMetamethod",
            "desc": "Hooks a specific metamethod on a wrapped `Instance`. Allows for hooking functions\non Instances, modifying behaviors, or changing properties within sandboxed code.\n\n### Safety Guarantees\n- `self` received by the hook is always the wrapped proxy; it is never auto-unwrapped.\n- Every additional argument reaches the hook untouched; no wrapping, unwrapping, or copying occurs.\n- Return values flow straight back to the sandbox exactly as yielded by the hook; no automatic wrapping occurs.\n- The hook therefore must explicitly call `InstanceSandboxer.unwrap`, `deepUnwrap`, `wrapInstance`,\n  `deepWrap`, or `wrapArgs` whenever data crosses the sandbox boundary. *It is the caller's responsibility to\n  ensure that this occurs correctly.*\n\n### Behavior\n- The hook completely replaces the default handling of the selected metamethod for `inst`.\n  - The option to revert to conditionally default behavior is available by calling the first argument,\n    which is the original metamethod function. This function expects an unwrapped `Instance` as its first argument,\n\tfollowed by any additional arguments passed to the metamethod. *It is highly recommended to passthrough arguments\n\tuntouched to avoid unexpected behavior.*\n- If the hook does not call the original metamethod function, the default behavior is skipped.\n- Hooks do not chain; setting a hook for a metamethod that already has one overwrites the previous hook.\n- Hooks are per-instance, not inherited.",
            "params": [
                {
                    "name": "inst",
                    "desc": "**Unwrapped** Instance to hook.",
                    "lua_type": "Instance"
                },
                {
                    "name": "name",
                    "desc": "Metamethod name (with or without `__` prefix) to hook.",
                    "lua_type": "Metamethod"
                },
                {
                    "name": "hook",
                    "desc": "Callback invoked when the metamethod fires.",
                    "lua_type": "MetamethodHook?"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 900,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "unwrap",
            "desc": "Unwraps a wrapped `Instance` or `RBXScriptSignal`.\nIf the `Instance` or `RBXScriptSignal` is not wrapped, it will return `nil`.",
            "params": [
                {
                    "name": "a",
                    "desc": "The wrapped `Instance` or `RBXScriptSignal` to unwrap.",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "(Instance | RBXScriptSignal)?\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 179,
                "path": "src/InstanceList.luau"
            }
        },
        {
            "name": "isWrapped",
            "desc": "Checks if `a` is a wrapped `Instance`.",
            "params": [
                {
                    "name": "a",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 189,
                "path": "src/InstanceList.luau"
            }
        },
        {
            "name": "isWrappedSignal",
            "desc": "Checks if `a` is a wrapped `RBXScriptSignal`.",
            "params": [
                {
                    "name": "a",
                    "desc": "",
                    "lua_type": "any"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "boolean\n"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 200,
                "path": "src/InstanceList.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "Instances",
            "desc": "A set of all `Instance`s that have been accessed in the sandbox, \nincluding those that were created by the sandbox and those that were not. \nThis is used for tracking all `Instance`s that may have been modified by \nsandboxed code, so that they can be cleaned up or handled differently if necessary.\n\nWhen `Config.TrackInstances` is enabled, any `Instance` that is accessed \nin the sandbox will be added here. Keep in mind newly-created `Instance`s \nwill be added here as well, so this will also include all `Instance`s in \n`InstanceSandboxer.NewInstances`. New `Instance`s that were created and\nwere found to be forbidden via `InstanceList.ForbiddenClasses` will not \nbe added to this set.\n\nTo iterate through these `Instance`s and clear them, you can do something like this:\n\n```lua\nfor inst in InstanceSandboxer.Instances do\n\t-- Your cleanup code here, such as:\n\tinst:Destroy()\nend\n\n-- Instances get GC'd once destroyed, so this \n-- is sufficient for cleanup in most cases, \n-- but you can also do:\ntable.clear(InstanceSandboxer.Instances)\n```",
            "lua_type": "{ [Instance]: true }",
            "readonly": true,
            "source": {
                "line": 70,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "NewInstances",
            "desc": "A set of `Instance`s that have been created in the sandbox.\nThis is used for tracking Instances that were created by \nsandboxed code, so that they can be cleaned up or handled \ndifferently if necessary.\n\nWhen `Config.TrackInstances` is enabled, any `Instance` that is created\nwill be added here. Keep in mind these will **also** be added to \n`InstanceSandboxer.Instances`, since they are also accessed in the sandbox.\n\nTo iterate through these `Instance`s and clear them, you can do something like this:\n\n```lua\nfor inst in InstanceSandboxer.NewInstances do\n\t-- Your cleanup code here, such as:\n\tinst:Destroy()\nend\n\n-- Instances get GC'd once destroyed, so this \n-- is sufficient for cleanup in most cases, \n-- but you can also do:\ntable.clear(InstanceSandboxer.NewInstances)\n```",
            "lua_type": "{ [Instance]: true }",
            "readonly": true,
            "source": {
                "line": 100,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "RBXScriptConnections",
            "desc": "A set of `RBXScriptConnection`s that have been created by the sandbox. \nThis is used for tracking connections so that they can be cleaned up\nwhen necessary. This will only be populated if and only if\n`Config.TrackRBXScriptConnections` is enabled.\n\nTo iterate through these connections and clear them, you can do something like this:\n\n```lua\nfor conn in InstanceSandboxer.RBXScriptConnections do\n\tconn:Disconnect()\nend\n\n-- RBXScriptConnections get GC'd once disconnected,\n-- so this is sufficient for cleanup in most cases, \n-- but you can also do:\ntable.clear(InstanceSandboxer.RBXScriptConnections)\n```",
            "lua_type": "{ [RBXScriptConnection]: true }",
            "readonly": true,
            "source": {
                "line": 135,
                "path": "src/InstanceSandboxer.luau"
            }
        }
    ],
    "types": [
        {
            "name": "AnyFn",
            "desc": "Function type that all functions can be reduced to.",
            "lua_type": "(...any) -> ...any",
            "source": {
                "line": 174,
                "path": "src/InstanceSandboxer.luau"
            }
        },
        {
            "name": "MetamethodHook",
            "desc": "Type for metamethod hook functions.\nFirst argument is the original metamethod function that can be called. \n**This function expects an unwrapped `Instance` as its first argument.**\n\nSecond argument is the wrapped `Instance`.\nRemaining arguments are the arguments passed to the metamethod at invocation.",
            "lua_type": "(inst: any, fn: (Instance, ...any) -> ...any, ...any) -> ...any",
            "source": {
                "line": 411,
                "path": "src/InstanceSandboxer.luau"
            }
        }
    ],
    "name": "InstanceSandboxer",
    "desc": "A class for wrapping and unwrapping `Instance`s and `RBXScriptSignal`s.",
    "source": {
        "line": 32,
        "path": "src/InstanceSandboxer.luau"
    }
}