Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/xtop-cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers};
use std::fs;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use xtop_core::application::plugin_manager::PluginManager;
use xtop_core::application::state::{AppState, Config, InputMode, PalettePage};
Expand Down Expand Up @@ -174,7 +174,7 @@ fn save_config(state: &AppState) {
let _ = config::save_config(&cfg);
}

fn build_plugin_manager(state: &mut AppState, cfg_dir: &PathBuf) -> PluginManager {
fn build_plugin_manager(state: &mut AppState, cfg_dir: &Path) -> PluginManager {
let plugins_dir = cfg_dir.join("plugins");
fs::create_dir_all(&plugins_dir).ok();
let mut mgr = PluginManager::new(plugins_dir);
Expand Down Expand Up @@ -417,7 +417,7 @@ fn cmd_plugin_install(name_or_url: &str) -> anyhow::Result<()> {
println!("Building xtop with {pkg_name} ...");
let build = std::process::Command::new("cargo")
.args(["build", "--release"])
.current_dir(&workspace_dir)
.current_dir(workspace_dir)
.status()
.map_err(|e| anyhow::anyhow!("cargo build failed: {e}"))?;
if !build.success() {
Expand Down
8 changes: 4 additions & 4 deletions crates/xtop-cli/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! MCP module (`xtop_plugin_sentinel::mcp::run_server`) which handles the
//! actual stdin/stdout MCP protocol loop.

use std::path::PathBuf;
use std::path::{Path, PathBuf};
use xtop_core::application::plugin_manager::PluginManager;
use xtop_core::application::state::AppState;
use xtop_core::infrastructure::composite_provider::CompositeProvider;
Expand All @@ -27,7 +27,7 @@ pub fn run_mcp_server() -> anyhow::Result<()> {
// Delegate to Sentinel's MCP module
#[cfg(feature = "plugin-sentinel")]
{
return xtop_plugin_sentinel::mcp::run_server(&mut state);
xtop_plugin_sentinel::mcp::run_server(&mut state)
}

#[cfg(not(feature = "plugin-sentinel"))]
Expand All @@ -42,7 +42,7 @@ fn config_dir() -> PathBuf {
xtop_core::infrastructure::config::config_dir()
}

fn build_plugin_manager(state: &mut AppState, cfg_dir: &PathBuf) -> PluginManager {
fn build_plugin_manager(state: &mut AppState, cfg_dir: &Path) -> PluginManager {
let plugins_dir = cfg_dir.join("plugins");
std::fs::create_dir_all(&plugins_dir).ok();
let mut mgr = PluginManager::new(plugins_dir);
Expand All @@ -58,7 +58,7 @@ fn build_plugin_manager(state: &mut AppState, cfg_dir: &PathBuf) -> PluginManage
mgr
}

fn initialize_state(cfg_dir: &PathBuf) -> anyhow::Result<AppState> {
fn initialize_state(cfg_dir: &Path) -> anyhow::Result<AppState> {
let sysinfo_provider = SysinfoProvider::new();
let composite = CompositeProvider::new(Box::new(sysinfo_provider));

Expand Down
16 changes: 8 additions & 8 deletions crates/xtop-core/src/application/plugin_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ impl PluginManager {
Ok(())
}

fn plugin_has_capability(plugin: &Box<dyn Plugin>, cap: &PluginCapability) -> bool {
fn plugin_has_capability(plugin: &dyn Plugin, cap: &PluginCapability) -> bool {
plugin.manifest().capabilities.contains(cap)
}

fn build_context<'a>(
base: &std::path::Path,
plugin: &Box<dyn Plugin>,
plugin: &dyn Plugin,
state: &'a mut AppState,
) -> PluginContext<'a> {
let id = plugin.manifest().id.clone();
Expand All @@ -91,7 +91,7 @@ impl PluginManager {
let base = self.plugin_data_base.clone();
for plugin in &mut self.plugins {
let id = plugin.manifest().id.clone();
let mut ctx = Self::build_context(&base, plugin, state);
let mut ctx = Self::build_context(&base, &**plugin, state);
if let Err(e) = plugin.on_tick(&mut ctx) {
eprintln!("[plugin:{id}] tick error: {e}");
}
Expand All @@ -104,7 +104,7 @@ impl PluginManager {
let base = self.plugin_data_base.clone();
for plugin in &mut self.plugins {
let id = plugin.manifest().id.clone();
let mut ctx = Self::build_context(&base, plugin, state);
let mut ctx = Self::build_context(&base, &**plugin, state);
match plugin.on_key(&mut ctx, key) {
Ok(true) => return true,
Err(e) => eprintln!("[plugin:{id}] key error: {e}"),
Expand All @@ -119,7 +119,7 @@ impl PluginManager {
pub fn collect_data_providers(&self) -> Vec<Box<dyn SystemDataProvider>> {
let mut providers: Vec<Box<dyn SystemDataProvider>> = Vec::new();
for plugin in &self.plugins {
if Self::plugin_has_capability(plugin, &PluginCapability::ReadSystemInfo) {
if Self::plugin_has_capability(&**plugin, &PluginCapability::ReadSystemInfo) {
if let Some(provider) = plugin.data_provider() {
providers.push(provider);
}
Expand All @@ -133,7 +133,7 @@ impl PluginManager {
pub fn collect_widgets(&self) -> Vec<WidgetRegistration> {
let mut widgets: Vec<WidgetRegistration> = Vec::new();
for plugin in &self.plugins {
if Self::plugin_has_capability(plugin, &PluginCapability::RenderWidgets) {
if Self::plugin_has_capability(&**plugin, &PluginCapability::RenderWidgets) {
if let Some(widget) = plugin.widget() {
widgets.push(widget);
}
Expand All @@ -157,7 +157,7 @@ impl PluginManager {
if plugin.manifest().id != plugin_id {
continue;
}
let mut ctx = Self::build_context(&base, plugin, state);
let mut ctx = Self::build_context(&base, &**plugin, state);
return plugin.execute(&mut ctx, action, params);
}
Err(PluginError::Recoverable(format!(
Expand All @@ -180,7 +180,7 @@ impl PluginManager {
let base = self.plugin_data_base.clone();
for plugin in &mut self.plugins {
let id = plugin.manifest().id.clone();
let mut ctx = Self::build_context(&base, plugin, state);
let mut ctx = Self::build_context(&base, &**plugin, state);
if let Err(e) = plugin.on_disable(&mut ctx) {
eprintln!("[plugin:{id}] disable error: {e}");
}
Expand Down
7 changes: 4 additions & 3 deletions crates/xtop-core/src/domain/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,13 @@ impl std::fmt::Display for PluginError {

impl std::error::Error for PluginError {}

type RenderFn =
std::sync::Arc<dyn Fn(&mut ratatui::Frame, &AppState, ratatui::prelude::Rect) + Send + Sync>;

/// A widget that a plugin registers for rendering in the TUI.
pub struct WidgetRegistration {
pub name: String,
pub render: std::sync::Arc<
dyn Fn(&mut ratatui::Frame, &AppState, ratatui::prelude::Rect) + Send + Sync,
>,
pub render: RenderFn,
}

impl Debug for WidgetRegistration {
Expand Down
43 changes: 17 additions & 26 deletions plugins/xtop-plugin-sentinel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ pub struct SentinelPlugin {
spawn_history: HashMap<String, Vec<(u32, u64)>>,
}

impl Default for SentinelPlugin {
fn default() -> Self {
Self::new()
}
}

impl SentinelPlugin {
pub fn new() -> Self {
Self {
Expand Down Expand Up @@ -218,10 +224,10 @@ impl SentinelPlugin {
fields.iter().any(|f| match *f {
"name" => re.is_match(&p.name),
"cmd" => re.is_match(&p.cmd),
"user" => p.user_id.as_deref().map_or(false, |u| re.is_match(u)),
"user" => p.user_id.as_deref().is_some_and(|u| re.is_match(u)),
"state" => re.is_match(&p.state),
"exe" => p.exe_path.as_deref().map_or(false, |e| re.is_match(e)),
"cwd" => p.cwd.as_deref().map_or(false, |c| re.is_match(c)),
"exe" => p.exe_path.as_deref().is_some_and(|e| re.is_match(e)),
"cwd" => p.cwd.as_deref().is_some_and(|c| re.is_match(c)),
_ => false,
})
})
Expand Down Expand Up @@ -258,7 +264,7 @@ impl SentinelPlugin {
procs.retain(|p| {
re.is_match(&p.name)
|| re.is_match(&p.cmd)
|| p.exe_path.as_deref().map_or(false, |e| re.is_match(e))
|| p.exe_path.as_deref().is_some_and(|e| re.is_match(e))
});
}

Expand Down Expand Up @@ -328,10 +334,7 @@ impl SentinelPlugin {

/// Rule 3: Process masquerading (name != exe file stem OR system name not at canonical path).
fn rule_masquerading(&self, proc: &ProcessInfo) -> Option<SentinelAlert> {
let exe = match proc.exe_path.as_deref() {
Some(e) => e,
None => return None,
};
let exe = proc.exe_path.as_deref()?;
// Extract file stem from exe
let stem = std::path::Path::new(exe)
.file_stem()
Expand Down Expand Up @@ -383,14 +386,8 @@ impl SentinelPlugin {

/// Rule 4: Privilege escalation (EUID != UID).
fn rule_privilege_escalation(&self, proc: &ProcessInfo) -> Option<SentinelAlert> {
let euid = match proc.effective_user_id.as_deref() {
Some(u) => u,
None => return None,
};
let uid = match proc.user_id.as_deref() {
Some(u) => u,
None => return None,
};
let euid = proc.effective_user_id.as_deref()?;
let uid = proc.user_id.as_deref()?;
if euid == uid {
return None;
}
Expand Down Expand Up @@ -433,14 +430,8 @@ impl SentinelPlugin {
proc: &ProcessInfo,
parent_map: &HashMap<u32, &ProcessInfo>,
) -> Option<SentinelAlert> {
let ppid = match proc.parent_pid {
Some(pid) => pid,
None => return None,
};
let parent = match parent_map.get(&ppid) {
Some(p) => p,
None => return None,
};
let ppid = proc.parent_pid?;
let parent = parent_map.get(&ppid)?;
let parent_lower = parent.name.to_lowercase();
let is_browser = BROWSER_NAMES.iter().any(|b| parent_lower.contains(b));
if !is_browser {
Expand Down Expand Up @@ -479,7 +470,7 @@ impl SentinelPlugin {
if KNOWN_THREAT_CMDS.iter().any(|t| {
Regex::new(t)
.ok()
.map_or(false, |re| re.is_match(&cmd_joined))
.is_some_and(|re| re.is_match(&cmd_joined))
}) {
return Some(SentinelAlert::new(
"known_threat_pattern",
Expand Down Expand Up @@ -695,7 +686,7 @@ impl Plugin for SentinelPlugin {

fn on_tick(&mut self, ctx: &mut PluginContext) -> Result<(), PluginError> {
self.tick_count += 1;
if self.tick_count % 5 == 0 {
if self.tick_count.is_multiple_of(5) {
self.alerts = self.analyze_processes(ctx);
}
Ok(())
Expand Down
Loading