diff --git a/src/slic3r/plugin/PluginAuditManager.cpp b/src/slic3r/plugin/PluginAuditManager.cpp index 9cf010d803..7464a8a41f 100644 --- a/src/slic3r/plugin/PluginAuditManager.cpp +++ b/src/slic3r/plugin/PluginAuditManager.cpp @@ -353,16 +353,18 @@ void PluginAuditManager::add_denied_filename(const std::string& filename) std::vector PluginAuditManager::default_denied_filenames() { // AppConfig::config_path() picks .conf vs .ini on the USE_JSON_CONFIG ifdef and the app key - // on its mode, and is a non-static member we cannot call without an instance. Denying all + // on its mode, and is a non-static member we cannot call without an instance. Denying all // four names is cheaper and more robust than replicating that; the two unused names cost one - // string comparison each. Single-sourced here so install_hook() and the tests seed from the - // same list and cannot drift apart. + // string comparison each. Keep the plugin install-state sidecar here too: it controls which + // packages load on startup and must remain host-managed. Single-sourced so install_hook() and + // the tests seed from the same list and cannot drift apart. return { SLIC3R_APP_KEY ".conf", GCODEVIEWER_APP_KEY ".conf", SLIC3R_APP_KEY ".ini", GCODEVIEWER_APP_KEY ".ini", secret_constants::USER_SECRET_FILENAME, + ".install_state.json", }; } @@ -1283,8 +1285,9 @@ void PluginAuditManager::install_hook() // "cert" denied-path keyword seeded below. add_global_allowed_root(resources_dir(), /*allow_write=*/false); - // The user's app config and cloud credentials live directly inside data_dir(), so the - // root just granted would otherwise expose them to any plugin. Deny them by name. + // The user's app config and cloud credentials live directly inside data_dir(), and plugin + // install state lives beneath it, so the root just granted would otherwise expose them to + // plugins. Deny them by name. // // Seeded here rather than by each secret's owner because install_hook() runs during lazy // interpreter init and therefore provably precedes any plugin bytecode, whereas diff --git a/tests/slic3rutils/test_plugin_audit.cpp b/tests/slic3rutils/test_plugin_audit.cpp index debf5784b1..de0c1cf87e 100644 --- a/tests/slic3rutils/test_plugin_audit.cpp +++ b/tests/slic3rutils/test_plugin_audit.cpp @@ -69,7 +69,7 @@ void seed_denied_keywords() } // namespace -TEST_CASE("Plugin audit denies app config and token filenames anywhere", "[audit]") +TEST_CASE("Plugin audit denies host-managed filenames anywhere", "[audit]") { seed_denied_names(); const PluginAuditManager& mgr = PluginAuditManager::instance(); @@ -81,6 +81,7 @@ TEST_CASE("Plugin audit denies app config and token filenames anywhere", "[audit CHECK(mgr.is_denied_filename(fs::path(SLIC3R_APP_KEY ".ini"))); CHECK(mgr.is_denied_filename(fs::path(GCODEVIEWER_APP_KEY ".ini"))); CHECK(mgr.is_denied_filename(fs::path(secret_constants::USER_SECRET_FILENAME))); + CHECK(mgr.is_denied_filename(fs::path(".install_state.json"))); } SECTION("companions holding the same secrets are denied by the prefix rule") @@ -136,6 +137,7 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]") const fs::path conf = fs::path(data_dir()) / (SLIC3R_APP_KEY ".conf"); const fs::path token = fs::path(data_dir()) / secret_constants::USER_SECRET_FILENAME; + const fs::path install_state = fs::path(data_dir()) / "plugins" / "example" / ".install_state.json"; SECTION("a non-denied file inside the allowed root is writable (root really grants writes)") { @@ -176,6 +178,17 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]") CHECK_FALSE(decision.allowed); } + SECTION("plugin install state is blocked for reads and writes despite the allowed root") + { + AuditDecision read_decision = mgr.check_open(install_state.string(), "r"); + CHECK_FALSE(read_decision.allowed); + CHECK(read_decision.reason == "denied filename"); + + AuditDecision write_decision = mgr.check_open(install_state.string(), "w"); + CHECK_FALSE(write_decision.allowed); + CHECK(write_decision.reason == "denied filename"); + } + SECTION("a traversal path resolving to the config is blocked") { const fs::path traversal = fs::path(data_dir()) / "plugins" / ".." / (SLIC3R_APP_KEY ".conf");