From 760dea8147c75747ee7f89a18067676d2847f0f7 Mon Sep 17 00:00:00 2001 From: ai Date: Fri, 3 Apr 2026 13:02:34 +0000 Subject: [PATCH] security: fix path traversal in plugin_rm, add coding tools - prevent path traversal in ais plugin rm (reject .. and /) - validate plugin_add requires user/repo format - extract list_plugins() helper to deduplicate code - add git aliases (s, d, l, lg, pa for dual push, etc) - add cargo/rg aliases and mkcd helper to zshrc - fix zr alias (source instead of unreachable exec) --- .gitconfig | 21 +++++++++++++++ .zshrc | 16 +++++++++++- rust/ais/src/main.rs | 61 +++++++++++++++++++++----------------------- 3 files changed, 65 insertions(+), 33 deletions(-) diff --git a/.gitconfig b/.gitconfig index ea26620..90bcaa1 100644 --- a/.gitconfig +++ b/.gitconfig @@ -12,3 +12,24 @@ defaultBranch = main [push] autoSetupRemote = true +[alias] + s = status -sb + d = diff + ds = diff --staged + l = log --oneline -20 + lg = log --oneline --graph --all -30 + a = add + cm = commit -m + co = checkout + br = branch -v + p = push + pa = !git push origin main && git push gitea main + f = fetch --all + last = log -1 --stat + undo = reset --soft HEAD~1 + amend = commit --amend --no-edit + wip = !git add -A && git commit -m 'wip' +[diff] + colorMoved = default +[merge] + conflictstyle = diff3 diff --git a/.zshrc b/.zshrc index cf0cd51..cece214 100644 --- a/.zshrc +++ b/.zshrc @@ -12,9 +12,23 @@ alias v="vim" alias ts="vim ~/.tmux.conf" alias vs="vim ~/.vimrc" alias zs="vim ~/.zshrc" -alias zr="exec $SHELL && . ~/.zshrc" +alias zr="source ~/.zshrc" alias ll="ls -alh" alias df="df -H" +alias g="git" +alias gs="git s" +alias gd="git d" +alias gl="git l" +alias gp="git pa" +alias cb="cargo build --release" +alias cr="cargo run" +alias ct="cargo test" + +# mkcd: create and enter directory +mkcd() { mkdir -p "$1" && cd "$1" } + +# rg shortcut with common defaults +r() { rg --smart-case --hidden --glob '!.git' "$@" } zmodload zsh/complist zstyle ':completion:*' menu select diff --git a/rust/ais/src/main.rs b/rust/ais/src/main.rs index 49c0b51..dd0203e 100644 --- a/rust/ais/src/main.rs +++ b/rust/ais/src/main.rs @@ -75,9 +75,12 @@ fn plugin_add(name: &str) { let repo = name.rsplit('/').next().unwrap_or(name); let repo = repo.strip_suffix(".git").unwrap_or(repo); (name.to_string(), repo.to_string()) - } else { + } else if name.contains('/') { let repo = name.split('/').last().unwrap_or(name); (format!("https://github.com/{}.git", name), repo.to_string()) + } else { + eprintln!("usage: ais plugin add "); + return; }; let dest = format!("{}/{}", dir, repo_name); @@ -97,59 +100,53 @@ fn plugin_add(name: &str) { } } -fn plugin_ls() { +fn list_plugins() -> Vec { let dir = plugin_dir(); let entries = match std::fs::read_dir(&dir) { Ok(e) => e, - Err(_) => { - eprintln!("no plugins"); - return; - } + Err(_) => return vec![], }; + entries + .flatten() + .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false)) + .map(|e| e.file_name().to_string_lossy().to_string()) + .filter(|n| !n.starts_with('.')) + .collect() +} - for entry in entries.flatten() { - if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { - let name = entry.file_name().to_string_lossy().to_string(); - if !name.starts_with('.') { - println!("{}", name); - } - } +fn plugin_ls() { + let plugins = list_plugins(); + if plugins.is_empty() { + eprintln!("no plugins"); + return; + } + for name in &plugins { + println!("{}", name); } } fn plugin_rm(name: Option<&str>) { - let dir = plugin_dir(); - let target = if let Some(n) = name { n.to_string() } else { - // fuzzy select from installed plugins - let entries = match std::fs::read_dir(&dir) { - Ok(e) => e, - Err(_) => { - eprintln!("no plugins"); - return; - } - }; - let plugins: Vec = entries - .flatten() - .filter(|e| e.file_type().map(|t| t.is_dir()).unwrap_or(false)) - .map(|e| e.file_name().to_string_lossy().to_string()) - .filter(|n| !n.starts_with('.')) - .collect(); - + let plugins = list_plugins(); if plugins.is_empty() { eprintln!("no plugins"); return; } - match fuzzy_select(&plugins, "rm plugin") { Some(s) => s, None => return, } }; - let path = format!("{}/{}", dir, target); + // prevent path traversal + if target.contains('/') || target.contains("..") { + eprintln!("invalid plugin name: {}", target); + return; + } + + let path = format!("{}/{}", plugin_dir(), target); if !std::path::Path::new(&path).exists() { eprintln!("{} not found", target); return;