Website | Code Examples | Discord | Web Demo
Safe, low-latency and fast sandbox for the Godot game engine.
Godot Sandbox lets players run untrusted code safely. Write gameplay in SafeGDScript, a safe GDScript-dialect executed inside a memory-safe sandbox with limits and execution timeouts. Restricted programs cannot access the host beyond what you explicitly allow, making it safe to load mods, user-generated content and scripts from other players. All Godot platforms are supported.
-
Automatic (Recommended): Download the plugin from the official Godot Asset Store using the AssetLib tab in Godot by searching for Godot Sandbox.
-
Manual: Download the latest github release and move only the addons folder into your project addons folder.
SafeGDScript (.sgd) is the default language for sandboxed code. It is a safety-oriented GDScript-dialect with most of the same syntax, and some additions (like structs). Attach a .sgd file to any node the same way you would attach a .gd script:
VS Code users can install the SafeGDScript extension for syntax highlighting and basic editor support.
SafeGDScript supports reusable traits. A trait can contribute state, constants, enums, signals and concrete/static methods, while abstract methods state what the using class must provide:
uses Damageable
trait Damageable:
var health: int = 100
func take_damage(amount: int) -> void:
health -= amount
@abstract func on_death() -> void
func on_death() -> void:
queue_free()Traits are nominal for SafeGDScript classes (value is Damageable) and may be
used as type hints. Foreign Godot objects can satisfy a trait structurally by
providing every instance method declared by it. Disable that compatibility path
with sandbox/safe_gdscript/trait_structural_fallback for strictly nominal matching.
struct Item:
var name: String
var value: int
var dropped_at: Vector2?
func try_stack(other: Item) -> bool:
if self.name != other.name:
return false
self.value += other.value
return true
func drop(at: int | Vector2) -> void:
if at is int:
self.dropped_at = Vector2(at, 0)
else:
self.dropped_at = at
var inventory: Array[Item] = []
func add_item(item_name: String, item_value: int) -> bool:
var item := Item(item_name, item_value)
for stored in inventory:
if stored.try_stack(item):
return true
inventory.append(item)
return false
func _ready():
add_item("coin", 1)
add_item("gem", 5)
add_item("coin", 1)
inventory[0].drop(Vector2(64, 32))SafeGDScript also supports await:
func cutscene(player_knocked : Signal) -> String:
$Gate.text = "The gate is sealed."
await get_tree().create_timer(0.8).timeout
await player_knocked
return "opened"The host receives a Signal and awaits it like any other coroutine: var result = await $Director.cutscene(knocked). See examples/async for a complete example.
A restricted sandbox denies all host access by default: methods, properties, classes and resource loading. Gamedevs decide what a mod can reach by passing an explicit API:
var api : Dictionary = {}
func mod_init(granted : Dictionary) -> void:
api = granted
api["log"].call("hello from the mod")
func _physics_process(delta):
api["report"].call("ticks", 1)See examples/modding for a complete mod loader with a hostile-mod audit.
For maximum performance, you can also write sandboxed programs in C++ or Rust. They use the same sandbox and the same restrictions. See the code examples repository and the demo repository.
- Sandboxed C++ is 2.5-10x faster than GDScript by default, 5-50x with binary translation
- Enable full binary translation for maximum performance on all platforms (including locked down iOS, Web, Switch etc.)
- JIT builds are available in the Releases section for Windows, macOS, Android and Linux
Using typed variables in SafeGDScript will help the compiler optimize:
--- logic CPU dispatch ---
case ns/emulated instr vs ref vs base
SafeGDScript (sandbox) 115.4 5.36x -4.1%
GDScript (engine) 618.0 1.00x -7.2%
In the modding example that implements a virtual CPU, we gained 5x over GDScript by using typed variables.
-
Assign an ELF script resource directly to a node. Constructs a shared sandbox among all instances with that script, maximum scalability, call functions and attach signals like GDScript
-
Or, create a Sandbox node and assign the ELF resource to it. One sandbox per node, with auto-completion from other GDScripts using @export
In order to build as a module, add it to a godot repo:
git submodule add https://github.com/libriscv/godot-sandbox modules/sandbox
cd modules/sandbox
git submodule update --init --recursive
The SafeGDScript language has been created with LLM assistance. It can be turned off by building with the ENABLE_SAFEGDSCRIPT option disabled in CMake like so:
cmake -S . -B .build -DENABLE_SAFEGDSCRIPT=OFFRequirements:
If you want to contribute to this repo, here are steps on how to build locally:
./build.shYou can also use scons similar to how godot-cpp addons are built.
Thanks goes to these wonderful people (emoji key):
Alf-André Walla 💻 |
K. S. Ernest (iFire) Lee 💻 🔬 |
Dragos Daian 💻 |
This project follows the all-contributors specification. Contributions of any kind welcome!
The Jenova Framework has a built-in C++ compiler, and supports writing C++ in the Godot editor with hot-reloading support.
