CodeVetter · Repo Unpack · Risk map
ohmyzsh/ohmyzsh
The dominant risks are supply-chain and self-modification: every user runs curl|sh installer code and an auto-updater that git-pulls new shell code into a live config; `omz` commands rewrite the user's .zshrc with awk; and 368 community plugins execute unchecked code at every shell start. Mitigations exist (compfix permission checks, PR-test label gating, .zshrc backups + syntax validation, hardened CI), but coverage of the critical paths is minimal.
- The documented install path is `sh -c "$(curl -fsSL .../tools/install.sh)"` — remote code executed with user privileges; the script then chsh's the login shell (via sudo when available) and execs zsh.
- install.sh resolves $HOME via `eval echo ~"$USER"` when HOME is unset — an acknowledged CWE-78-adjacent path that is only safe because $USER is first restricted to [alnum, _, ., -].
- Auto-update executes freshly fetched code: upgrade.sh does `git pull --rebase` on master and the new oh-my-zsh.sh/plugins run at next shell start; the 'cooldown' zstyle exists specifically to delay applying new commits as a supply-chain buffer.
- Every plugin file is arbitrary code sourced into every interactive shell; the only vetting is maintainer review, and `omz pr test` explicitly warns unlabeled PRs 'may contain malicious code' before fetching and running them.
- omz plugin/theme commands write directly into ${ZDOTDIR:-$HOME}/.zshrc via awk transformations; mitigations are the .zshrc.bck backup and the post-write `zsh -n` syntax check with automatic rollback, but a semantically-wrong-but-parseable edit would stick.
- compinit -i skips completion dirs that fail compaudit ownership/permission checks; users hitting this get a warning via handle_completion_insecurities — ZSH_DISABLE_COMPFIX=true bypasses the check entirely by switching to compinit -u.
- check_for_upgrade.sh's GitHub API call uses a 2s connect timeout and treats non-GitHub remotes as always-updateable; is_update_available returning 0 on merge-base failure assumes updates exist — fail-open rather than fail-closed.
- uninstall.sh runs `rm -rf ~/.oh-my-zsh` and `chsh` after a single y/N prompt, and install.sh's setup_shell runs `sudo chsh` — destructive/account-level operations driven by tty prompts that are bypassed only in unattended mode.
- update.lock is a stale-able mkdir lock under $ZSH/log: the code deletes locks older than a day, and the EXIT/INT/QUIT trap must return $ret correctly so Ctrl+C actually interrupts — a fragile bit of signal semantics the comments call out.
- Async prompt handler cancellation kills the process group only under MONITOR; without job control it can only TERM the direct child, and the file itself notes forked grandchildren 'may be orphaned and left behind'.
- lib/vcs_info.zsh exists specifically to %-quote hook_com fields before vcs_info hooks run — a workaround for CVE-2021-45444 where crafted branch/repo data could trigger command substitution in prompts on zsh 5.0.3-5.8.
- git.zsh documents real-world fragility: a =~ regex on non-UTF-8 git output aborted with REG_ILLSEQ (fixed via LC_ALL=C scoping), and async prompt is behind ':omz:alpha:lib:git' with 'force'/opt-out because of known issue reports; the CentOS-7 workaround carries a dated TODO.
- SECURITY.md states only the latest master commit is supported and relies on 'automated updates to minimize the time between vulnerability publication and patch release' — there is no stable/release line to pin.
- The dependencies updater executes precopy/postcopy shell snippets declared in .github/dependencies.yml inside CI with a write-capable token — a code-execution path driven by YAML data.