diff --git a/sandbox_probe/probe5.gd b/sandbox_probe/probe5.gd new file mode 100644 index 0000000..1242944 --- /dev/null +++ b/sandbox_probe/probe5.gd @@ -0,0 +1,301 @@ +extends Node + +# probe5.gd — Corrected, comprehensive sandboxing probe for Godot 4.6.2. +# Fixes the API-usage errors in earlier probes (static vs instance calls). + +func _ready(): + print("=== SANDBOX PROBE 5: Corrected Sandboxing Mechanisms ===") + probe_script_loading() + probe_os_apis() + probe_file_access() + probe_network() + probe_classdb() + probe_expression_engine() + probe_engine_singletons() + probe_global_scope() + probe_worker_threads() + probe_project_settings() + probe_scriptserver() + probe_script_restrictions() + probe_subviewport_isolation() + print("=== SANDBOX PROBE 5 END ===") + get_tree().quit() + +func probe_script_loading(): + print("--- Script loading ---") + # Can we load a GDScript from a string at runtime? + var gd = GDScript.new() + gd.source_code = "extends RefCounted\nfunc hello():\n\treturn 42\n" + var err = gd.reload() + print("GDScript.reload() from source_code err=", err) + if err == OK: + var inst = gd.new() + print(" instantiated, hello()=", inst.call("hello")) + # Can we load a script resource from a path? + var res = load("res://test_script.gd") + print("load('res://test_script.gd') = ", res) + # Can we get the source code back from a loaded script? + if res: + print(" get_source_code() available: ", res.has_method("get_source_code")) + # Can we get the AST / parse tree? + print(" GDScript instance has 'get_ast': ", gd.has_method("get_ast")) + print(" GDScript instance has 'get_parser': ", gd.has_method("get_parser")) + # Can we get the list of methods? + print(" GDScript instance has 'get_script_method_list': ", gd.has_method("get_script_method_list")) + # Can we get the list of member variables? + print(" GDScript instance has 'get_script_property_list': ", gd.has_method("get_script_property_list")) + # Can we get the script's base class? + print(" GDScript instance has 'get_instance_base_type': ", gd.has_method("get_instance_base_type")) + # Can we get the script's resource path? + print(" GDScript instance has 'get_path': ", gd.has_method("get_path")) + +func probe_os_apis(): + print("--- OS APIs ---") + # Can we execute shell commands? + var out = [] + var exit_code = OS.execute("echo", ["hello"], out) + print("OS.execute('echo') exit=", exit_code, " out=", out) + # Environment access + print("OS.get_environment('HOME') = ", OS.get_environment("HOME")) + # Process spawning + print("OS has 'create_process': ", OS.has_method("create_process")) + # Can we get the command line? + print("OS.get_cmdline_args() = ", OS.get_cmdline_args()) + # Can we get user data dir? + print("OS.get_user_data_dir() = ", OS.get_user_data_dir()) + # Can we get executable path? + print("OS.get_executable_path() = ", OS.get_executable_path()) + # Can we get the process ID? + print("OS has 'get_process_id': ", OS.has_method("get_process_id")) + # Can we kill processes? + print("OS has 'kill': ", OS.has_method("kill")) + +func probe_file_access(): + print("--- File access ---") + # Can we read arbitrary files? + var f = FileAccess.open("/etc/hostname", FileAccess.READ) + print("FileAccess.open('/etc/hostname') = ", f) + if f: + print(" content: ", f.get_as_text()) + f.close() + # Can we write outside the project? + var w = FileAccess.open("/tmp/godot_probe_write.txt", FileAccess.WRITE) + print("FileAccess.open('/tmp/...', WRITE) = ", w) + if w: + w.store_string("probe") + w.close() + print(" wrote to /tmp OK") + # Can we list directories? + var dir = DirAccess.open("/") + print("DirAccess.open('/') = ", dir) + if dir: + var names = dir.get_files() + print(" root files count: ", names.size()) + # Can we access res:// and user:// ? + var res_dir = DirAccess.open("res://") + print("DirAccess.open('res://') = ", res_dir) + var user_dir = DirAccess.open("user://") + print("DirAccess.open('user://') = ", user_dir) + # Can we use FileAccess.get_file_as_string? + print(" FileAccess has 'get_file_as_string': ", FileAccess.has_method("get_file_as_string")) + # Can we check if a file exists? + print(" FileAccess has 'file_exists': ", FileAccess.has_method("file_exists")) + +func probe_network(): + print("--- Network ---") + # Can we open raw sockets? + var tcp = TCPServer.new() + var bind_err = tcp.listen(0) + print("TCPServer.listen(0) err=", bind_err) + # HTTP client + var http = HTTPClient.new() + print("HTTPClient instantiated: ", http != null) + # Can we make HTTP requests? + var http_req = HTTPRequest.new() + print("HTTPRequest instantiated: ", http_req != null) + # PacketPeerUDP + var udp = PacketPeerUDP.new() + print("PacketPeerUDP instantiated: ", udp != null) + # WebSocket + var ws = WebSocketPeer.new() + print("WebSocketPeer instantiated: ", ws != null) + # Can we connect to a remote host? + var http2 = HTTPClient.new() + var connect_err = http2.connect_to_host("example.com", 80) + print("HTTPClient.connect_to_host('example.com', 80) err=", connect_err) + +func probe_classdb(): + print("--- ClassDB ---") + # ClassDB is the registry of all engine classes + 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")) + print("ClassDB.class_exists('HTTPClient'): ", ClassDB.class_exists("HTTPClient")) + # Can we enumerate all classes? + var class_list = ClassDB.get_class_list() + print("ClassDB.get_class_list() count: ", class_list.size()) + # Can we get methods of a class? + var os_methods = ClassDB.class_get_method_list("OS") + print("OS method count: ", os_methods.size()) + # Can we get properties of a class? + var os_props = ClassDB.class_get_property_list("OS") + print("OS property count: ", os_props.size()) + # Can we instantiate a class by name? + var inst = ClassDB.instantiate("Node") + print("ClassDB.instantiate('Node') = ", inst) + # Can we check if a class is a singleton? + print("ClassDB has 'class_get_singleton': ", ClassDB.has_method("class_get_singleton")) + # Can we get the singleton instance of OS? + var os_singleton = ClassDB.class_get_singleton("OS") + print("ClassDB.class_get_singleton('OS') = ", os_singleton) + # Can we check what methods are available on the singleton? + if os_singleton: + print(" OS singleton has 'execute': ", os_singleton.has_method("execute")) + print(" OS singleton has 'get_environment': ", os_singleton.has_method("get_environment")) + # Can we get the class list of a specific class? + print(" ClassDB has 'class_get_inheriters': ", ClassDB.has_method("class_get_inheriters")) + # Can we check if a class is an enum? + print(" ClassDB has 'class_get_enum_list': ", ClassDB.has_method("class_get_enum_list")) + +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) + if err == OK: + var result = expr.execute() + print(" Expression.execute() = ", result) + # Can Expression access OS? + var expr2 = Expression.new() + err = expr2.parse("OS.get_environment('HOME')") + print("Expression.parse('OS.get_environment') err=", err) + 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) + # Can Expression call arbitrary methods? + var expr4 = Expression.new() + err = expr4.parse("FileAccess.open('/etc/hostname', 1)") + print("Expression.parse('FileAccess.open') err=", err) + if err == OK: + var result4 = expr4.execute() + print(" Expression.execute() = ", result4) + # Can Expression access the scene tree? + var expr5 = Expression.new() + err = expr5.parse("get_tree().quit()") + print("Expression.parse('get_tree().quit()') err=", err) + if err == OK: + var result5 = expr5.execute() + print(" Expression.execute() = ", result5) + # Can Expression access the global scope? + var expr6 = Expression.new() + err = expr6.parse("print('hello')") + print("Expression.parse('print') err=", err) + if err == OK: + var result6 = expr6.execute() + print(" Expression.execute() = ", result6) + +func probe_engine_singletons(): + print("--- Engine singletons ---") + # Engine singletons are the global objects like OS, Engine, etc. + 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")) + # Can we get the main loop? + print("Engine.get_main_loop() = ", Engine.get_main_loop()) + # Can we get the physics server? + print("Engine has 'get_physics_server': ", Engine.has_method("get_physics_server")) + +func probe_global_scope(): + print("--- Global scope ---") + # Can we enumerate what's in the global scope? + var globals = [] + for g in GDScript.get_global_class_list(): + globals.append(g) + 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_worker_threads(): + print("--- Worker threads ---") + # Can we run scripts in worker threads? + var thread = Thread.new() + print(" Thread.new() = ", thread) + # 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_project_settings(): + print("--- Project settings ---") + # Check for any sandbox-related project settings + 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")) + # Can we set project settings at runtime? + print(" ProjectSettings has 'set_setting': ", ProjectSettings.has_method("set_setting")) + print(" ProjectSettings has 'save': ", ProjectSettings.has_method("save")) + +func probe_scriptserver(): + print("--- ScriptServer ---") + # 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 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()