2
0

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)
This commit is contained in:
ai
2026-04-03 13:02:34 +00:00
parent cffc44205c
commit 760dea8147
3 changed files with 65 additions and 33 deletions

View File

@@ -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

16
.zshrc
View File

@@ -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

View File

@@ -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 <user/repo>");
return;
};
let dest = format!("{}/{}", dir, repo_name);
@@ -97,59 +100,53 @@ fn plugin_add(name: &str) {
}
}
fn plugin_ls() {
fn list_plugins() -> Vec<String> {
let dir = plugin_dir();
let entries = match std::fs::read_dir(&dir) {
Ok(e) => e,
Err(_) => {
eprintln!("no plugins");
return;
}
Err(_) => return vec![],
};
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_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<String> = entries
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();
.collect()
}
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 target = if let Some(n) = name {
n.to_string()
} else {
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;