Harden the bootstrap, pin the supply chain, and prune dead config - #91
Merged
Conversation
Result of a full audit of this repo. The changes that matter most are correctness fixes on paths CI structurally cannot reach: a fresh Apple Silicon bootstrap could not complete, and the install one-liner the README recommends destroyed uncommitted work without warning. Bootstrap - scripts/install_dotfiles.sh: put brew on PATH after installing it. Homebrew's installer does not touch the calling shell, and /opt/homebrew/bin is not on the default Apple Silicon PATH, so the `brew bundle` in the darwin script exited 127 and took the whole `chezmoi apply` with it. GitHub's macOS runners ship brew already on PATH, which is why CI never caught this. - scripts/install_dotfiles.sh: gate the destructive reset behind DOTFILES_FORCE_RESET. The default path now fast-forwards and refuses to run against a dirty tree, instead of `reset --hard` + `clean -fdx`. - scripts/install_dotfiles.sh: verify the Xcode CLI tools install actually succeeded, and clean up the softwareupdate lockfile. - darwin/...homebrew: honour HOMEBREW_CASK_OPTS at apply time so casks land in ~/Applications with --require-sha. Casks upstream ships as `sha256 :no_check` install in a second pass without that flag. Supply chain - .chezmoiexternal.yaml: pin all 14 externals to tags (one commit, where upstream publishes no tags) and add checksum.sha256 to each. Every entry previously tracked master, unverified, on a weekly refresh — arbitrary code execution in every shell and tmux session for anyone able to push to any one of those repos. - linux installs: pin lazygit and verify its release checksum, verify rustup-init before running it, and check the eza signing key fingerprint before trusting that apt repo. - install.sh: pin the chezmoi version fetched from get.chezmoi.io. Shell - zsh/exports.zsh: stop forcing TERM=xterm-256color. It overwrote Ghostty's xterm-ghostty and tmux's tmux-256color, and lied outright on the Linux virtual console. Now falls back only when the terminal has no terminfo entry at all, detected via zsh/terminfo without a fork. - fzf/fzf.zsh: wire fzf up with `fzf --zsh`. The old brew install step only wrote an ~/.fzf.zsh that nothing sourced, so Ctrl-T and Alt-C were never bound and the FZF_*_COMMAND exports were unreachable. - dot_zshenv: new. DOTFILES, XDG base dirs and EDITOR, so non-interactive shells, cron and git hooks see them. PATH deliberately stays out: /etc/zprofile runs path_helper afterwards and demotes prepends. - dot_zshrc, zsh/completion.zsh: move the completion caches under $XDG_CACHE_HOME; only fork mkdir when the directory is missing; guard stty behind a tty test so scripted shells stop printing an error. - system/alii.zsh: inline the Linux aliases behind an $OSTYPE guard, replacing a run_onchange script that appended to a gitignored file and so both duplicated entries and lost them to `git clean -fdx`. - macos/alii.zsh: guard on darwin, and fix four aliases using fish's `; and` — in zsh those ran "command not found: and". - system/alii.zsh, bin/enum: fix GNU-only `stat -c` and `sed -i` on BSD. - git/git-alii.zsh: point gbr/gpr at gh (hub is not installed); drop a duplicate grb definition. Public repo hygiene - dot_gitconfig.tmpl: move employer-specific config behind an untracked ~/.gitconfig.work include. Nothing sensitive was ever committed — this is org topology, not credentials — but it does not belong here. CI - .github/workflows/ci.yaml: apply the checkout instead of cloning master, so pull requests finally test their own code. Add shellcheck (matrixed over both OSes, since each platform's chezmoi scripts render empty on the other), gitleaks, and an idempotence gate. Bump actions/checkout v4 -> v7.0.1. Removals - Parrot OS support, retired. Also removes the script that read a password from ~/Desktop and passed it as argv to expect, where ps exposed it to any local user. - iterm2/ (Ghostty now), vagrant/alii.zsh (not installed), zsh/zshenv (unreachable: wrong name and location to ever be sourced), scripts/install_xcode_cli_tools.sh (never invoked; its two useful safety checks were folded into the bootstrap), and vim's Tagbar plus the long-commented-out Syntastic block.
The first CI run failed on lint (ubuntu-latest) while lint (macos-latest) passed on identical code. Ubuntu 24.04's apt shellcheck lags Homebrew's by several releases and the two disagree about which findings to report, so an unpinned gate is non-deterministic: it depends on which runner you land on. - .github/workflows/ci.yaml: install one pinned shellcheck (v0.11.0) from upstream releases on both runners instead of apt/brew, selecting the right platform tarball from $RUNNER_OS and uname -m. - bin/secret: replace `[ "$#" -gt 0 ] && shift || true` with a plain if/then. Same behaviour -- verified no-args, help, unknown subcommand and argument passthrough are unchanged -- but the old idiom trips SC2015, since C can run when A succeeds and B fails.
Second CI failure: lint (ubuntu-latest) tried to shellcheck the darwin
Homebrew script and reported SC2148 (no shebang). On Linux that template
renders to a single newline rather than nothing, because its guard closed
with `{{ end }}` while every sibling uses `{{- end -}}`. One byte is not
empty, so the job's skip check let it through.
- darwin/...homebrew: close the guard with `{{- end -}}`, matching the
other four platform-guarded scripts. All five now render 0 bytes when
guarded off; verified the script is unchanged when guarded on.
- ci.yaml: skip a rendered template when it has no non-whitespace
content, rather than testing `[ -s ]`. Belt and braces, so a future
template with a stray newline cannot resurrect this.
Gating it on `push` meant scripts/install_dotfiles.sh was shellchecked but
never executed on a PR -- and that file holds the Homebrew-on-PATH fix, the
DOTFILES_FORCE_RESET gate and the Xcode CLI tools check. The `apply` job
runs install.sh, which is a different entry point, so the highest-severity
fix in this branch was merging on a lint pass alone.
The previous comment claimed the path "can only be tested after a merge".
That was wrong: install_dotfiles.sh clones whatever DOTFILES_BRANCH names,
and a PR branch exists on the upstream remote. Only forks genuinely cannot
be tested this way, since GITHUB_HEAD_REF there names a branch on the fork.
- ci.yaml: gate on push OR not-a-fork, and pass
DOTFILES_BRANCH=${{ github.head_ref || github.ref_name }} so the job
clones the branch under test rather than master.
- ci.yaml: correct the comment about DOTFILES_FORCE_RESET -- a fresh runner
has no source directory, so it is a no-op guard, not a requirement.
Still cannot reproduce the Apple Silicon PATH failure itself: GitHub's
macOS runners ship Homebrew already on PATH. Noted in the job comment.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Outcome of a full read-only audit of this repo, then the fixes. 35 files, +615/−6223.
The two changes that matter most are correctness fixes on paths CI structurally could not reach.
Why
A fresh Apple Silicon bootstrap could not complete.
scripts/install_dotfiles.shinstalled Homebrew and immediatelyexecd intochezmoi init --apply, whose first darwin script runsbrew bundleunderset -e. Homebrew's installer does not modify the calling shell, and/opt/homebrew/binis not on the default Apple SiliconPATH— sobrewwas not found, the script exited 127, and the entire apply aborted. GitHub's macOS runners ship Homebrew already onPATH, which is precisely why CI never caught it.The install one-liner in the README destroyed uncommitted work. It ran
git checkout -B+reset --hard FETCH_HEAD+clean -fdxagainst~/.local/share/chezmoiunconditionally, with no warning in the docs.Nothing verified upstream code. All 14
.chezmoiexternal.yamlentries trackedmasterwith no checksum on a 168h auto-refresh. Those files are sourced into every interactive shell and every tmux session, so anyone able to push to any one of those repos had automatic code execution here within a week.Changes
Bootstrap
brewonPATHafter installing it, via prefix detection (/opt/homebrew, then/usr/local). Applied in bothinstall_dotfiles.shand the darwin Homebrew script, sochezmoi applyis self-sufficient outside the bootstrap.DOTFILES_FORCE_RESET. The default path fast-forwards and refuses to run on a dirty tree. CI sets the flag.softwareupdatelockfile that was previously created and left behind.HOMEBREW_CASK_OPTSat apply time. Casks upstream ships assha256 :no_checkinstall in a second pass without--require-sha, so enforcement is the default and the exceptions are explicit.Supply chain
checksum.sha256to each.rustup-initbefore executing it; check the eza signing key fingerprint before trusting that apt repo.get.chezmoi.io.Shell
TERM=xterm-256color. It overwrote Ghostty'sxterm-ghosttyand tmux'stmux-256color, and lied outright on the Linux virtual console (TERM=linux, 8 colours). Now falls back only when the terminal has no terminfo entry at all — detected throughzsh/terminfowith no fork.~/.fzf.zshthat nothing sourced, soCtrl-T/Alt-Cwere unbound and the threeFZF_*_COMMANDexports were unreachable. Replaced withfzf --zshthrough the existingcached-evalhelper.dot_zshenvforDOTFILES, XDG base dirs andEDITOR, so non-interactive shells, cron and git hooks see them.PATHdeliberately stays out —/etc/zprofilerunspath_helperafterwards and demotes prepends (measured: asdf shims position 1 → 13).$XDG_CACHE_HOME; guardsttybehind a tty test; only forkmkdirwhen the cache dir is genuinely missing.run_onchangescript that appended Linux aliases to a gitignored file — it duplicated entries on every edit and lost them entirely togit clean -fdx— with an inline$OSTYPEguard.; and(zsh rancommand not found: and), GNU-onlystat -con macOS, GNU-onlysed -ion BSD,hubsubcommands withhubuninstalled, and a duplicategrb.macos/alii.zshon darwin; it was previously sourced on Linux too.Public-repo hygiene
~/.gitconfig.workinclude. No credentials were ever committed —gitleaksover all 239 commits reports clean — this is org topology only. Note this does not purge history; the strings remain in an earlier commit, which was judged not worth afilter-reporewrite.CI
master, so pull requests finally test their own code. Previouslyactions/checkoutoutput was discarded and every PR validatedmaster.shellcheck, matrixed over both OSes — each platform's chezmoi scripts render to an empty string on the other, so a single-OS lint job would silently skip half of them.gitleaksand an idempotence gate (two applies, then assert no drift). The gate ischezmoi status --exclude=scripts, notchezmoi verify: the four plainrun_after_scripts are meant to run every apply, so bareverifycan never exit 0.actions/checkoutv4 → v7.0.1.Removals
~/Desktopand passed it as argv toexpect, wherepsexposed it to any local user.iterm2/(Ghostty now),vagrant/alii.zsh(not installed),zsh/zshenv(unreachable — wrong name and location to ever be sourced),scripts/install_xcode_cli_tools.sh(never invoked; its two useful safety checks were folded into the bootstrap), and vim's Tagbar plus the long-commented-out Syntastic block.Verification
shellcheck— plain scripts + all 7 rendered templates, both platformszsh -n— every zsh fileSHA256 mismatchpowerline/default/blue.tmuxtheme,tpm, all*.tmux)DOTFILES_FORCE_RESETTERMguardvt100/vt220/dumb; overrides only genuinely-missing onesfzf-file-widgetdefined (was 0/0)gitleaks$HOMEbin/,terminal/all correctly ignoredNot verified — worth reviewer attention. The Linux install path and all CI jobs are verified by rendering, linting and local simulation, not by running on Debian or a GitHub runner. The first CI run is the real proof. The Apple Silicon bootstrap fix is likewise code-path evidence; confirming it needs a clean VM.
Required after merge
Answering yes to "Install Mac applications" triggers ~33 formulae plus two font casks on the next apply.
~/.gitconfig.workmust exist on work machines. Git silently ignores a missing include, so the failure mode is "rewrite stops happening", not an error.