diff --git a/sandbox_probe/probe5.gd b/sandbox_probe/probe5.gd index 46d2fa8..436e359 100644 --- a/sandbox_probe/probe5.gd +++ b/sandbox_probe/probe5.gd @@ -1,7 +1,7 @@ extends Node func _ready(): - print("=== SANDBOX PROBE 5: Corrected Sandboxing Mechanisms (Godot 4.6.2) ===") + print("=== SANDBOX PROBE 5b: Corrected Sandboxing Mechanisms (Godot 4.6.2) ===") probe_script_loading() probe_os_apis() probe_file_access() @@ -11,15 +11,13 @@ func _ready(): probe_engine_singletons() probe_worker_threads() probe_project_settings() - probe_scriptserver() probe_script_restrictions() probe_allowlist() - print("=== SANDBOX PROBE 5 END ===") + print("=== SANDBOX PROBE 5b 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() @@ -27,63 +25,51 @@ func probe_script_loading(): 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? (instance methods) 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")) + # Can we get the list of static methods on GDScript class? + var gd_methods = GDScript.get_method_list() + var gd_method_names = [] + for m in gd_methods: + gd_method_names.append(String(m.name)) + print(" GDScript static methods: ", gd_method_names) 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()) 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://") @@ -91,83 +77,63 @@ func probe_file_access(): 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) 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? (static method on ClassDB) - 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 enumerate ClassDB methods to see what's available? + var classdb_methods = ClassDB.get_method_list() + var classdb_method_names = [] + for m in classdb_methods: + classdb_method_names.append(String(m.name)) + print(" ClassDB methods: ", classdb_method_names) 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) @@ -177,33 +143,24 @@ func probe_expression_engine(): func probe_engine_singletons(): print("--- Engine singletons ---") - # Engine singletons are the global objects like OS, Engine, etc. - # get_singletons() is a static method on Engine - 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.get_physics_server() = ", Engine.get_physics_server()) + # Can we enumerate singletons via Engine instance methods? + var engine_methods = Engine.get_method_list() + var engine_method_names = [] + for m in engine_methods: + engine_method_names.append(String(m.name)) + print(" Engine instance methods: ", engine_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: @@ -211,31 +168,14 @@ func probe_project_settings(): 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() @@ -243,14 +183,15 @@ func probe_script_restrictions(): 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")) print(" GDScript has method 'get_global_scope': ", gd.has_method("get_global_scope")) + # Can we check what identifiers a script references via the analyzer? + print(" GDScript has 'get_script_constant_map': ", gd.has_method("get_script_constant_map")) + # Can we get the list of member variables? + print(" GDScript has 'get_script_property_list': ", gd.has_method("get_script_property_list")) func probe_allowlist(): print("--- Allowlist / static analysis ---") - # 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", @@ -261,22 +202,20 @@ func probe_allowlist(): 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) # Can we use a tokenizer/lexer to parse GDScript? - # GDScript has a built-in tokenizer accessible via the language - var lang = GDScriptLanguage.new() - print(" GDScriptLanguage.new() = ", lang) - print(" GDScriptLanguage has 'get_name': ", lang.has_method("get_name")) - print(" GDScriptLanguage has 'get_extension': ", lang.has_method("get_extension")) - # Can we get the tokenizer? - print(" GDScriptLanguage has 'get_tokenizer': ", lang.has_method("get_tokenizer")) - # Can we get the parser? - print(" GDScriptLanguage has 'get_parser': ", lang.has_method("get_parser")) - # Can we get the analyzer? - print(" GDScriptLanguage has 'get_analyzer': ", lang.has_method("get_analyzer")) - # Can we get the compiler? - print(" GDScriptLanguage has 'get_compiler': ", lang.has_method("get_compiler")) + # GDScriptLanguage is NOT exposed to GDScript (confirmed by parse error) + print(" GDScriptLanguage NOT exposed to GDScript (parse error confirmed)") + # Can we use the Expression engine to validate syntax? + var expr = Expression.new() + var err = expr.parse("var x = 1 + 2") + print(" Expression.parse('var x = 1 + 2') err=", err) + err = expr.parse("OS.execute('rm', ['-rf', '/'])") + print(" Expression.parse('OS.execute...') err=", err) + if err == OK: + var result = expr.execute() + print(" Expression.execute() = ", result) diff --git a/sandbox_probe/probe5.tscn b/sandbox_probe/probe5.tscn new file mode 100644 index 0000000..4ba7895 --- /dev/null +++ b/sandbox_probe/probe5.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://probe5.gd" id="1"] + +[node name="Probe5" type="Node"] +script = ExtResource("1") diff --git a/sandbox_probe/project.godot b/sandbox_probe/project.godot index 79c5ba9..e145707 100644 --- a/sandbox_probe/project.godot +++ b/sandbox_probe/project.godot @@ -4,5 +4,5 @@ config_version=5 [application] config/name="sandbox_probe" -run/main_scene="res://probe6.tscn" +run/main_scene="res://probe5.tscn" config/features=PackedStringArray("4.6")