* Remove unsafe project bin PATH injection * Cover customized unsafe Mise paths * Revoke legacy Mise Work trust * Harden legacy Mise trust cleanup * Preserve ignored Mise Work configs * Scope Mise path cleanup to env * Accept paranoid Mise ignore marker Reported-by: infosec-us-team
271 lines
10 KiB
Bash
271 lines
10 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
|
|
|
require_command mise
|
|
|
|
test_dir=$(mktemp -d)
|
|
trap 'rm -rf "$test_dir"' EXIT
|
|
migration="$ROOT/migrations/1789095456.sh"
|
|
|
|
run_migration() {
|
|
local test_home="$1"
|
|
|
|
env -i \
|
|
HOME="$test_home" \
|
|
XDG_CACHE_HOME="$test_home/.cache" \
|
|
XDG_CONFIG_HOME="$test_home/.config" \
|
|
XDG_DATA_HOME="$test_home/.local/share" \
|
|
XDG_STATE_HOME="$test_home/.local/state" \
|
|
MISE_PARANOID="${OMARCHY_TEST_MISE_PARANOID:-false}" \
|
|
PATH=/usr/bin \
|
|
bash -euo pipefail "$migration"
|
|
}
|
|
|
|
run_mise() {
|
|
local test_home="$1"
|
|
shift
|
|
|
|
env -i \
|
|
HOME="$test_home" \
|
|
XDG_CACHE_HOME="$test_home/.cache" \
|
|
XDG_CONFIG_HOME="$test_home/.config" \
|
|
XDG_DATA_HOME="$test_home/.local/share" \
|
|
XDG_STATE_HOME="$test_home/.local/state" \
|
|
MISE_PARANOID="${OMARCHY_TEST_MISE_PARANOID:-false}" \
|
|
PATH=/usr/bin \
|
|
mise "$@"
|
|
}
|
|
|
|
mise_environment() {
|
|
local test_home="$1"
|
|
local project="$2"
|
|
|
|
(
|
|
cd "$project"
|
|
run_mise "$test_home" env -s bash
|
|
)
|
|
}
|
|
|
|
mise_path_active() {
|
|
local test_home="$1"
|
|
local project="$2"
|
|
local output
|
|
|
|
if ! output=$(mise_environment "$test_home" "$project" 2>/dev/null); then
|
|
return 1
|
|
fi
|
|
|
|
grep -F "$project/bin" <<<"$output" >/dev/null
|
|
}
|
|
|
|
assert_unsafe_variant_removed() {
|
|
local variant="$1"
|
|
local assignment="$2"
|
|
local variant_home="$test_dir/$variant-home"
|
|
local variant_config="$variant_home/Work/.mise.toml"
|
|
local variant_project="$variant_home/Work/tries/untrusted-repository"
|
|
|
|
mkdir -p "$variant_project/bin"
|
|
printf '[env]\n%s\n' "$assignment" >"$variant_config"
|
|
run_mise "$variant_home" trust "$variant_config" >/dev/null
|
|
|
|
mise_path_active "$variant_home" "$variant_project" || fail "$variant legacy config prepends the repository bin directory"
|
|
|
|
run_migration "$variant_home" >/dev/null
|
|
if mise_path_active "$variant_home" "$variant_project"; then
|
|
fail "$variant repository bin directory remains in PATH after migration"
|
|
fi
|
|
}
|
|
|
|
install_home="$test_dir/install-home"
|
|
install_log="$test_dir/install-mise.log"
|
|
mkdir -p "$install_home" "$test_dir/bin"
|
|
cat >"$test_dir/bin/mise" <<'SH'
|
|
#!/bin/bash
|
|
printf '%s\n' "$*" >>"$MISE_TEST_LOG"
|
|
SH
|
|
chmod +x "$test_dir/bin/mise"
|
|
|
|
env \
|
|
HOME="$install_home" \
|
|
MISE_TEST_LOG="$install_log" \
|
|
OMARCHY_SETUP_CONTEXT=runtime \
|
|
PATH="$test_dir/bin:/usr/bin" \
|
|
bash -euo pipefail -c 'source "$1"' bash "$ROOT/install/user/mise-work.sh"
|
|
|
|
[[ -d $install_home/Work/tries ]] || fail "installer creates the work and tries directories"
|
|
[[ ! -e $install_home/Work/.mise.toml ]] || fail "installer does not create a trusted Work Mise config"
|
|
[[ $(<"$install_log") == "use -g node@latest" ]] || fail "installer only invokes Mise for the global Node setup"
|
|
pass "new installs do not add project bin directories to PATH"
|
|
|
|
stock_home="$test_dir/stock-home"
|
|
stock_config="$stock_home/Work/.mise.toml"
|
|
stock_project="$stock_home/Work/tries/untrusted-repository"
|
|
mkdir -p "$stock_project/bin"
|
|
cat >"$stock_config" <<'TOML'
|
|
[env]
|
|
_.path = "{{ cwd }}/bin"
|
|
TOML
|
|
run_mise "$stock_home" trust "$stock_config" >/dev/null
|
|
|
|
mise_path_active "$stock_home" "$stock_project" || fail "legacy config prepends the repository bin directory"
|
|
|
|
run_migration "$stock_home" >/dev/null
|
|
[[ ! -e $stock_config ]] || fail "migration removes the stock Work Mise config"
|
|
cat >"$stock_config" <<'TOML'
|
|
[env]
|
|
_.path = "{{ cwd }}/bin"
|
|
TOML
|
|
if mise_path_active "$stock_home" "$stock_project"; then
|
|
fail "recreated Work config remains trusted after migration"
|
|
fi
|
|
run_migration "$stock_home" >/dev/null
|
|
[[ ! -e $stock_config ]] || fail "stock migration is idempotent"
|
|
pass "migration removes the repository bin directory and revokes the Work trust root"
|
|
|
|
assert_unsafe_variant_removed inline-comment '_.path = "{{ cwd }}/bin" # Omarchy default'
|
|
assert_unsafe_variant_removed single-quoted "_.path = '{{ cwd }}/bin'"
|
|
pass "migration removes annotated and single-quoted project bin paths"
|
|
|
|
custom_home="$test_dir/custom-home"
|
|
custom_config="$custom_home/Work/.mise.toml"
|
|
mkdir -p "$(dirname "$custom_config")"
|
|
cat >"$custom_config" <<'TOML'
|
|
[env]
|
|
KEEP = "yes"
|
|
_.path = "{{ cwd }}/bin"
|
|
# _.path = "{{ cwd }}/bin"
|
|
|
|
[tools]
|
|
ruby = "latest"
|
|
|
|
[other]
|
|
_.path = "{{ cwd }}/bin"
|
|
TOML
|
|
cp "$custom_config" "$test_dir/custom-original"
|
|
cat >"$test_dir/custom-expected" <<'TOML'
|
|
[env]
|
|
KEEP = "yes"
|
|
# _.path = "{{ cwd }}/bin"
|
|
|
|
[tools]
|
|
ruby = "latest"
|
|
|
|
[other]
|
|
_.path = "{{ cwd }}/bin"
|
|
TOML
|
|
chmod 600 "$custom_config"
|
|
run_mise "$custom_home" trust "$custom_config" >/dev/null
|
|
|
|
custom_output=$(run_migration "$custom_home")
|
|
cmp -s "$test_dir/custom-expected" "$custom_config" || fail "migration preserves unrelated custom Mise settings"
|
|
[[ $(stat -c %a "$custom_config") == "600" ]] || fail "migration preserves custom config permissions"
|
|
grep -F "mise trust $custom_config" <<<"$custom_output" >/dev/null || fail "migration explains how to review and re-trust a custom config"
|
|
custom_backups=("$custom_config".bak.*)
|
|
[[ -f ${custom_backups[0]} ]] || fail "migration backs up a customized Mise config"
|
|
(( ${#custom_backups[@]} == 1 )) || fail "migration creates one custom config backup"
|
|
cmp -s "$test_dir/custom-original" "${custom_backups[0]}" || fail "custom config backup preserves the original"
|
|
|
|
run_migration "$custom_home" >/dev/null
|
|
custom_backups=("$custom_config".bak.*)
|
|
(( ${#custom_backups[@]} == 1 )) || fail "custom migration does not create another backup on rerun"
|
|
cmp -s "$test_dir/custom-expected" "$custom_config" || fail "custom migration is idempotent"
|
|
pass "custom Mise settings, permissions, and original backup survive the repair"
|
|
|
|
unrelated_home="$test_dir/unrelated-home"
|
|
unrelated_config="$unrelated_home/Work/.mise.toml"
|
|
unrelated_project="$unrelated_home/Work/tries/untrusted-repository"
|
|
mkdir -p "$unrelated_project/bin"
|
|
printf '[env]\nKEEP = "yes"\n' >"$unrelated_config"
|
|
cp "$unrelated_config" "$test_dir/unrelated-original"
|
|
run_mise "$unrelated_home" trust "$unrelated_config" >/dev/null
|
|
run_migration "$unrelated_home" >/dev/null
|
|
cmp -s "$test_dir/unrelated-original" "$unrelated_config" || fail "unrelated Mise config remains unchanged"
|
|
unrelated_backups=("$unrelated_config".bak.*)
|
|
[[ ! -e ${unrelated_backups[0]} ]] || fail "unchanged Mise config is not backed up"
|
|
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$unrelated_config"
|
|
if mise_path_active "$unrelated_home" "$unrelated_project"; then
|
|
fail "safe Work config retains its old trust grant"
|
|
fi
|
|
|
|
absent_home="$test_dir/absent-home"
|
|
absent_config="$absent_home/Work/.mise.toml"
|
|
absent_project="$absent_home/Work/tries/untrusted-repository"
|
|
mkdir -p "$(dirname "$absent_config")"
|
|
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$absent_config"
|
|
run_mise "$absent_home" trust "$absent_config" >/dev/null
|
|
rm "$absent_config"
|
|
rmdir "$absent_home/Work"
|
|
run_migration "$absent_home" >/dev/null
|
|
[[ ! -e $absent_home/Work ]] || fail "migration does not retain a temporary Work directory"
|
|
mkdir -p "$absent_project/bin"
|
|
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$absent_config"
|
|
if mise_path_active "$absent_home" "$absent_project"; then
|
|
fail "deleted Work directory retains its stale trust grant"
|
|
fi
|
|
pass "migration leaves unrelated configs alone and revokes dangling Work trust"
|
|
|
|
paranoid_home="$test_dir/paranoid-home"
|
|
paranoid_work="$paranoid_home/Work"
|
|
paranoid_config="$paranoid_work/.mise.toml"
|
|
paranoid_project="$paranoid_work/tries/untrusted-repository"
|
|
mkdir -p "$paranoid_project/bin"
|
|
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$paranoid_config"
|
|
OMARCHY_TEST_MISE_PARANOID=true run_mise "$paranoid_home" trust "$paranoid_config" >/dev/null
|
|
OMARCHY_TEST_MISE_PARANOID=true mise_path_active "$paranoid_home" "$paranoid_project" || fail "paranoid legacy config prepends the repository bin directory"
|
|
rm -r "$paranoid_work"
|
|
|
|
OMARCHY_TEST_MISE_PARANOID=true run_migration "$paranoid_home" >/dev/null
|
|
[[ ! -e $paranoid_work ]] || fail "paranoid migration removes its temporary Work directory"
|
|
mkdir -p "$paranoid_project/bin"
|
|
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$paranoid_config"
|
|
if OMARCHY_TEST_MISE_PARANOID=true mise_path_active "$paranoid_home" "$paranoid_project"; then
|
|
fail "paranoid migration retains content-bound trust for the deleted legacy config"
|
|
fi
|
|
pass "migration revokes stale content-bound trust in Mise paranoid mode"
|
|
|
|
ignored_home="$test_dir/ignored-home"
|
|
ignored_config="$ignored_home/Work/.mise.toml"
|
|
ignored_project="$ignored_home/Work/tries/untrusted-repository"
|
|
mkdir -p "$ignored_project/bin"
|
|
cat >"$ignored_config" <<'TOML'
|
|
[env]
|
|
_.path = "{{ cwd }}/bin"
|
|
KEEP = "yes"
|
|
TOML
|
|
run_mise "$ignored_home" trust "$ignored_config" >/dev/null
|
|
run_mise "$ignored_home" trust --ignore "$ignored_config" >/dev/null
|
|
|
|
ignored_output=$(run_migration "$ignored_home")
|
|
grep -F '{{ cwd }}/bin' "$ignored_config" >/dev/null && fail "ignored config retains the unsafe path"
|
|
grep -Fx 'KEEP = "yes"' "$ignored_config" >/dev/null || fail "ignored config keeps unrelated settings"
|
|
ignored_entries=("$ignored_home/.local/state/mise/ignored-configs/"*)
|
|
[[ -L ${ignored_entries[0]} ]] || fail "migration preserves the explicit Mise ignore marker"
|
|
(( ${#ignored_entries[@]} == 1 )) || fail "migration preserves exactly one Mise ignore marker"
|
|
ignored_target=$(readlink "${ignored_entries[0]}")
|
|
[[ $ignored_target == $ignored_home/Work || $ignored_target == $ignored_config ]] || fail "preserved Mise ignore marker still targets the Work config"
|
|
grep -F "remains ignored by Mise" <<<"$ignored_output" >/dev/null || fail "migration reports that the custom config remains ignored"
|
|
if mise_path_active "$ignored_home" "$ignored_project"; then
|
|
fail "ignored config becomes active after migration"
|
|
fi
|
|
pass "migration preserves an explicit decision to ignore the Work config"
|
|
|
|
symlink_home="$test_dir/symlink-home"
|
|
symlink_config="$symlink_home/Work/.mise.toml"
|
|
symlink_target="$test_dir/dotfiles-mise.toml"
|
|
mkdir -p "$(dirname "$symlink_config")"
|
|
cat >"$symlink_target" <<'TOML'
|
|
[env]
|
|
_.path = "{{ cwd }}/bin"
|
|
KEEP = "yes"
|
|
TOML
|
|
ln -s "$symlink_target" "$symlink_config"
|
|
|
|
run_migration "$symlink_home" >/dev/null
|
|
[[ -L $symlink_config ]] || fail "migration preserves a dotfile symlink"
|
|
grep -F '{{ cwd }}/bin' "$symlink_target" >/dev/null && fail "symlink target retains the unsafe path"
|
|
grep -Fx 'KEEP = "yes"' "$symlink_target" >/dev/null || fail "symlink target keeps unrelated settings"
|
|
pass "custom dotfile symlinks survive the repair"
|