Trust, Then Verify the Release Asset: Lessons from the OVHcloud CLI Binary Swap

Attackers used a developer's hijacked GitHub account to replace release binaries of the OVHcloud CLI for about 90 minutes. Here is what the advisory says happened, how a CI hash pin caught it, and how to verify downloads in Go and Python.

What happened

On 19 October 2026 the release binaries of the OVHcloud CLI (ovhcloud-cli) were swapped for malicious ones. We are relying on two primary pages here: OVHcloud’s security advisory, GHSA-862f-55f5-vmvw, and the customer-filed issue #300. The trade press write-up at The Stack adds colour.

According to the advisory:

  • Attackers used a compromised developer GitHub account to replace release assets for v0.11.0 to v0.15.0. The source code and build chain were not affected.
  • The malicious binaries were available for about 1h33, between 14:47 and 16:21 (UTC+2). Only people who downloaded in that window are affected.
  • A customer reported it at 15:50. Binaries were removed at 16:21 and a clean v0.16.0 with a signed tag followed at 19:16.
  • The binaries were the genuine CLI plus an injected “envchecker” module that, per OVHcloud, harvested editor and AI assistant data, cloud credentials (AWS, Google Cloud, Azure, DigitalOcean), and Docker, Kubernetes, npm, Terraform, SSH, .netrc and Git config.

The reporter’s catch is the instructive part. In issue #300 they say a SHA-256 pin in their CI for the Linux arm64 archive started failing. They then ran go version -m on the binary and saw a development build marked +dirty, with no release version flag, that did not match the v0.15.0 tag. Meanwhile the Go module proxy still served the original tagged commit.

The root cause is not exotic

OVHcloud’s stated main cause was that the GitHub repository “was not appropriately configured to ensure release immutability”. It also says developers were allowed to link personal GitHub accounts to the organisation. The advisory traces the entry point to a malicious editor extension installed on a developer’s personal MacBook in October 2025, with the personal GitHub account compromised in March 2026. OVHcloud says its corporate laptop was not compromised as far as it knows. The Stack reports OVHcloud links the extension’s infrastructure to the Glassworm actor; that attribution is OVHcloud’s, relayed by a single news source, and we have not checked it independently.

Our assessment: nothing here required a vulnerability in the CLI. A long-lived credential on a personal machine, plus release assets that could be overwritten after publication, was enough.

What to do as a consumer

  1. Pin a hash you obtained separately. A checksums.txt file hosted beside the binary is no help if the attacker can replace both. In issue #300 the checksums file was among the replaced assets.
  2. Prefer building from a tag via the module proxy where you can. The reporter says they switched to this temporarily, and the proxy still served the original commit.
  3. Inspect build metadata. go version -m ./ovhcloud shows the VCS revision and the vcs.modified flag. A release should not say modified=true.
  4. Treat a bad download as a credential incident. If you ran an affected binary, OVHcloud says to rotate the credentials listed above, delete the binary and move to v0.16.0 or later.

Below is a small Go verifier. It is illustrative, not production-ready: it does not fetch the expected hash for you, and you must source that value through a separate trusted channel.

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"strings"
)

func run(path, want string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return err
	}
	got := hex.EncodeToString(h.Sum(nil))
	if got != strings.ToLower(strings.TrimSpace(want)) {
		return fmt.Errorf("hash mismatch: got %s", got)
	}
	return nil
}

func main() {
	if len(os.Args) != 3 {
		fmt.Fprintln(os.Stderr, "usage: verify <file> <sha256>")
		os.Exit(2)
	}
	if err := run(os.Args[1], os.Args[2]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	fmt.Println("ok")
}

The advisory also lists a host indicator: a temp directory named envchecker- followed by random digits. This Python check looks for it. It is illustrative and only catches this one reported artefact, so a clean result proves little.

import glob
import os
import tempfile

pattern = os.path.join(tempfile.gettempdir(), "envchecker-*")
hits = glob.glob(pattern)
if hits:
    print("Possible match, treat secrets as exposed:", *hits, sep="\n  ")
else:
    print("No envchecker-* directory found in", tempfile.gettempdir())

For hash comparison, the advisory publishes MD5 values for the five malicious binaries; check the advisory itself rather than copying hashes from a third party.

What to do as a publisher

OVHcloud says it has enabled immutable releases across its organisation and plans signed commits and merges, a review of linked personal accounts, and release publishing tied to its central IAM. Those are sensible for your own projects too:

  • Make releases immutable so assets cannot be replaced after publication.
  • Publish only from a locked-down pipeline with an approval step, not from a person’s token.
  • Keep personal and corporate identities and devices separate.
  • Enable private vulnerability reporting. The Stack notes the reporter filed a public issue because no private channel was open; the issue thread says it was enabled afterwards.
  • Sign tags, and publish provenance consumers can check.

Still unconfirmed

OVHcloud has said further analysis of the binary will follow. As of 22 October 2026 we found no such follow-up in the three pages above. Treat details such as exfiltration endpoints as potentially changing; the advisory itself says the exfiltration URL may change.