Compare commits

...
2 changed files with 22 additions and 6 deletions

No files matched your search

+8 -5
View File
@@ -296,16 +296,18 @@ void PluginAuditManager::add_denied_filename(const std::string& filename)
std::vector<std::string> 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",
};
}
@@ -1076,8 +1078,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
+14 -1
View File
@@ -43,7 +43,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();
@@ -55,6 +55,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")
@@ -110,6 +111,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)")
{
@@ -150,6 +152,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");