Add security hardening, Ghidra, recreate skill, checkpoints, shells, web search and themes

- Sandbox (bubblewrap) for non-admin shell/git, MCP per-user rights, hard budgets and quotas,
  login rate limiting, audit log and backups
- Ghidra headless decompilation (ARM64 natives built from source), binary_info tool,
  clean room / deep workflow per session
- Recreate skill and commands for analysing and re-building old software
- File checkpoints with rewind/restore, background shells (bash_output, kill_shell)
- web_search tool (DuckDuckGo, SearXNG, Brave, Tavily)
- In-app dialogs and toasts instead of browser dialogs
- 15 base themes (trilium-themes palettes plus Nord, VS Code, GitHub, Catppuccin, One,
  Dracula, Rose Pine, Everforest, Kanagawa) with light/dark/auto switcher and license notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 19:12:30 +01:00
1 parent fab9add5d5
commit 5b569b6835
108 files changed
+6785 -279

No files matched your search

+33 -1
View File
@@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex, RwLock};
use tokio::sync::{OwnedSemaphorePermit, Semaphore, broadcast, oneshot};
use tokio_util::sync::CancellationToken;
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum PermissionReply {
/// Allow this one call.
@@ -39,6 +39,8 @@ pub struct Runtime {
pub cancel: Option<CancellationToken>,
pub read_files: HashSet<PathBuf>,
pub approved: HashSet<String>,
/// The model request currently running (for clients that connect mid-step).
pub step: Option<Value>,
}
pub struct App {
@@ -61,6 +63,12 @@ pub struct App {
owners: Mutex<HashMap<String, (String, Option<String>)>>,
/// root session id → users it is shared with.
shares: Mutex<HashMap<String, Vec<String>>>,
/// Failed sign-ins per key (ip or username): timestamps in ms.
pub login_failures: Mutex<HashMap<String, Vec<i64>>>,
/// State of an admin-triggered tool installation (Ghidra).
pub tool_install: crate::reverse::ToolInstall,
/// Background shells of all sessions.
pub shells: crate::shells::Shells,
}
/// What a user may do with a session.
@@ -94,6 +102,9 @@ impl App {
oidc_discovery: Mutex::default(),
owners: Mutex::default(),
shares: Mutex::default(),
login_failures: Mutex::default(),
tool_install: Mutex::default(),
shells: Mutex::default(),
});
crate::auth::bootstrap(&app)?;
app.clone().start_mcp();
@@ -195,6 +206,11 @@ impl App {
}
}
/// Record an action in the audit log.
pub fn audit(&self, user: Option<&crate::store::User>, action: &str, detail: impl AsRef<str>, ip: &str) {
self.store.audit(user.map(|u| u.id.as_str()), user.map(|u| u.username.as_str()).unwrap_or(""), action, detail.as_ref(), ip);
}
pub fn data_dir(&self) -> PathBuf {
self.config().data_dir()
}
@@ -232,6 +248,14 @@ impl App {
// --- events ---------------------------------------------------------
pub fn emit(&self, ev: Event) {
match &ev {
Event::StepStarted { session_id, .. } => {
let v = serde_json::to_value(&ev).ok();
self.with_runtime(session_id, |r| r.step = v);
}
Event::SessionStatus { session_id, busy: false } => self.with_runtime(session_id, |r| r.step = None),
_ => {}
}
let _ = self.events.send(ev);
}
@@ -373,6 +397,14 @@ impl App {
Ok(res)
}
/// "tool: title" of a pending permission request (for the audit log).
pub fn pending_title(&self, id: &str) -> Option<String> {
self.permissions.lock().unwrap().get(id).and_then(|p| match &p.event {
Event::PermissionRequest { tool, title, .. } => Some(format!("{tool}: {title}")),
_ => None,
})
}
/// Session of a pending permission request or question.
pub fn pending_session(&self, id: &str) -> Option<String> {
let from_perm = self.permissions.lock().unwrap().get(id).and_then(|p| p.event.session_id().map(String::from));