Skip to content

Harden download -> verify -> execute chain (v1.121.9)#77

Merged
TheAbider merged 1 commit into
masterfrom
fix/download-execute-hardening
Jul 1, 2026
Merged

Harden download -> verify -> execute chain (v1.121.9)#77
TheAbider merged 1 commit into
masterfrom
fix/download-execute-hardening

Conversation

@TheAbider

Copy link
Copy Markdown
Owner

Fixes from an adversarial audit of the installer download → verify → execute chain (FileServer, Agent Installer, ISO download, Utilities staging). Verified findings only; each fix mirrors an existing in-repo hardening pattern.

FileServer (39-FileServer.ps1)

  • Configured installer hash is now enforced without a sidecar (HIGH). A caller/config-supplied ExpectedHash was skipped when the server published no .sha256 sidecar — exactly its documented RMM use case — dropping to a weaker size-only prompt. It's now enforced inside the NoSidecar branch; a mismatch fails closed, a match is full verification.
  • Cloudflare Access token no longer resent on redirects (LOW). The resume, HEAD, and hash-sidecar requests set AllowAutoRedirect=$false; the main download now streams via HttpWebRequest (no WebClient) and treats a 3xx as a hard failure. This keeps the service token from being sent to a redirect target on another host — and avoids a WebClient subclass (Add-Type) that can't compile on both Windows PowerShell 5.1 and pwsh 7 (verified). Redirect-refusal was proven with a local HttpListener test (200 streams, 302 refused).

Agent Installer (57-AgentInstaller.ps1)

  • HashManifest lookup uses .ContainsKey (MEDIUM). $script:AgentInstaller is a hashtable, so the old .PSObject.Properties.Name -contains 'HashManifest' was always false → ExpectedHash never loaded (the whole config-hash gate was dead). Now matches the correct RequireHash idiom.
  • No false SUCCESS on reinstall (MEDIUM). On a reinstall/upgrade, an install that timed out or was Escape-skipped could report SUCCESS (and leave a hung installer running) off the pre-existing service. Both paths now require -not $preInstalled to claim success; a skipped-but-running installer is left to finish rather than killed.
  • Protected staging before execution (MEDIUM). The verified EXE is moved into an Administrators/SYSTEM-only dir before running as SYSTEM, closing the verify→execute TOCTOU. Mirrors the self-update staging.
  • Arg quoting (LOW). Space-bearing InstallArgs tokens (/token "value with spaces") are quoted so PS 5.1's Start-Process doesn't re-split them.

Utilities (35-Utilities.ps1)

  • New Move-ToProtectedStaging helper — re-asserts an Administrators+SYSTEM DACL on every call (closing a pre-created-weak-dir gap the inline self-update copy has).

ISO download (42-ISODownload.ps1)

  • Rollback pointer no longer leaks (LOW). The low-disk-space re-download bail now restores the .old fallback and clears $script:_isoRollbackPath, instead of stranding the renamed ISO and mis-acting on a later download for a different version.

Verification

  • New Section 196 (17 asserts) covering all seven fixes; 3 stale tests updated to the new behavior (WebClient→HttpWebRequest; NoSidecar+ExpectedHash gate).
  • Full suite 5308/5308; PSSA 0 findings (project settings). Monolithic rebuilt, parse check passed. Version stamps → 1.121.9.

Fixes from an adversarial audit of the installer download/verify/execute path
(FileServer, Agent Installer, ISO download, Utilities staging).

39-FileServer.ps1:
- Enforce a caller/config-supplied ExpectedHash even when the file server publishes
  no .sha256 sidecar (previously the configured hash was skipped in exactly its
  documented RMM use case, dropping to a weaker size-only prompt). A mismatch now
  fails closed.
- Stop resending the Cloudflare Access token on HTTP redirects: the resume, HEAD,
  and hash-sidecar requests set AllowAutoRedirect=false, and the main download now
  streams via HttpWebRequest (no WebClient) and treats a 3xx as a hard failure.
  Avoids leaking the service token to a redirect target on another host, and dodges
  the WebClient-subclass Add-Type that can't compile on both PS 5.1 and pwsh 7.

57-AgentInstaller.ps1:
- HashManifest lookup uses .ContainsKey (hashtable idiom) so ExpectedHash actually
  loads (the PSObject idiom returned .NET members, never the keys -> dead gate).
- Reinstall/upgrade no longer reports SUCCESS from the pre-existing service on a
  timeout or Escape-skip; a skipped-but-running installer is left to finish.
- Move the verified installer into an Administrators/SYSTEM-only stage before
  running it as SYSTEM (TOCTOU between verify and execute).
- Quote space-bearing InstallArgs tokens so Start-Process doesn't re-split them.

35-Utilities.ps1: add Move-ToProtectedStaging helper (re-asserts an Admin+SYSTEM
DACL each call, closing a pre-created-weak-dir gap the inline self-update copy has).

42-ISODownload.ps1: the low-space re-download bail restores the .old fallback and
clears the script-scope rollback pointer instead of stranding it and mis-acting on
a later download for a different version.

Tests: Section 196 (17 asserts) + 3 stale tests updated to the new behavior;
5308/5308 passing. PSSA 0. Monolithic + version stamps -> 1.121.9.
@TheAbider TheAbider merged commit 14f2d94 into master Jul 1, 2026
6 checks passed
@TheAbider TheAbider deleted the fix/download-execute-hardening branch July 1, 2026 12:41
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

1 participant