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
-8
@@ -36,15 +36,32 @@ struct Out {
|
||||
stderr: String,
|
||||
}
|
||||
|
||||
/// Options that keep repository content from running code (hooks, fsmonitor).
|
||||
const SAFE_OPTS: &[&str] = &["-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", "-c", "protocol.ext.allow=never"];
|
||||
|
||||
tokio::task_local! {
|
||||
/// Sandbox of the requesting user for all git calls of this request.
|
||||
static SANDBOX: Option<crate::sandbox::SandboxSpec>;
|
||||
}
|
||||
|
||||
async fn git(dir: &Path, args: &[&str], timeout: u64) -> Result<Out, ApiError> {
|
||||
let fut = Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(dir)
|
||||
.env("GIT_TERMINAL_PROMPT", "0")
|
||||
.env("GIT_PAGER", "cat")
|
||||
.env("LC_ALL", "C")
|
||||
.kill_on_drop(true)
|
||||
.output();
|
||||
let mut all: Vec<&str> = SAFE_OPTS.to_vec();
|
||||
all.extend_from_slice(args);
|
||||
let spec = SANDBOX.try_with(|s| s.clone()).ok().flatten();
|
||||
let mut cmd = match &spec {
|
||||
Some(spec) => {
|
||||
if !crate::sandbox::available() {
|
||||
return Err(ApiError(StatusCode::SERVICE_UNAVAILABLE, "git is disabled: the sandbox is not available".into()));
|
||||
}
|
||||
crate::sandbox::command(spec, dir, "git", &all)
|
||||
}
|
||||
None => {
|
||||
let mut c = Command::new("git");
|
||||
c.args(&all).current_dir(dir).env("GIT_TERMINAL_PROMPT", "0").env("GIT_PAGER", "cat").env("LC_ALL", "C");
|
||||
c
|
||||
}
|
||||
};
|
||||
let fut = cmd.kill_on_drop(true).output();
|
||||
let out = tokio::time::timeout(Duration::from_secs(timeout), fut)
|
||||
.await
|
||||
.map_err(|_| ApiError(StatusCode::GATEWAY_TIMEOUT, "git timed out".into()))?
|
||||
@@ -147,6 +164,11 @@ fn parse_status(raw: &str) -> Value {
|
||||
}
|
||||
|
||||
pub async fn handle(Extension(user): Extension<User>, State(app): State<AppState>, Json(b): Json<GitBody>) -> ApiResult {
|
||||
let spec = crate::sandbox::spec_for(&app, Some(&user.id));
|
||||
SANDBOX.scope(spec, handle_inner(user, app, b)).await
|
||||
}
|
||||
|
||||
async fn handle_inner(user: User, app: AppState, b: GitBody) -> ApiResult {
|
||||
let target = checked_path(&app, &user, &b.path, b.action != "clone")?;
|
||||
|
||||
// Actions that create a repository.
|
||||
@@ -309,6 +331,9 @@ pub async fn handle(Extension(user): Extension<User>, State(app): State<AppState
|
||||
}
|
||||
};
|
||||
let o = git(&repo, &args, 300).await?;
|
||||
if b.action == "push" {
|
||||
app.audit(Some(&user), "git.push", format!("{} ({})", repo.display(), if o.ok { "ok" } else { "failed" }), "");
|
||||
}
|
||||
if !o.ok {
|
||||
return Err(fail(o));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user