From a1846f34b5614859f5fc596eb5749e3786a4f92a Mon Sep 17 00:00:00 2001 From: architect-bot Date: Mon, 17 Aug 2026 20:33:56 +0000 Subject: [PATCH] WIP checkpoint: Research and design GDScript sandboxing approach --- sandbox_probe/probe2.gd | 125 +++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/sandbox_probe/probe2.gd b/sandbox_probe/probe2.gd index fc585eb..b382c6b 100644 --- a/sandbox_probe/probe2.gd +++ b/sandbox_probe/probe2.gd @@ -1,78 +1,83 @@ extends Node func _ready(): - print("=== SANDBOX PROBE 2: ScriptServer & Restrictions ===") + print("=== SANDBOX PROBE 2: Sandboxing Mechanisms ===") probe_scriptserver() probe_script_restrictions() + probe_subviewport_isolation() probe_engine_restrictions() - probe_global_scope() - probe_classdb() - probe_script_extension() + probe_allowlist_approach() print("=== SANDBOX PROBE 2 END ===") get_tree().quit() func probe_scriptserver(): print("--- ScriptServer ---") - # Is ScriptServer available? It's a static class in Godot 4. - print("ScriptServer exists: ", ClassDB.class_exists("ScriptServer")) - if ClassDB.class_exists("ScriptServer"): - # What methods does it expose? - var methods = ClassDB.class_get_method_list("ScriptServer") - for m in methods: - print(" ScriptServer method: ", m.name, " args=", m.args.size()) + # Does ScriptServer exist and what can it do? + print("ScriptServer has_method('get_language_count'): ", ScriptServer.has_method("get_language_count")) + print("ScriptServer has_method('get_language'): ", ScriptServer.has_method("get_language")) + # Can we enumerate registered script languages? + if ScriptServer.has_method("get_language_count"): + var count = ScriptServer.get_language_count() + print(" language count: ", count) + for i in range(count): + var lang = ScriptServer.get_language(i) + print(" language[", i, "] = ", lang) func probe_script_restrictions(): print("--- Script restrictions ---") - # Can we restrict script loading via ProjectSettings? - var keys = [ - "debug/gdscript/warnings/", - "debug/gdscript/", - "application/run/", - ] - for k in keys: - var settings = ProjectSettings.get_setting(k) - print(" ProjectSettings['", k, "'] = ", settings) + # Can we restrict which classes/globals a script can access? + # Test: does GDScript have any built-in allowlist mechanism? + var gd = GDScript.new() + gd.source_code = "extends RefCounted\nfunc evil():\n\treturn OS.get_environment('HOME')\n" + var err = gd.reload() + print(" GDScript with OS access reload err=", err) + if err == OK: + var inst = gd.new() + print(" evil() result: ", inst.call("evil")) + # Can we block specific global classes? + print(" GDScript has method to restrict globals: ", gd.has_method("set_global_scope")) + +func probe_subviewport_isolation(): + print("--- SubViewport isolation ---") + # SubViewports isolate rendering, but do they isolate script execution? + var vp = SubViewport.new() + print(" SubViewport.new() = ", vp) + # SubViewports share the same process/engine - scripts still have full access + print(" SubViewport does NOT isolate script execution (same process)") + vp.free() func probe_engine_restrictions(): print("--- Engine restrictions ---") - # Check if there's any sandbox/restriction setting - var all_settings = ProjectSettings.get_property_list() - var sandbox_like = [] - for s in all_settings: - var name = String(s.name) - if "sandbox" in name.to_lower() or "restrict" in name.to_lower() or "security" in name.to_lower(): - sandbox_like.append(name) - print(" Sandbox/restrict/security settings found: ", sandbox_like) + # Is there a way to disable OS/File/Network access globally? + print(" Engine.has_singleton('OS'): ", Engine.has_singleton("OS")) + print(" Engine.has_singleton('FileAccess'): ", Engine.has_singleton("FileAccess")) + # Check if there's a '--disable-*' flag or project setting + print(" ProjectSettings has 'application/run/disable_*': ", ProjectSettings.has_setting("application/run/disable_os")) + # Check for any sandbox-related project settings + var settings = ProjectSettings.get_property_list() + var sandbox_settings = [] + for s in settings: + if "sandbox" in String(s.name).to_lower() or "restrict" in String(s.name).to_lower(): + sandbox_settings.append(s.name) + print(" Sandbox-related project settings: ", sandbox_settings) -func probe_global_scope(): - print("--- Global scope ---") - # What global classes/functions are available to scripts? - # Check if we can enumerate global scope - var global_classes = [] - if ClassDB.class_exists("GlobalScope"): - var methods = ClassDB.class_get_method_list("GlobalScope") - for m in methods: - global_classes.append(m.name) - print(" GlobalScope methods count: ", global_classes.size()) - # Key dangerous globals - var dangerous = ["OS", "FileAccess", "DirAccess", "TCPServer", "HTTPClient", "ProjectSettings", "Engine", "ResourceLoader", "ClassDB", "Expression", "JavaScriptBridge"] - for d in dangerous: - print(" Class '", d, "' exists: ", ClassDB.class_exists(d)) - -func probe_classdb(): - print("--- ClassDB ---") - # Can scripts access ClassDB to instantiate arbitrary classes? - print(" ClassDB.class_exists('Node') = ", ClassDB.class_exists("Node")) - # Can we get all classes? - var all_classes = ClassDB.get_class_list() - print(" ClassDB.get_class_list().size() = ", all_classes.size()) - -func probe_script_extension(): - print("--- Script extension / custom interpreter ---") - # Check if ScriptExtension exists (for custom script languages) - print(" ScriptExtension exists: ", ClassDB.class_exists("ScriptExtension")) - # Check GDScript internals - print(" GDScript class exists: ", ClassDB.class_exists("GDScript")) - var gd_methods = ClassDB.class_get_method_list("GDScript") - for m in gd_methods: - print(" GDScript method: ", m.name) +func probe_allowlist_approach(): + print("--- Allowlist approach ---") + # The allowlist approach: parse GDScript source and reject disallowed patterns + # Test: can we detect dangerous API calls via string analysis? + var dangerous_patterns = [ + "OS.execute", "OS.create_process", "OS.get_environment", + "FileAccess.open", "DirAccess.open", "TCPServer", "HTTPClient", + "load(", "preload(", "ClassDB", "Engine.get_main_loop", + "get_tree().quit", "ProjectSettings" + ] + var test_script = """ +extends Node +func _ready(): + var f = FileAccess.open("/etc/passwd", FileAccess.READ) + OS.execute("rm", ["-rf", "/"]) + """ + print(" Test script contains dangerous patterns:") + for p in dangerous_patterns: + if p in test_script: + print(" DETECTED: ", p)