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

+38 -13
View File
@@ -24,7 +24,9 @@ impl Tool for Bash {
"Run a shell command (bash) in the session working directory. stdout and stderr are \
combined. Default timeout 2 minutes, max 10 minutes (timeout_ms). The working directory \
does not persist between calls – use absolute paths or `cd dir && ...`. Prefer the \
dedicated read/edit/glob/grep tools over cat/sed/find/grep."
dedicated read/edit/glob/grep tools over cat/sed/find/grep. For long-running processes \
(dev servers, watchers, long builds) set run_in_background=true: you get a shell id, \
read new output with bash_output and stop it with kill_shell."
.into()
}
fn schema(&self) -> Value {
@@ -33,7 +35,8 @@ impl Tool for Bash {
"properties": {
"command": {"type": "string"},
"description": {"type": "string", "description": "5-10 word description of what the command does"},
"timeout_ms": {"type": "integer"}
"timeout_ms": {"type": "integer"},
"run_in_background": {"type": "boolean", "description": "Keep running; read output with bash_output"}
},
"required": ["command"]
})
@@ -49,17 +52,18 @@ impl Tool for Bash {
let timeout = Duration::from_millis(
input["timeout_ms"].as_u64().unwrap_or(DEFAULT_TIMEOUT_MS).min(MAX_TIMEOUT_MS),
);
let mut cmd = Command::new("bash");
cmd.arg("-c")
.arg(format!("{command} 2>&1"))
.current_dir(&ctx.cwd)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.env("CODECRAFT", "1")
.env("GIT_TERMINAL_PROMPT", "0")
.env("PAGER", "cat")
.kill_on_drop(true);
let mut cmd = match shell_command(ctx, command) {
Ok(c) => c,
Err(e) => return Ok(e),
};
if input["run_in_background"].as_bool().unwrap_or(false) {
let id = crate::shells::spawn(&ctx.app, &ctx.session_id, command, cmd)?;
return Ok(ToolOutput::text(format!(
"Started in the background with shell id {id}. Read its output with bash_output (shell_id \"{id}\") and stop it with kill_shell."
))
.with_meta(json!({"shell_id": id})));
}
cmd.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::null()).kill_on_drop(true);
// Own process group so the whole tree can be killed on abort/timeout.
cmd.process_group(0);
let mut child = cmd.spawn()?;
@@ -128,6 +132,27 @@ impl Tool for Bash {
}
}
/// The bash command for a script, sandboxed for non-admin users.
pub fn shell_command(ctx: &ToolCtx, command: &str) -> std::result::Result<Command, ToolOutput> {
// stderr of every command (not just the last one) goes into the output.
let script = format!("exec 2>&1\n{command}");
Ok(match &ctx.sandbox {
Some(spec) => {
if !crate::sandbox::available() {
return Err(ToolOutput::error(
"Shell commands are disabled for this account: the server's sandbox (bubblewrap) is not available. Ask an admin.",
));
}
crate::sandbox::command(spec, &ctx.cwd, "bash", &["-c", &script])
}
None => {
let mut c = Command::new("bash");
c.arg("-c").arg(&script).current_dir(&ctx.cwd).env("CODECRAFT", "1").env("GIT_TERMINAL_PROMPT", "0").env("PAGER", "cat");
c
}
})
}
fn tail(s: &str, max: usize) -> String {
if s.len() <= max {
return s.to_string();