Skip to content

fix(installer): execute-gate installed tools so a broken binary fails Step 1 (#411) - #451

Open
shujaatTracebloc wants to merge 4 commits into
developfrom
fix/411-execute-gate-tools
Open

fix(installer): execute-gate installed tools so a broken binary fails Step 1 (#411)#451
shujaatTracebloc wants to merge 4 commits into
developfrom
fix/411-execute-gate-tools

Conversation

@shujaatTracebloc

@shujaatTracebloc shujaatTracebloc commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Closes #411.

Problem

The only post-install "verification" was a log-only, failure-masking interpolation (Log "k3d: $(k3d version …)" / … 2>/dev/null || echo present). So a corrupt or wrong-architecture binary — winget shims / partial installs skip the direct path's checksum verify, and brew delivers with no checksum of ours (#429) — still printed ✔ System tools and only died at cluster-create in Step 2. Lukas's audit confirmed it's all three OSes.

Fix (both halves + macOS)

  • Shared execute-gate helpers: assert_tool_runs (common.sh, bash) and Assert-ToolRuns (install-k8s.ps1, PowerShell). Each runs the tool's self-check; on a non-zero exit or an exception it removes the located binary and fails loudly with an arch-aware remedy, so the tool step fails instead of the cluster step.
  • Gate kubectl / k3d / helm after install on Linux, macOS, and Windows — on both the fresh-install and already-present paths. System tools success only prints once all gates pass.
  • kubectl is gated with version --client, not --short (removed in kubectl 1.28+, which would false-fail the gate).
  • New check-drift.sh parity check (_drift_execute_gates) so no installer can silently drop a gate.

Acceptance criteria

  • A corrupt / wrong-arch k3d.exe fails Step 1 loudly with an actionable message; Step 2 is never reached (Err/error exits before it).
  • Coverage for the failure path — widened to bats + Pester + the drift check.

Tests (+11)

  • common.bats +2 (working tool passes & binary untouched / broken tool errors + removes the binary)
  • check-drift.bats +2 (execute-gate parity)
  • install-k8s.Tests.ps1 +7 Pester (exit-nonzero / exception / binary-removal / arch remedy / static gates)

Verified locally (CI-identical):

  • Pester full: 182 passed, 0 failed, 8 skipped (Windows-only)
  • all bats: 3 failures are pre-existing macOS-local flakes (my new tests all pass); ubuntu CI is the arbiter
  • PSScriptAnalyzer: 0 Error-severity · check-style / drift: clean · manifest regenerated

Note

Low Risk
Installer-only hardening with fail-fast behavior before cluster creation; no runtime auth, data, or cluster logic changes.

Overview
Replaces log-only post-install version checks (which could mask corrupt or wrong-arch binaries and still show ✔ System tools until cluster-create) with execute gates that actually run each tool’s self-check during Step 1.

Adds assert_tool_runs in common.sh and Assert-ToolRuns in install-k8s.ps1: on failure they remove the located binary when possible and exit with an arch-aware remediation message. kubectl, k3d, and helm are gated on Linux, macOS, and Windows for both fresh installs and already-on-PATH tools; kubectl uses version --client (not --short, which breaks on kubectl 1.28+).

Adds _drift_execute_gates in check-drift.sh plus bats/Pester coverage so installers cannot silently drop a gate; updates manifest.sha256 and setup-linux test mocks so gates see runnable tool stubs.

Reviewed by Cursor Bugbot for commit 328b78b. Bugbot is set up for automated code reviews on this repo. Configure here.

… Step 1 (#411)

The only post-install "verification" was a log-only, failure-masking
interpolation, so a corrupt or wrong-architecture binary (winget shims /
partial installs skip checksum verify; brew delivers with no checksum of ours)
still printed "System tools" and only died at cluster-create in Step 2.
All three OSes had the gap.

- New shared execute-gate helpers: assert_tool_runs (common.sh, bash) and
  Assert-ToolRuns (install-k8s.ps1, PowerShell). Each runs the tool's self-check;
  on non-zero exit or an exception it removes the located binary and fails loudly
  with an arch-aware remedy, so the tool step fails instead of the cluster step.
- Gate kubectl / k3d / helm after install on Linux (setup-linux.sh), macOS
  (setup-macos.sh), and Windows (install-k8s.ps1), on both the fresh-install and
  already-present paths. "System tools" success only prints once all gates pass.
- kubectl is gated with `version --client` (NOT --short — removed in kubectl
  1.28+, which would false-fail the gate).
- New check-drift.sh parity check (_drift_execute_gates) so no installer can
  silently drop a gate for a tool.

Tests: +2 bats (common — working tool passes / broken tool errors + removes the
binary), +2 bats (drift parity), +7 Pester (exit-nonzero / exception /
binary-removal / arch remedy / static gates).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@shujaatTracebloc shujaatTracebloc self-assigned this Jul 28, 2026
…-gate (#411)

The execute-gate (#411) runs `<tool> version` after install, but the
setup-linux.bats harness only marked tools "present" via has() — it never
provided a RUNNABLE k3d/kubectl/helm. On the toolless CI runner the gate's
probe found no binary and failed 4 install_k3d tests; locally it false-passed
because a real k3d was on PATH. Add silent (non-recording) runnable stubs to
setup() so the gate has something to execute and they shadow any real tool on a
dev host. Test-only; no production change.
…te-tools

# Conflicts:
#	scripts/manifest.sha256
#	scripts/tests/install-k8s.Tests.ps1
…te-tools

# Conflicts:
#	scripts/manifest.sha256
#	scripts/tests/check-drift.bats
#	scripts/tests/check-drift.sh
#	scripts/tests/install-k8s.Tests.ps1
@shujaatTracebloc
shujaatTracebloc marked this pull request as ready for review July 28, 2026 15:40

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 328b78b. Configure here.

if [[ ! -f "$DRIFT_ROOT/$ps1" ]]; then _note "$ps1 is missing"; fi
for t in kubectl k3d helm; do
grep -qE "Assert-ToolRuns -Name \"$t\"" "$DRIFT_ROOT/$ps1" || \
_note "$ps1: no execute-gate for '$t' (Assert-ToolRuns -Name \"$t\") — #411"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drift gate uses whole-file grep

Medium Severity

_drift_execute_gates scans entire installer files for assert_tool_runs / Assert-ToolRuns tokens. That can match comments or other non-call text, so deleting a real gate can still pass if the token lingers elsewhere — the same fail-open pattern that broke preflight host parity in #450. The next check in this file already documents why probe-shaped extractors are required.

Fix in Cursor Fix in Web

Triggered by learned rule: Drift/parity checks must extract structured entries — no whole-file grep

Reviewed by Cursor Bugbot for commit 328b78b. Configure here.

Comment thread scripts/install-k8s.ps1
if (-not (Has "helm")) { Err "Helm could not be installed. Install manually from https://helm.sh/docs/intro/install/ and re-run." }
}
Log "helm: $(cmd /c 'helm version --short 2>&1')"
Assert-ToolRuns -Name "helm" -VersionArgs @("version","--short") -BinPath "$TOOL_DIR\helm.exe"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong binary removed on gate fail

Medium Severity

Assert-ToolRuns for k3d and helm always passes -BinPath under TOOL_DIR, including already-present and winget paths where PATH may resolve a different binary. On gate failure the helper deletes that TOOL_DIR path, which can remove a good leftover copy while the broken package-manager binary that actually failed stays on PATH. kubectl only passes -BinPath on the fresh direct-download path.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 328b78b. Configure here.

Comment thread scripts/lib/common.sh
return 0
fi
local path; path="$(command -v "$name" 2>/dev/null || true)"
[[ -n "$path" && -w "$path" ]] && rm -f "$path" 2>/dev/null || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rm fires on the already-present path too (macOS gates all three unconditionally after has), so a broken brew-managed binary gets its symlink deleted — but brew install on re-run sees the formula already installed and won't relink, so the gate fails again → confusing loop. Note the Windows side deliberately omits -BinPath on the already-present calls (815) to avoid removing a tool it didn't install; the bash gate has no such guard. Consider only removing when we placed the binary (i.e. pass the dest path like the fresh-install callers, and skip removal otherwise).

spin_cmd "Installing system tools…" brew install helm
fi
log "helm: $(helm version --short 2>/dev/null || echo installed)"
assert_tool_runs helm version --short

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kubectl was moved off --short here because it was removed in 1.28+ and would false-fail the gate — but helm is still gated with version --short on all three OSes. Worth confirming the pinned Helm (v4.x) still supports --short; if it ever drops it the gate false-fails and deletes a perfectly good binary. helm version --template / bare helm version would be immune.

Comment thread scripts/install-k8s.ps1
}
}
Log "k3d: $(k3d version | Select-Object -First 1)"
Assert-ToolRuns -Name "k3d" -VersionArgs @("version") -BinPath "$TOOL_DIR\k3d.exe"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gate runs on the already-present path too, but always points -BinPath at $TOOL_DIR\k3d.exe. If k3d came from winget/choco (or was already on PATH), the broken binary is elsewhere, so Remove-Item is a no-op and Has "k3d" stays true on re-run → same failure every time. The remedy text does tell the user to uninstall the package-manager copy, so this is loud-but-stuck rather than silent; just flagging that the auto-remove can't actually self-heal that path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants