From 9450fb86d017294e3413dae5b84deede059c6ce9 Mon Sep 17 00:00:00 2001 From: architect-bot Date: Mon, 17 Aug 2026 20:36:34 +0000 Subject: [PATCH] WIP checkpoint: Research and design GDScript sandboxing approach --- sandbox_probe/probe3.gd | 175 +++++++++++++++++++++----------------- sandbox_probe/probe3.tscn | 6 ++ 2 files changed, 101 insertions(+), 80 deletions(-) create mode 100644 sandbox_probe/probe3.tscn diff --git a/sandbox_probe/probe3.gd b/sandbox_probe/probe3.gd index 1cb914a..9268945 100644 --- a/sandbox_probe/probe3.gd +++ b/sandbox_probe/probe3.gd @@ -2,91 +2,83 @@ extends Node func _ready(): print("=== SANDBOX PROBE 3: Deep Sandboxing Mechanisms ===") - probe_script_restriction_apis() - probe_global_scope() - probe_classdb_restrictions() + probe_global_scope_restriction() + probe_classdb_restriction() probe_engine_singletons() - probe_network_restrictions() - probe_threading() - probe_expression_eval() - probe_resource_loading() + probe_script_validation() + probe_expression_engine() + probe_worker_threads() + probe_os_restriction_flags() print("=== SANDBOX PROBE 3 END ===") get_tree().quit() -func probe_script_restriction_apis(): - print("--- Script restriction APIs ---") - # Does GDScript expose any way to restrict globals? - var gd = GDScript.new() - print(" GDScript methods: ", gd.get_method_list().map(func(m): return m.name)) - # Check for any 'restrict' or 'allow' methods on the language - var lang = GDScriptLanguage.new() - print(" GDScriptLanguage methods: ", lang.get_method_list().map(func(m): return m.name)) - # Check ScriptServer via Engine - print(" Engine.get_singleton_list contains ScriptServer: ", "ScriptServer" in Engine.get_singleton_list()) - -func probe_global_scope(): - print("--- Global scope ---") - # What globals are available to a script? - # Test if we can enumerate global classes +func probe_global_scope_restriction(): + print("--- Global scope restriction ---") + # Can we enumerate what's in the global scope? var globals = [] - for g in GlobalScope.get_singleton_list(): + for g in GDScript.get_global_class_list(): globals.append(g) - print(" GlobalScope singletons: ", globals) - # Can we access ClassDB to enumerate classes? + print(" GDScript.get_global_class_list() count: ", globals.size()) + # Can we check if a global is available before using it? + print(" GDScript has 'get_global_class_list': ", GDScript.has_method("get_global_class_list")) + # Is there any way to restrict which globals a script sees? + print(" GDScript has 'set_global_scope': ", GDScript.has_method("set_global_scope")) + print(" GDScript has 'restrict_globals': ", GDScript.has_method("restrict_globals")) + # Check the GDScript class methods + var methods = GDScript.get_method_list() + var method_names = [] + for m in methods: + method_names.append(m.name) + print(" GDScript methods: ", method_names) + +func probe_classdb_restriction(): + print("--- ClassDB restriction ---") + # ClassDB is the registry of all engine classes. Can we restrict it? + print(" ClassDB has 'get_class_list': ", ClassDB.has_method("get_class_list")) + print(" ClassDB has 'class_exists': ", ClassDB.has_method("class_exists")) + # Can we check if a specific class is available? print(" ClassDB.class_exists('OS'): ", ClassDB.class_exists("OS")) print(" ClassDB.class_exists('FileAccess'): ", ClassDB.class_exists("FileAccess")) print(" ClassDB.class_exists('TCPServer'): ", ClassDB.class_exists("TCPServer")) - -func probe_classdb_restrictions(): - print("--- ClassDB restrictions ---") - # Can we instantiate arbitrary classes? - var os = ClassDB.instantiate("OS") - print(" ClassDB.instantiate('OS') = ", os) - # Can we call static methods via ClassDB? - print(" ClassDB has method 'class_get_method_list': ", ClassDB.has_method("class_get_method_list")) - # Can we access the full class list? - var class_list = ClassDB.get_class_list() - print(" ClassDB.get_class_list() count: ", class_list.size()) - print(" First 10 classes: ", class_list.slice(0, 10)) + # Is there a way to hide classes from scripts? + print(" ClassDB has 'set_class_enabled': ", ClassDB.has_method("set_class_enabled")) + print(" ClassDB has 'disable_class': ", ClassDB.has_method("disable_class")) func probe_engine_singletons(): print("--- Engine singletons ---") - # What singletons are available? - var singletons = Engine.get_singleton_list() - print(" Engine singletons: ", singletons) - # Can we get a singleton by name? - for s in ["OS", "Engine", "ProjectSettings", "ResourceLoader", "ClassDB", "Input", "DisplayServer"]: - print(" Engine.has_singleton('", s, "'): ", Engine.has_singleton(s)) + # Engine singletons are the global objects like OS, Engine, etc. + # Can we enumerate them? + var singletons = Engine.get_singletons() + print(" Engine.get_singletons() count: ", singletons.size()) + for s in singletons: + print(" singleton: ", s) + # Can we remove a singleton? + print(" Engine has 'remove_singleton': ", Engine.has_method("remove_singleton")) + print(" Engine has 'add_singleton': ", Engine.has_method("add_singleton")) -func probe_network_restrictions(): - print("--- Network restrictions ---") - # Can we make outbound HTTP requests? - var http = HTTPRequest.new() - print(" HTTPRequest.new() = ", http) - # Can we create a WebSocket client? - var ws = WebSocketPeer.new() - print(" WebSocketPeer.new() = ", ws) - # Can we access the network via PacketPeer? - var pp = PacketPeerUDP.new() - print(" PacketPeerUDP.new() = ", pp) +func probe_script_validation(): + print("--- Script validation ---") + # Can we validate a script before running it? + var gd = GDScript.new() + gd.source_code = "extends RefCounted\nfunc evil():\n\treturn OS.get_environment('HOME')\n" + var err = gd.reload() + print(" GDScript.reload() err=", err) + # Can we get the list of identifiers used in a script? + print(" GDScript has 'get_script_method_list': ", gd.has_method("get_script_method_list")) + # Can we get the source code back? + print(" GDScript has 'get_source_code': ", gd.has_method("get_source_code")) + # Can we check what identifiers a script references? + print(" GDScript has 'get_script_constant_map': ", gd.has_method("get_script_constant_map")) + # Check if there's a way to get the AST + print(" GDScript has 'get_ast': ", gd.has_method("get_ast")) + print(" GDScript has 'get_parser': ", gd.has_method("get_parser")) -func probe_threading(): - print("--- Threading ---") - # Can we spawn threads? - var thread = Thread.new() - print(" Thread.new() = ", thread) - # Can we use mutexes / semaphores? - var mutex = Mutex.new() - print(" Mutex.new() = ", mutex) - var sem = Semaphore.new() - print(" Semaphore.new() = ", sem) - -func probe_expression_eval(): - print("--- Expression evaluation ---") - # Godot's Expression class - can it evaluate arbitrary code? +func probe_expression_engine(): + print("--- Expression engine ---") + # Godot has an Expression class for evaluating expressions safely var expr = Expression.new() var err = expr.parse("1 + 2") - print(" Expression.parse('1+2') err=", err) + print(" Expression.parse('1 + 2') err=", err) if err == OK: var result = expr.execute() print(" Expression.execute() = ", result) @@ -97,15 +89,38 @@ func probe_expression_eval(): if err == OK: var result2 = expr2.execute() print(" Expression.execute() = ", result2) + # Expression with base_instance + var expr3 = Expression.new() + err = expr3.parse("get_environment('HOME')", ["OS"]) + print(" Expression.parse with base_instance err=", err) + if err == OK: + var result3 = expr3.execute([], OS) + print(" Expression.execute with OS base = ", result3) -func probe_resource_loading(): - print("--- Resource loading ---") - # Can we load arbitrary resources? - var res = load("res://test_script.gd") - print(" load('res://test_script.gd') = ", res) - # Can we load from absolute paths? - var res2 = load("/etc/hostname") - print(" load('/etc/hostname') = ", res2) - # Can we use ResourceLoader directly? - var rl = ResourceLoader - print(" ResourceLoader.load('res://test_script.gd') = ", rl.load("res://test_script.gd")) +func probe_worker_threads(): + print("--- Worker threads ---") + # Can we run scripts in worker threads? + print(" Thread has 'new': ", Thread.has_method("new")) + # WorkerThreadPool + print(" WorkerThreadPool has 'add_task': ", WorkerThreadPool.has_method("add_task")) + # Can we isolate a script in a thread? + print(" Thread available: ", Thread != null) + +func probe_os_restriction_flags(): + print("--- OS restriction flags ---") + # Check if there are any OS-level sandbox flags + print(" OS has 'get_environment': ", OS.has_method("get_environment")) + print(" OS has 'execute': ", OS.has_method("execute")) + print(" OS has 'create_process': ", OS.has_method("create_process")) + # Check project settings for sandbox-related options + var settings = ProjectSettings.get_property_list() + var relevant = [] + for s in settings: + var name = String(s.name) + if "sandbox" in name.to_lower() or "restrict" in name.to_lower() or "security" in name.to_lower(): + relevant.append(name) + print(" Sandbox/security project settings: ", relevant) + # Check if there's a way to disable specific features + print(" ProjectSettings has 'application/run/disable_os': ", ProjectSettings.has_setting("application/run/disable_os")) + print(" ProjectSettings has 'application/run/disable_file_access': ", ProjectSettings.has_setting("application/run/disable_file_access")) + print(" ProjectSettings has 'application/run/disable_network': ", ProjectSettings.has_setting("application/run/disable_network")) diff --git a/sandbox_probe/probe3.tscn b/sandbox_probe/probe3.tscn new file mode 100644 index 0000000..5b8c302 --- /dev/null +++ b/sandbox_probe/probe3.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://probe3.gd" id="1"] + +[node name="Probe3" type="Node"] +script = ExtResource("1")