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:
1 parent
fab9add5d5
commit
5b569b6835
108 files changed
+6785
-279
No files matched your search
+33
-1
@@ -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));
|
||||
|
||||
Reference in new issue
Block a user