[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 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-03-25 22:43:06 +00:00
committed by Stan Grams
parent cbe22bd7b6
commit 9cf66fc04a
+47
View File
@@ -9,6 +9,9 @@ use std::ptr::NonNull;
use libloading::{Library, Symbol}; use libloading::{Library, Symbol};
use tracing::{info, warn}; 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 PLUGIN_ENV: &str = "TRX_PLUGIN_DIRS";
const BACKEND_ENTRYPOINT: &str = "trx_register_backend"; const BACKEND_ENTRYPOINT: &str = "trx_register_backend";
const FRONTEND_ENTRYPOINT: &str = "trx_register_frontend"; const FRONTEND_ENTRYPOINT: &str = "trx_register_frontend";
@@ -37,6 +40,12 @@ fn load_plugins_for_entrypoint(
entrypoint: &str, entrypoint: &str,
context: NonNull<std::ffi::c_void>, context: NonNull<std::ffi::c_void>,
) -> Vec<Library> { ) -> Vec<Library> {
// 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 mut libraries = Vec::new();
let search_paths = plugin_search_paths(); let search_paths = plugin_search_paths();
@@ -75,6 +84,11 @@ fn load_plugins_from_dir(
continue; continue;
} }
// Validate file permissions before loading.
if !validate_plugin_file(&path) {
continue;
}
unsafe { unsafe {
match Library::new(&path) { match Library::new(&path) {
Ok(lib) => { Ok(lib) => {
@@ -130,6 +144,39 @@ fn plugin_search_paths() -> Vec<PathBuf> {
paths 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 { fn is_plugin_file(path: &Path) -> bool {
path.extension() path.extension()
.and_then(OsStr::to_str) .and_then(OsStr::to_str)