Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
99ae80e
Initial plan
Copilot Jul 24, 2026
c880e47
fix: validate zi repository identity before destructive git operations
Copilot Jul 24, 2026
2649df9
fix: address code review feedback on security validation
Copilot Jul 24, 2026
1bcc948
fix: address remaining code review issues
Copilot Jul 24, 2026
008ef76
fix: strengthen URL matching with exact case pattern instead of grep
Copilot Jul 24, 2026
8cba7e3
fix: add clarity improvements from code review
Copilot Jul 24, 2026
a11f407
fix: clarify test double error message for remote subcommand validation
Copilot Jul 24, 2026
900ba1d
chore: add automated checksum generation script and integrate into CI
Copilot Jul 24, 2026
55f30fd
feat: automate checksum generation with generate-checksums.sh
Copilot Jul 24, 2026
57ea5ef
refactor: address code review feedback - minor clarity improvements
Copilot Jul 24, 2026
0b7c01b
refactor: address second code review round - inline _zi_remote, renam…
Copilot Jul 24, 2026
83d3f57
Potential fix for pull request finding
ss-o Jul 24, 2026
54365fe
Checksum 83d3f571cc95c3fb0f01073f18ecfc58a24d9235
digital-teams Jul 24, 2026
3b724d0
Potential fix for pull request finding
ss-o Jul 24, 2026
e4737e4
fix: satisfy shellcheck in checksum generator
Copilot Jul 24, 2026
f3a62a2
fix: remove remaining shellcheck warning
Copilot Jul 24, 2026
1606087
chore: refresh installer checksum
Copilot Jul 24, 2026
404b6d8
test: clarify fake git remote errors
Copilot Jul 24, 2026
ed4776b
chore: address validation feedback
Copilot Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/check-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y zsh
- name: "⚙️ Generate checksums"
run: sh ./public/sh/generate-checksums.sh
- name: "⚙️ Check: unit fixtures"
run: sh ./tests/installers.sh
- name: "⚙️ Check: install.sh -- -i skip"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/check-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ jobs:
- name: ⚙️ Prepare dependencies
run: |
brew install zsh
- name: "⚙️ Generate checksums"
run: sh ./public/sh/generate-checksums.sh
- name: "⚙️ Check: unit fixtures"
run: sh ./tests/installers.sh
- name: "⚙️ Check: install.sh -- -i skip"
Expand Down
10 changes: 2 additions & 8 deletions .github/workflows/checksum.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Checksum
on:
push:
paths:
- "public/sh/generate-checksums.sh"
- "public/sh/install_zpmod.sh"
- "public/sh/install.sh"
- "public/sh/sync-init.sh"
Expand All @@ -25,14 +26,7 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: "🆗 Generate checksum"
uses: jmgilman/actions-generate-checksum@3ea6dc9bf8eecf28e2ecc982fab683484a1a8561 # v1.0.1
with:
patterns: |
public/sh/install_zpmod.sh
public/sh/install.sh
public/sh/sync-init.sh
public/zsh/init.zsh
- run: mv checksum.txt public/
run: sh ./public/sh/generate-checksums.sh
- name: "🆗 Commit"
uses: z-shell/.github/actions/commit@3222f53692e448ed6e2caf8b8722e74fb2ca2461 # v1.1.1
with:
Expand Down
2 changes: 1 addition & 1 deletion public/checksum.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
0140801e80b2d8767e95d215850b6feab3642ee2b6fd70fbf4aab09afcfe7fca public/sh/install_zpmod.sh
0cca0bb9f95101b4bf392d6aa3c23123602d99a7cbb0decacaa72973e016d89d public/sh/install.sh
f9d8ef697ba682bdfe66fdd67d4517caa4e62bb8baf2868ec8ed9932809b76aa public/sh/install.sh
08cc893ceb982fc99d17db1966c6c30790cc571e16e4f5392352d995f5252952 public/sh/sync-init.sh
5c7af31d7fc848d80f9fedf69c75e6844503f550a3d5e020bc7cdfdaca624763 public/zsh/init.zsh
45 changes: 45 additions & 0 deletions public/sh/generate-checksums.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env sh
# -*- mode: sh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*-
# vim: ft=sh sw=2 ts=2 et
#
# generate-checksums.sh — regenerate public/checksum.txt
#
# Usage (from any directory):
# sh public/sh/generate-checksums.sh
#
# This script is also invoked by the CI workflows before running
# tests/installers.sh so that public/checksum.txt is always current.

set -eu

ROOT="$(
unset CDPATH
cd "$(dirname "$0")/../.." 2>/dev/null && pwd
)" || { printf '%s\n' "generate-checksums: cannot determine repository root" >&2; exit 1; }
Comment thread
Copilot marked this conversation as resolved.

CHECKSUM_FILE="${ROOT}/public/checksum.txt"

sha256_file() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
elif command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$1" | awk '{print $1}'
else
printf '%s\n' "generate-checksums: sha256sum or shasum is required" >&2
exit 1
fi
}

# Clear (or create) the checksum file before writing fresh entries with `: > file`.
: > "${CHECKSUM_FILE}"
for f in \
public/sh/install_zpmod.sh \
public/sh/install.sh \
public/sh/sync-init.sh \
public/zsh/init.zsh
do
hash="$(sha256_file "${ROOT}/${f}")"
printf '%s %s\n' "${hash}" "${f}" >> "${CHECKSUM_FILE}"
done

printf '%s\n' "Checksums written to ${CHECKSUM_FILE}"
25 changes: 25 additions & 0 deletions public/sh/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ while getopts ":i:a:b:" opt; do
done
shift $((OPTIND - 1))

# Validate BOPT to prevent sed delimiter injection when building init.zsh.
# | is the sed delimiter used in the substitution; \ and & are special in
# sed replacement expressions. The *[\\]* pattern matches a single backslash.
case "${BOPT}" in
# [\\] is a bracket expression for a literal backslash.
*'|'* | *[\\]* | *'&'* )
printf '%s\n' "-- ERROR -- Invalid -b value: branch name must not contain '|', '\\', or '&'." >&2
exit 1
;;
esac

SCRIPT_DIR=""
LOCAL_INIT_ZSH=""
LOCAL_INSTALL_ZPMOD=""
Expand Down Expand Up @@ -147,6 +158,20 @@ fi
command chmod a+x /tmp/zi/git-process-output.zsh

if test -d "${ZI_HOME}/${ZI_BIN_DIR_NAME}/.git"; then
_zi_valid=0
if test -f "${ZI_HOME}/${ZI_BIN_DIR_NAME}/zi.zsh"; then
# Canonical zi remote URLs (HTTPS and SSH, with and without .git suffix)
case "$(command git -C "${ZI_HOME}/${ZI_BIN_DIR_NAME}" remote get-url origin 2>/dev/null || true)" in
https://github.com/z-shell/zi | https://github.com/z-shell/zi.git \
| git@github.com:z-shell/zi | git@github.com:z-shell/zi.git)
_zi_valid=1 ;;
esac
fi
if [ "${_zi_valid}" -ne 1 ]; then
printf '%s\n' "▓▒░ ${ZI_HOME}/${ZI_BIN_DIR_NAME} contains a .git directory but does not appear to be a zi repository." >&2
printf '%s\n' "▓▒░ Expected zi.zsh and a z-shell/zi remote origin. Unset ZI_HOME/ZI_BIN_DIR_NAME or remove the directory to install fresh." >&2
exit 1
fi
cd "${ZI_HOME}/${ZI_BIN_DIR_NAME}" || exit 1
printf '%s\n' "▓▒░ Updating (z-shell/zi) plugin manager at ${ZI_HOME}/${ZI_BIN_DIR_NAME}"
command git clean -d -f -f
Expand Down
90 changes: 88 additions & 2 deletions tests/installers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ EOF
#!/usr/bin/env sh
set -eu

# Strip -C <dir> flag if present (used by install.sh to check remote URL)
if [ "${1:-}" = "-C" ]; then
[ -n "${2:-}" ] || { printf '%s\n' "installers.sh git test double: -C requires a directory argument" >&2; exit 64; }
shift 2
fi

cmd="${1:-}"
[ "$#" -gt 0 ] && shift

Expand All @@ -148,7 +154,7 @@ case "${cmd}" in
for arg do
dest="${arg}"
done
[ -n "${dest}" ] || { printf '%s\n' "git test double: missing clone destination" >&2; exit 64; }
[ -n "${dest}" ] || { printf '%s\n' "installers.sh git test double: missing clone destination" >&2; exit 64; }
mkdir -p "${dest}/.git" "${dest}/lib"
printf '%s\n' '# fake zi.zsh' > "${dest}/zi.zsh"
printf '%s\n' '# fake _zi completion' > "${dest}/lib/_zi"
Expand All @@ -158,8 +164,21 @@ case "${cmd}" in
log)
printf '%s\n' 'abcdef0 - fake zi commit (now) <test>'
;;
remote)
# After -C strip (if any) and cmd shift, $1/$2 hold the remote subcommand args
if [ "${1:-}" != "get-url" ]; then
printf '%s\n' "installers.sh git test double: expected remote subcommand 'get-url', got '${1:-<missing>}'" >&2
exit 65
fi
if [ "${2:-}" != "origin" ]; then
printf '%s\n' "installers.sh git test double: expected remote name 'origin', got '${2:-<missing>}'" >&2
exit 65
fi
# Return a zi remote URL; override via ZI_SRC_TEST_FAKE_REMOTE env var
printf '%s\n' "${ZI_SRC_TEST_FAKE_REMOTE:-https://github.com/z-shell/zi}"
;;
*)
printf '%s\n' "git test double: unexpected command ${cmd}" >&2
printf '%s\n' "installers.sh git test double: unexpected command ${cmd}" >&2
exit 65
;;
esac
Expand Down Expand Up @@ -226,6 +245,70 @@ test_standalone_zpmod_delegation() {
pass "standalone install.sh fetches zpmod helper"
}

test_update_valid_zi_clone() {
home="${TMP_ROOT}/update-valid-home"
data="${TMP_ROOT}/update-valid-data"
zi_bin="${data}/zi/bin"
command mkdir -p "${home}" "${zi_bin}/.git"
printf '%s\n' '# fake zi.zsh' >"${zi_bin}/zi.zsh"

HOME="${home}" \
ZDOTDIR="${home}" \
XDG_DATA_HOME="${data}" \
ZI_SRC_TEST_ROOT="${ROOT}" \
PATH="${FAKE_BIN}:${PATH}" \
sh "${ROOT}/public/sh/install.sh" -i skip >/dev/null

pass "update path accepts a valid zi clone"
}

test_update_rejects_foreign_repo() {
home="${TMP_ROOT}/update-foreign-home"
data="${TMP_ROOT}/update-foreign-data"
zi_bin="${data}/zi/bin"
command mkdir -p "${home}" "${zi_bin}/.git"
# Deliberately no zi.zsh: this simulates an unrelated git repo
err="${TMP_ROOT}/update-foreign-err"

set +e
HOME="${home}" \
ZDOTDIR="${home}" \
XDG_DATA_HOME="${data}" \
ZI_SRC_TEST_ROOT="${ROOT}" \
PATH="${FAKE_BIN}:${PATH}" \
sh "${ROOT}/public/sh/install.sh" -i skip >/dev/null 2>"${err}"
exit_code="$?"
set -e

[ "${exit_code}" -ne 0 ] || fail "install.sh should have rejected a foreign git repository"
contains "${err}" "does not appear to be a zi repository"
pass "update path rejects an unrecognised git repository"
}

test_update_rejects_wrong_remote() {
home="${TMP_ROOT}/update-wrong-remote-home"
data="${TMP_ROOT}/update-wrong-remote-data"
zi_bin="${data}/zi/bin"
command mkdir -p "${home}" "${zi_bin}/.git"
printf '%s\n' '# fake zi.zsh' >"${zi_bin}/zi.zsh"
err="${TMP_ROOT}/update-wrong-remote-err"

set +e
HOME="${home}" \
ZDOTDIR="${home}" \
XDG_DATA_HOME="${data}" \
ZI_SRC_TEST_ROOT="${ROOT}" \
ZI_SRC_TEST_FAKE_REMOTE="https://github.com/unrelated/project" \
PATH="${FAKE_BIN}:${PATH}" \
sh "${ROOT}/public/sh/install.sh" -i skip >/dev/null 2>"${err}"
exit_code="$?"
set -e

[ "${exit_code}" -ne 0 ] || fail "install.sh should have rejected a repo with a non-zi remote"
contains "${err}" "does not appear to be a zi repository"
pass "update path rejects a repository with a non-zi remote origin"
}

test_sync_init() {
local_file="${TMP_ROOT}/local-init.zsh"
remote_file="${TMP_ROOT}/remote-init.zsh"
Expand Down Expand Up @@ -265,4 +348,7 @@ write_fake_tools
test_loader_install
test_xdg_data_home_install
test_standalone_zpmod_delegation
test_update_valid_zi_clone
test_update_rejects_foreign_repo
test_update_rejects_wrong_remote
test_sync_init
Loading