SevyDevy

Guide · Backend

OVHcloud CLI's October 2026 Supply-Chain Attack Explained: Swapped Release Files

22 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • The OVHcloud CLI supply chain attack happened on 19 October 2026. Someone using a stolen OVHcloud developer GitHub account swapped the download files of existing releases v0.11.0 to v0.15.0 for a secret-stealing build.
  • A CLI is a command-line tool. A release asset is a file attached to a GitHub release. Without a setting called immutable releases, anyone with enough access can replace it under the same name.
  • A checksum is a short fingerprint of a file. The reporter found the checksums.txt file replaced as well, so a fingerprint stored beside the file proved nothing against the person editing both.
  • To check your copy, compare its MD5 fingerprint with the list in OVHcloud's advisory and look for envchecker folders in your temp folder. Never run the binary to check it. If either matches, rotate your secrets.
  • Beginners can stop after the checking section. Engineers then get what immutable releases lock and leave open, how release attestations work, and how to read a Go binary's build stamp without running it.

What goes wrong when a release download changes under the same version?

You get different bytes from the same link, and nothing on the page tells you. The version number, the tag and the download address all stay the same, so most quick checks look fine.

That is what the OVHcloud CLI supply chain attack did on 19 October 2026. For about an hour and a half, the official GitHub downloads of several ovhcloud versions were a recompiled build. It carried an infostealer, a program that copies secrets off your disk. The source code on GitHub was never changed.

Picture a common setup. A developer on a small platform team in Pune sets up a new laptop on a Monday evening. They paste the one-line install command from the project's README, run ovhcloud login, and go back to work. The setup is made up, but the install path and the risk are real.

That laptop also holds cloud keys in a credentials file, an SSH key, a Kubernetes config and saved editor sessions. A stealer that runs once can read all of them. This guide follows that laptop from the download to the clean-up.

Which words do you need before reading about this attack?

You need seven words. Each one gets a plain meaning and how it shows up in this incident.

  • GitHub release: a named version of a project on GitHub, such as v0.15.0. It points to a Git tag and can carry notes and files.
  • Git tag: a label fixed to one commit, which is one saved snapshot of the code. In this attack the v0.15.0 tag did not move, per issue 300.
  • Release asset: a file attached to a release, such as ovhcloud-cli_Linux_x86_64.tar.gz. Assets are uploaded separately from the code.
  • Checksum or hash: a short fingerprint calculated from every byte of a file. Change one byte and the fingerprint changes. SHA-256 and MD5 are two common recipes for it.
  • Infostealer: malware whose job is to find secrets such as passwords, tokens and keys, and send them to the attacker.
  • Indicator of compromise (IOC): a clue that proves or suggests an infection, such as a file fingerprint or a folder name.
  • Attestation: a signed statement that a service makes about a file, such as which release or which build produced it. Anyone can check the signature later.

What was the OVHcloud CLI supply chain attack in October 2026?

It was a swap of release files, not a change to the code. OVHcloud's advisory says a compromised developer GitHub account was used to replace the binaries of existing releases with malicious ones. It adds that the source code and build chain were not affected.

OVHcloud's advisory gives the window as 14:47 to 16:21 on 19 October 2026, in Central European Summer Time (UTC+2). For readers in India, that is 18:17 to 19:51 IST. The advisory counts about 250 downloads in that window.

Timeline from the OVHcloud advisory, 19 October 2026 (IST is UTC+2 time plus 3 hours 30 minutes)
Time (UTC+2)Time (IST)What happened
14:4718:17The macOS (Darwin) binary of v0.15.0 is replaced
14:5718:27All v0.15.0 binaries are replaced
15:3019:00Binaries of v0.11.0 to v0.15.0 are replaced
15:5019:20A user reports suspicious activity in GitHub issue 300
16:2119:51All malicious binaries are removed from the repository
18:5222:22OVHcloud posts a public announcement on X
19:1622:46Clean v0.16.0 is released with a signed tag

The report itself is a good lesson. The reporter compared the replaced files with the originals and posted both SHA-256 fingerprints in issue 300. A maintainer replied that the files were deleted at 16:21 and that v0.16.0 was signed and immutable.

How you installed the tool decides whether this touches you. The table sorts the install methods from the project's README by what the sources say.

Which ovhcloud installs could have received a swapped binary (sources as of 28 October 2026)
Install methodExposed?Why
Manual download from GitHub releases, v0.11.0 to v0.15.0, inside the windowYesThese are the release assets the advisory says were replaced
install.sh one-liner run inside the windowLikely, if it picked v0.11.0 to v0.15.0The script downloads the release asset and does not check any checksum
Docker image ovhcom/ovhcloud-cliNo, per BleepingComputerIt reports Docker images and source builds were unaffected, relaying OpenSourceMalware
go install from sourceNoThe advisory says the source code was not affected
Homebrew cask from ovh/tapNot stated by OVHcloudThe cask pins its own SHA-256 in a separate repository; see the checksum section
Any version downloaded before 14:47 or after 16:21NoThe advisory limits the impact to the window

Our Pune laptop used the install.sh one-liner. Say the developer ran it at 19:10 IST (made up). That falls inside the window, and the script fetches the latest release, which at that time was v0.15.0.

How could a release change without touching the source code?

Because the files on a release are uploaded separately from the code, and an account with enough access could replace one with a new file under the same name. The tag name, version and download address stayed the same.

A normal ovhcloud release is built by a CI pipeline, an automated system that builds and tests code on every change. OVHcloud's CEO said in a post on X, quoted by BleepingComputer, that those pipelines were not impacted. The bad files skipped the pipeline completely.

Issue 300 shows how. Go programs carry a stamp of how they were built, and the reporter read it from a replaced binary. It said the module version was v0.15.1-0.20261013083505-5ead8ef9b43d+dirty. That stamp means a build of a later commit, from 13 October, with uncommitted local changes.

The reporter also noticed the binary lacked the version setting that the project's release tool, GoReleaser, normally adds. Their reading: it was likely not built by the release pipeline. OpenSourceMalware, a community threat-intelligence group, reached the same view, as reported by BleepingComputer.

Normal release path vs the swap on 19 October

The top path ends in signed, tested files. Notice the bottom path joins at the release itself, after every pipeline check has already passed.

Diagram connections:

  • Source on main branch to Release pipeline (Normal build)
  • Release pipeline to GitHub release v0.15.0 (Uploads original assets)
  • Stolen developer account to Locally built binary (Builds outside CI)
  • Locally built binary to GitHub release v0.15.0 (Replaces the assets)
  • GitHub release v0.15.0 to Your download (Served to users)
  • Source on main branch: Unchanged throughout the incident
  • Release pipeline: Builds, tests and uploads assets
  • GitHub release v0.15.0: Tag still points to the original commit
  • Stolen developer account: Used with stolen keys, per the advisory
  • Locally built binary: Later commit plus an envchecker stealer
  • Your download: Same URL, same file name, new bytes

A picture helps here. A release is like a medicine box on a pharmacy shelf. The shelf label is the version, the box is the release and the strip inside is the binary. The batch sticker on the box is checksums.txt.

Someone with a staff key opened the box, swapped the strip and printed a new batch sticker. The shelf label was never touched. Where the picture breaks: a real box is one copy, while a release is served to everyone, so one swap reached every buyer at once.

The chain of causes behind the swap

No single mistake did this. The advisory and the reporting describe a chain, and breaking any link would have stopped or shrunk it.

  1. A personal machine was infected

    The advisory says the developer installed an extension, color-picker-universal 2.8.91, on a personal MacBook on 20 October 2025. It does not say which app the extension was for.

  2. Keys were stolen and kept

    The advisory says the releases were replaced using stolen keys. It also says the wallet used by that extension belongs to the GlassWorm threat actor.

  3. The account still had release rights a year later

    The account could still edit releases of ovhcloud-cli. The advisory says OVHcloud then suspended it and reviewed every account with write access on the repository.

  4. Releases were mutable

    The advisory names the root cause: the repository was not configured to ensure release immutability. The CEO said the setting was disabled.

  5. Installers trusted whatever was there

    The install.sh script on the main branch, as of 28 October 2026, downloads the asset and runs no checksum check.

BleepingComputer also reports a third-party claim that the account had been compromised for more than half a year. That comes from OpenSourceMalware, not from OVHcloud's own advisory.

Why did checksums.txt not catch the swapped binaries?

Because the same account that replaced the binaries could also replace checksums.txt, and the reporter says it did. A fingerprint only proves something when the attacker cannot change the fingerprint too.

Many projects publish a checksum file next to their binaries, and ovhcloud-cli's releases had one called checksums.txt. A careful installer downloads both, calculates the SHA-256 of the binary and compares. That catches a broken download or a tampered mirror, which is a copy of the files hosted somewhere else.

Pause and predict before running the demo below. It swaps a harmless text file standing in for a binary, then swaps the checksum file too. Which of the four checks will pass?

bash
# Harmless demo: text files stand in for release assets
mkdir release
printf 'good build v0.15.0\n' > release/tool_Linux_x86_64.tar.gz
(cd release && sha256sum tool_Linux_x86_64.tar.gz > checksums.txt)
cp release/checksums.txt pinned_elsewhere.txt   # a pin kept in another place

(cd release && sha256sum -c checksums.txt)      # 1. original files

printf 'tampered build\n' > release/tool_Linux_x86_64.tar.gz
(cd release && sha256sum -c checksums.txt)      # 2. only the asset swapped

(cd release && sha256sum tool_Linux_x86_64.tar.gz > checksums.txt)
(cd release && sha256sum -c checksums.txt)      # 3. checksums.txt swapped too

(cd release && sha256sum -c ../pinned_elsewhere.txt)  # 4. the pin elsewhere

On GNU coreutils 9.4 the four checks print OK, FAILED, OK and FAILED. A failed check also prints a warning and exits with status 1. So the third check, the one that matches this incident, passes cleanly.

What just happened? Check 3 compared the new file against a fingerprint written by the same hand. Only check 4 used a fingerprint the attacker could not reach, and it caught the swap at once.

Where the fingerprint lives decides what it can stop
ProtectionWho can change itWould it stop a swap like this?
checksums.txt in the same releaseAnyone who can edit the releaseNo, it was replaced too, per issue 300
SHA-256 pinned in a Homebrew cask in another repositoryWhoever can push to that repositoryYes, if the attacker cannot also push there
Signed Git tagThe signer's keyNo, it protects the commit, not the uploaded files
Immutable release with a release attestationNobody after publication, per GitHub docsYes, for later swaps of a published release
Artifact attestation from the CI buildOnly the build workflowIt would flag a binary built outside CI, if users verify it
The advisory's MD5 listOVHcloud, after the factNo, it detects; it does not prevent

The Homebrew row needs a careful reading. The cask file, as of 28 October 2026, lists a SHA-256 for each platform's download, and Homebrew's Cask Cookbook calls it the checksum of the downloaded file. OVHcloud has not said whether Homebrew users were affected, so treat that row as how it should work.

How do you check whether you got an affected OVHcloud CLI binary?

Compare the binary's MD5 fingerprint with the advisory's list, and look for envchecker folders in your temp folder. Do both without running the binary. The advisory says not to run it, and an affected copy does its work on every run.

That rule rules out the usual reflex. Running ovhcloud version or ovhcloud --help to see the version could run the stealer again on an affected copy.

  1. Find the file without running it

    On macOS or Linux, run command -v ovhcloud. It prints the path and does not start the program. On Windows, use where.exe ovhcloud in a terminal.

  2. Calculate its MD5

    On Linux, run md5sum on that path. On Windows PowerShell, run Get-FileHash with -Algorithm MD5. On any machine with Python 3, the one-liner below works.

  3. Compare with the advisory list

    Match the 32-character result against the table below. A match means you have a malicious binary.

  4. Look for the leftover folder

    The advisory says each run creates a temp folder named envchecker followed by 9 or 10 digits, and never deletes it.

  5. Check every machine

    Repeat on each laptop, build agent and container image where the CLI was installed during the window.

MD5 fingerprints of the malicious binaries, from the OVHcloud advisory appendix
FilePlatformMD5
ovhcloudmacOS Apple silicon (Darwin_arm64)6f2f086239f3d88eb2750e871901927d
ovhcloudmacOS Intel (Darwin_x86_64)347e65eb7f2d78d1c6872690272a6cc3
ovhcloudLinux ARM (Linux_arm64)01f57206d37c28cf711677f612052913
ovhcloudLinux Intel or AMD (Linux_x86_64)301bff0b51980b9e76c93633e7cfd3bd
ovhcloud.exeWindows x86_646a3cf153338c416169cda2fe704c8848

The advisory labels these as hashes of the ovhcloud binary itself, so hash the extracted program, not the downloaded .tar.gz or .zip archive. The script below does the comparison on macOS or Linux.

bash
BIN="$(command -v ovhcloud)"     # path only, does not run it
H="$(python3 -c 'import hashlib,sys; print(hashlib.md5(open(sys.argv[1],"rb").read()).hexdigest())' "$BIN")"
echo "MD5: $H"
case "$H" in
  6f2f086239f3d88eb2750e871901927d|347e65eb7f2d78d1c6872690272a6cc3|\
  01f57206d37c28cf711677f612052913|301bff0b51980b9e76c93633e7cfd3bd)
    echo "MATCH: malicious binary from the advisory list" ;;
  *) echo "No match with the advisory list" ;;
esac

# leftover folders: prints nothing if none exist
find "${TMPDIR:-/tmp}" /tmp -maxdepth 1 -type d -name 'envchecker-*' 2>/dev/null | sort -u

Expect one line starting MD5: followed by 32 hexadecimal characters, which is 128 bits. Then you get either MATCH or No match. We ran this comparison on harmless test files only, including a forced match to test the MATCH branch.

Where should you look for the folder? The advisory only says the temp folder. On macOS and Linux, check the folder in TMPDIR and also /tmp. On Windows, check the folder in the TEMP environment variable, for example with Get-ChildItem $env:TEMP -Filter envchecker-*.

Beginners can stop here if their check came back clean. The rest explains what to do on a match and how GitHub's newer features close the gap.

What should you do if your binary matches or the folder exists?

Treat every secret on that machine as stolen, change or revoke them, then remove the binary and install v0.16.0 or newer. That is the order the advisory gives, and the CEO's short version was to change all the secrets on your disk.

  1. Rotate secrets first

    Change or deactivate credentials for every service the advisory lists. We would do this from another machine, because the advisory does not say whether anything else was left running.

  2. Delete the binary

    Remove the matched ovhcloud file. Deleting it does not undo what it already copied.

  3. Install a clean version

    Install v0.16.0 or later. Check its fingerprint against a source the release uploader cannot edit.

  4. Check for the wider infection

    The advisory says the stealer reaches into many tools. Review cloud audit logs for unknown access since 19 October 2026.

The advisory's target list covers more than OVHcloud keys. It names AWS, Google Cloud, Azure and DigitalOcean credentials, Docker and Kubernetes configs, Terraform, npm, the .netrc file and global Git config. It also names SSH keys and the local databases of several code editors and AI coding assistants.

Back on the Pune laptop, the folder check printed one envchecker folder (made up). So the developer revoked the cloud keys, replaced the SSH key on every server, reset the Kubernetes credentials and signed out of editor sessions. All of that took far longer than the roughly 93 minutes the malware was online.

What do GitHub immutable releases lock, and what do they leave open?

They lock a release's assets and its Git tag once it is published. GitHub's docs say assets cannot be modified or deleted, and the tag is locked to one commit.

Three details matter for incidents like this one. Each comes from GitHub's documentation as of 28 October 2026.

  • Only future releases are covered. The settings page says immutability will only apply to future releases, so v0.11.0 to v0.15.0, published before the switch, keep their old rules.
  • Title and notes stay editable. You can still change a release's title, notes and its pre-release or latest flag. An attacker with access could still edit a download link written in the notes.
  • Deleting is allowed, but the tag name is burned. You can delete an immutable release and then its tag, but the same tag name can never be reused. That even holds after deleting and recreating the repository.

Publishing an immutable release also creates a release attestation. GitHub describes it as a cryptographically verifiable record of the release tag, the commit SHA (the commit's unique ID) and the release assets. Users check it with the GitHub CLI, called gh.

How an immutable release closes the swap

Follow the arrow into the release after publication. Notice the attempted re-upload is refused, and the user's check compares against a record GitHub signed at publication.

Diagram connections:

  • Draft release to Publish (Publish once)
  • Publish to Release attestation (GitHub signs)
  • Stolen account to Publish (Upload refused)
  • Publish to User downloads asset (Download)
  • User downloads asset to gh release verify-asset (Check local file)
  • Release attestation to gh release verify-asset (Expected hashes)
  • Draft release: Maintainer attaches every asset first
  • Publish: Assets and tag lock; attestation created
  • Release attestation: Signed record: tag, commit SHA, asset hashes
  • Stolen account: Tries to delete and re-upload an asset
  • User downloads asset: Gets the bytes published at release time
  • gh release verify-asset: Compares the local file with the attestation

GitHub's docs give two commands. The first checks that a release exists and is immutable, and the second checks that a local file exactly matches a release asset. Swap in your own tag and file path.

bash
# Is this release immutable, with a valid attestation?
gh release verify v0.16.0 -R ovh/ovhcloud-cli

# Does my downloaded archive exactly match the published asset?
gh release verify-asset v0.16.0 ./ovhcloud-cli_Linux_x86_64.tar.gz -R ovh/ovhcloud-cli

We checked these commands and their flags in the help text of gh 2.89.0. We could not run them against OVHcloud's releases from our test machine, so we do not show their output. The docs add that verify-asset cannot check GitHub's auto-made source zip or tarball.

For maintainers, the docs suggest one habit. Create the release as a draft, attach every asset, then publish. Otherwise you lock a release before all its files are in.

How do attestations and build stamps help you trust a binary?

A release attestation proves which bytes were published. An artifact attestation goes further and proves which workflow built them, from which commit. Neither proves the code is safe.

Artifact attestations tie a file to its build

An artifact attestation is created inside a GitHub Actions workflow. GitHub's docs say it records the workflow, the repository, the commit SHA and the triggering event. For public repositories, it is also written to a public transparency log, an append-only record anyone can search, run by Sigstore, an open-source signing project.

yaml
permissions:
  id-token: write
  contents: read
  attestations: write

steps:
  # ... your build step produces dist/ovhcloud ...
  - name: Generate artifact attestation
    uses: actions/attest@v4
    with:
      subject-path: 'dist/ovhcloud'

A user then runs gh attestation verify on the file with -R and the repository name. A binary built on someone's laptop, like the swapped ones, would have no matching attestation. The gh help text adds that you should ideally also pin the signer workflow with --signer-workflow. With only -R, the check accepts any workflow in that repository, which someone with push access could add. GitHub's docs still warn that attestations are not a guarantee that an artifact is secure.

Reading a Go binary's build stamp without running it

The ovhcloud CLI is written in the Go programming language, and Go normally embeds build information in the binaries it builds. The command go version -m prints it by reading the file. Issue 300's reporter used this kind of stamp as a clue.

We built a tiny harmless program twice on Go 1.24.7: once from a clean Git tag, and once after editing a file without committing. Here are the relevant lines.

text
== clean build at tag v0.1.0
	mod	example.com/demo	v0.1.0
	build	vcs.modified=false
== build with an uncommitted change
	mod	example.com/demo	v0.1.0+dirty
	build	vcs.modified=true

The +dirty suffix and vcs.modified=true are the same signals the reporter quoted. To prove the command does not run the file, we also read a macOS arm64 binary and a Windows binary on a Linux x86_64 machine. Both printed their build details, though Linux could not run either.

A stamp is a clue, not proof. A careful attacker can build with -buildvcs=false or from a clean checkout. Our run with -buildvcs=false printed no vcs lines at all.

How do you harden your own releases after this attack?

Lock what you publish, sign where it came from, and keep the right to change releases with as few accounts as possible. The table splits the steps by role.

Hardening steps, each tied to a link in this incident's chain
RoleStepLink it breaks
MaintainerEnable release immutability for the organisation, then publish via draft-then-publishMutable releases
MaintainerBuild and upload assets only from CI, with actions/attestBinaries built on a laptop
MaintainerReview who can edit releases and remove dormant accessA year-old stolen account still with rights
MaintainerMake install scripts verify a hash or attestation before installingInstallers that trust any bytes
UserPin versions and hashes in your own config, or run gh release verify-assetTrusting a mutable latest link
UserKeep long-lived cloud keys off laptops where you canOne stealer run exposing everything

OVHcloud's own list of next steps, as of its advisory, includes signing all commits and merges, immutability for all releases and linking release publishing to its central identity system. Two of them, immutability and tighter control of who can publish, match rows in the table.

How do you explain the OVHcloud CLI supply chain attack in a system design interview?

Frame it as a trust chain from source to user, then show where the chain had a gap.

  1. State the incident in one line

    Existing release binaries of a CLI were replaced with a stealer through a stolen account, while source and CI stayed clean.

  2. Draw the chain

    Source, CI build, release assets, installer, user machine. Mark the release step as the point the attacker joined.

  3. Explain why checksums failed

    The checksum file lived in the same mutable release, so whoever could swap the binary could swap the fingerprint.

  4. Name the controls

    Immutable releases lock assets after publishing. Release attestations prove the published bytes. Artifact attestations prove which workflow built them.

  5. Cover detection and response

    Hash comparison without running, IOC folders, and secret rotation in order, with the window and download count as scale.

  6. Close with the limits

    Immutability covers only future releases. Attestations prove origin, not safety. Account hygiene still matters.

Rough numbers help. The window was about 1 hour 33 minutes with about 250 downloads, per the advisory. One stolen account and one disabled setting were enough.

Questions people ask

Which OVHcloud CLI versions were affected?
Versions v0.11.0 to v0.15.0, but only if downloaded between 14:47 and 16:21 UTC+2 on 19 October 2026, according to OVHcloud's advisory. Versions v0.10.0 and older, v0.16.0 and newer, and copies downloaded outside the window are listed as unaffected. Issue 300 names only v0.13.0 to v0.15.0, and its reporter says some assets were not checked.
Can I just run ovhcloud version to see if I am affected?
No. The advisory says to determine it securely without running the binary, because an affected copy runs its stealer on every start. Find the file's path with command -v, calculate its MD5 and compare it with the advisory's list. Then look for envchecker folders in your temp folder.
Was the OVHcloud CLI source code or Docker image compromised?
No, according to OVHcloud. The advisory says the source code and build chain were not affected, and only released build assets were replaced. BleepingComputer also reports that source builds and Docker images were unaffected. A go install from source therefore built clean code.
Why does the advisory use MD5 when MD5 is broken?
MD5 is broken for collision resistance, which matters when proving a file is genuine. Here it only spots known bad files, and an innocent file will not match by accident. For checking that a download is genuine, use SHA-256 from a trusted place or gh release verify-asset on an immutable release.
Does turning on immutable releases protect my old releases?
No. GitHub's settings page says immutability will only apply to future releases. Releases published before you enable it keep their old, editable assets. If old versions matter, consider publishing fresh immutable releases and pointing users and installers to them.
Is a signed Git tag enough to protect a release?
No. A signed tag proves who tagged a commit, but release assets are separate uploads. In this incident the v0.15.0 tag still pointed to its original commit, per issue 300, while the files changed. You need immutable releases, attestations or hashes stored outside the release to cover the files.

Sources and further reading

Practise this with Lala

Practise