Skip to content

install.sh: destructive git reset/clean on unvalidated ZI_HOME/ZI_BIN_DIR_NAME target #175

Description

@ss-o

Summary

install.sh's "update an existing zi install" path runs git clean -d -f -f and git reset --hard HEAD against ${ZI_HOME}/${ZI_BIN_DIR_NAME} after only checking that a .git directory exists there — it never verifies the directory is actually zi's clone. Since ZI_HOME and ZI_BIN_DIR_NAME are both read from the environment with no validation, a misconfigured environment can point this at an unrelated git repository and destroy uncommitted/untracked work in it.

Where

public/sh/install.sh:149-154

if test -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}/.git"; then
  cd "${ZI_HOME}/${ZI_BIN_DIR_NAME}" || exit 1
  printf '%s\n' "...Updating (z-shell/zi) plugin manager..."
  command git clean -d -f -f
  command git reset --hard HEAD
  command git pull -q origin "${BOPT}"

Why this matters

This script is distributed as a curl | sh one-liner and run by end users, many of whom will have ZI_HOME/ZI_BIN_DIR_NAME unset (safe defaults apply) — but for anyone who does have them set, there is no guard rail:

  • ZI_BIN_DIR_NAME defaults to bin and ZI_HOME defaults to ${XDG_DATA_HOME:-$HOME/.local/share}/zi, but both are honored verbatim from the environment if already set (public/sh/install.sh:115-121).

  • The only precondition for the destructive branch is test -d ".../.git" — true for any git repository, not specifically zi's.

  • A plausible failure scenario: a user has ZI_HOME set to $HOME (e.g. a copy-pasted env var from an unrelated guide, or XDG tooling misconfiguration) and keeps ~/bin as a personal git-tracked scripts directory — a common setup. Running the installer's update path would then:

    1. git clean -d -f -f — wipe all untracked files in ~/bin, ignoring excludes.
    2. git reset --hard HEAD — discard any uncommitted edits.
    3. git pull -q origin "${BOPT}" — pull from whatever origin/${BOPT} that unrelated repo has configured.

    All three run before the script has done anything to confirm it's actually looking at zi's clone.

Suggested fix

Verify the target is actually zi's clone before touching it, e.g.:

if test -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}/.git" && \
   git -C "${ZI_HOME}/${ZI_BIN_DIR_NAME}" remote get-url origin 2>/dev/null | grep -q 'z-shell/zi'; then

or check for a zi-specific sentinel (e.g. zi.zsh) alongside .git, and fail loudly with a clear error instead of silently operating on an unexpected directory.

Related (not filed separately, noted here for context)

Found in the same review pass, lower severity, not opening separate issues unless wanted:

  • public/sh/install.sh:110 — the -b/BOPT value is interpolated unescaped into a sed replacement expression; a value containing | or a trailing \ breaks the substitution or corrupts the generated init.zsh.
  • public/sh/install.sh:101-102,244-245 and public/sh/sync-init.sh's _legacy_url — fallback URLs under main/lib/... are dead now that lib/ was renamed to public/ in CI Workflow Improvements and Repository Cleanup #174; fail safely today but are worth pruning once external-caller blast radius is confirmed.

Metadata

Metadata

Assignees

Labels

priority:highNeeds prompt attention.securitySecurity-sensitive issue or hardening work.type:bugSomething is broken or behaving incorrectly.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions