From 9cf66fc04aa4c2e4aa8d9ed83f758f668e1de2e6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 25 Mar 2026 22:43:06 +0000 Subject: [PATCH] [fix](trx-app): add plugin loading validation and disable toggle Reject world-writable plugin files on Unix to prevent loading tampered libraries. Add TRX_PLUGINS_DISABLED env var to disable plugin loading entirely. https://claude.ai/code/session_01XzurkeuUmamBuhQwxVy7T4 Signed-off-by: Claude --- src/trx-app/src/plugins.rs | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/trx-app/src/plugins.rs b/src/trx-app/src/plugins.rs index 47cef7a..d9e49a8 100644 --- a/src/trx-app/src/plugins.rs +++ b/src/trx-app/src/plugins.rs @@ -9,6 +9,9 @@ use std::ptr::NonNull; use libloading::{Library, Symbol}; use tracing::{info, warn}; +/// Environment variable to disable plugin loading entirely. +const PLUGIN_DISABLE_ENV: &str = "TRX_PLUGINS_DISABLED"; + const PLUGIN_ENV: &str = "TRX_PLUGIN_DIRS"; const BACKEND_ENTRYPOINT: &str = "trx_register_backend"; const FRONTEND_ENTRYPOINT: &str = "trx_register_frontend"; @@ -37,6 +40,12 @@ fn load_plugins_for_entrypoint( entrypoint: &str, context: NonNull, ) -> Vec { + // Allow disabling plugin loading entirely via environment variable. + if std::env::var(PLUGIN_DISABLE_ENV).is_ok() { + info!("Plugin loading disabled via {}", PLUGIN_DISABLE_ENV); + return Vec::new(); + } + let mut libraries = Vec::new(); let search_paths = plugin_search_paths(); @@ -75,6 +84,11 @@ fn load_plugins_from_dir( continue; } + // Validate file permissions before loading. + if !validate_plugin_file(&path) { + continue; + } + unsafe { match Library::new(&path) { Ok(lib) => { @@ -130,6 +144,39 @@ fn plugin_search_paths() -> Vec { paths } +/// Validate plugin file before loading: check ownership and permissions. +/// +/// On Unix, reject files that are world-writable (mode & 0o002) to prevent +/// loading tampered libraries. On non-Unix platforms, always accept. +fn validate_plugin_file(path: &Path) -> bool { + #[cfg(unix)] + { + use std::os::unix::fs::MetadataExt; + match std::fs::metadata(path) { + Ok(meta) => { + let mode = meta.mode(); + if mode & 0o002 != 0 { + warn!( + "Skipping world-writable plugin {:?} (mode {:o})", + path, mode + ); + return false; + } + true + } + Err(e) => { + warn!("Cannot stat plugin {:?}: {}", path, e); + false + } + } + } + #[cfg(not(unix))] + { + let _ = path; + true + } +} + fn is_plugin_file(path: &Path) -> bool { path.extension() .and_then(OsStr::to_str)