Skip to content
Control Plane Labs

The OVHcloud CLI Swap and How to Verify Downloads

Attackers swapped OVHcloud CLI release archives and their checksum file for 94 minutes. What caught it, and how 37 popular CLIs let you verify downloads.

Ben Ennis

Published October 24, 2026

On October 19, a CI job that installs the OVHcloud CLI started failing. Its own build hadn’t changed. The SHA-256 of the Linux arm64 archive it downloaded no longer matched the hash pinned in the job, so the job stopped. That failure was the first sign that the release files on GitHub had been replaced with a secret stealer.

Plenty of other installs that window didn’t fail. The official install script didn’t check a hash. Neither did anyone who downloaded the archive and compared it to the checksums.txt on the same release, because the attacker had replaced that file too. This write-up covers what happened, why the usual checks missed it, and what verification the tools you install in CI actually offer.

What happened

OVHcloud’s security advisory and reporting by The Hacker News and SecurityWeek give this timeline, in UTC:

Time Event
Oct 20, 2025 A developer installs a malicious extension, color-picker-universal 2.8.91, on a personal laptop. It registers a persistent Node.js service.
Mar 2026 Several personal repositories on the developer’s GitHub account are compromised.
Oct 19, 12:47 Using stolen keys, the attacker replaces the macOS and Linux archives, the Windows x86_64 archive, and checksums.txt on releases v0.11.0 through v0.15.0.
Oct 19, 13:50 A customer files a public issue after a pinned SHA-256 check fails in CI.
Oct 19, 14:21 OVHcloud removes the swapped files.
Oct 19, 17:16 OVHcloud publishes a clean v0.16.0 as an immutable release.

The source code, tags, and build pipeline weren’t touched. The release workflow didn’t run at all. The attacker simply deleted files attached to existing releases and uploaded new ones under the same names, which anyone with write access to a repository can do. The download URLs didn’t change. OVHcloud’s advisory names the root cause directly: “the GitHub repository was not appropriately configured to ensure release immutability.” OVHcloud CEO Octave Klaba said the malware’s purpose was to “retrieve secrets from the disk,” according to BleepingComputer. Anyone who installed an affected build in that window should treat every secret on the machine as compromised.

Why the checksum file didn’t help

A checksums.txt attached to a release protects against a corrupted download. It doesn’t protect against someone who can change the release, because they can change the checksum file in the same step. That’s exactly what happened here.

The check that worked had one property the others lacked: the expected hash lived somewhere the attacker couldn’t write. The customer’s CI configuration recorded the SHA-256 when the version was first adopted. When the file changed, the hash no longer matched. Three kinds of checks have that property:

  • A hash pinned in your own repository, recorded when you first adopt a version.
  • A signature or attestation tied to an identity the attacker doesn’t control, such as a release key or the project’s build workflow.
  • An immutable release, which stops the swap from happening in the first place.

The GitHub documentation on immutable releases says that once one is published, “its associated Git tag is locked to a specific commit and cannot be changed or removed,” and “all files attached to the release (such as binaries and archives) are protected from modification or deletion.” Immutable releases also get an automatic release attestation that records the tag, the commit SHA, and the assets. The catch is that the setting only protects releases created after it’s turned on, so v0.11.0 through v0.15.0 stayed editable.

To see how common each protection is, on October 24 we checked the latest stable GitHub release of 37 command-line tools that are often installed from GitHub releases in CI. The list spans Kubernetes tooling, security scanners, linters, and general utilities. For each release we recorded the immutable flag from the GitHub releases API and which verification files were attached. We also queried GitHub’s attestations API with the SHA-256 digest of the Linux amd64 build.

Protection on the latest release Tools
Checksum file or per-file hash 33 of 37
GitHub artifact attestation for the Linux amd64 build 16 of 35 checked
Signature file (Sigstore, cosign, or GPG) 14 of 37
Immutable release 13 of 37
SBOM attached 10 of 37
SLSA or in-toto provenance file attached 4 of 37

Twelve tools combine an immutable release with a GitHub attestation: gh, yq, golangci-lint, GoReleaser, Trivy, cosign, TFLint, step, Syft, Grype, OPA, and now the OVHcloud CLI itself. For those tools, a release can’t be swapped, and you can verify the file you downloaded against the project’s own build.

Thirteen tools offered no independent check at all: kind, kustomize, k9s, act, ShellCheck, mkcert, Gitleaks, the Tekton CLI, stern, kubectx, Istio, the Cilium CLI, and Velero. Their releases had a checksum file or nothing, no signature, no attestation, no provenance file, and weren’t immutable. For these, a pinned hash in your own repository is the only check that would have caught an OVHcloud-style swap.

These are heuristics. We matched asset names, so a project that publishes signatures elsewhere, such as a container registry or a separate download site, may offer more than this shows. Helm is one example: its binaries are served from its own download site, and the GitHub release carries only signatures. The full results are published as JSON.

How to verify downloads in CI

Pin the hash when you adopt a version. Download the file once, compute its SHA-256, and commit the value next to the version number. Every later install checks against it. You can compute a hash in the browser with the hash generator, or with sha256sum locally.

# GitHub Actions step: install a pinned CLI version
- name: Install tool
  env:
    VERSION: "1.2.3"
    SHA256: "<hash recorded when this version was adopted>"
  run: |
    curl -fsSLo tool.tar.gz \
      "https://github.com/OWNER/REPO/releases/download/v${VERSION}/tool_linux_amd64.tar.gz"
    echo "${SHA256}  tool.tar.gz" | sha256sum --check --strict
    tar -xzf tool.tar.gz tool

Verify attestations where they exist. For tools that publish GitHub artifact attestations, the GitHub CLI checks them with one command: gh attestation verify tool.tar.gz -R OWNER/REPO. The attestation links the file’s digest to the workflow run that built it. A file uploaded by hand from a stolen account won’t have one.

Verify signatures against an identity, not a file on the same release. If a project signs its checksum file with Sigstore, verify the signature against the project’s expected signing identity before you trust the checksums. A signature checked only against a key or certificate attached to the same release is no better than the checksum file.

Don’t pipe install scripts into a shell in CI. The OVHcloud install script fetched the latest archive without checking a hash. Install scripts are convenient on a laptop. In CI, download a pinned version and check it.

Update hashes deliberately. When you upgrade, record the new hash in the same change that bumps the version, and get it from a verified source. Tools like Renovate can open those changes for you. The point is that a hash changes only when you change it.

If you maintain a CLI

The OVHcloud incident took a year to unfold: a malicious extension on a personal laptop in October 2025, stolen keys, and finally a release swap. A maintainer can’t control every contributor’s laptop, but can limit what a stolen account can do:

  1. Turn on immutable releases for the repository or organization. The setting protects only releases created afterward, so cut a new release once it’s on.
  2. Publish attestations from the release workflow. Use the actions/attest-build-provenance action or a tool such as GoReleaser, so every asset can be traced to a workflow run.
  3. Keep release write access narrow. OpenSourceMalware reported that the account used here belonged to a contributor whose merged work was in OVHcloud’s documentation repository. Contributors who don’t cut releases shouldn’t be able to edit them.
  4. Make the install script verify. If you ship a curl | sh installer, have it check an attestation or a signature, not only the checksum file next to the archive.

Frequently asked questions

Which OVHcloud CLI versions were compromised?+
Archives for v0.11.0 through v0.15.0 were replaced on October 19, 2026, between 12:47 and 14:21 UTC. Only installs during that window are affected. v0.10.0 and v0.16.0 are clean, and v0.16.0 is an immutable release.
Why didn't checksums.txt detect the OVHcloud CLI swap?+
The attacker replaced checksums.txt together with the archives, so the new file listed the hashes of the malicious archives. A checksum stored on the same release can't detect a change made by someone who can edit that release.
What is a GitHub immutable release?+
A release whose Git tag can't be moved or deleted and whose attached files can't be modified or deleted after publication. GitHub also creates a release attestation for it. The setting applies only to releases created after it's enabled.
How do I verify a GitHub release download?+
Compare its SHA-256 with a hash you pinned in your own repository, and where the project publishes them, run gh attestation verify on the file with -R set to the project's repository. Check signatures against the project's expected signing identity.
How many popular CLIs use immutable releases?+
In our October 24, 2026 check of 37 tools often installed from GitHub releases, 13 had an immutable latest release, 16 of 35 had a GitHub attestation for the Linux amd64 build, and 13 offered only a checksum file or nothing.
What should I do if I installed a compromised OVHcloud CLI build?+
OVHcloud advises cleaning the machine and treating every secret on it as compromised. Rotate API keys, tokens, SSH keys, and cloud credentials that were stored on or reachable from that machine.

Tags: #supply-chain, #github-releases, #ci, #sigstore, #attestations, #incident, #original-data