Safeguard
Supply Chain Security•

OVHcloud CLI Release Backdoor: GlassWorm Link, IoCs, What to Rotate

OVHcloud CLI v0.11.0 to v0.15.0 binaries were swapped for a stealer on 19 Oct 2026. Check hashes and envchecker folders, rotate secrets, use v0.16.0.

Safeguard Security Team
33 min read

On 19 October 2026, a hijacked developer GitHub account replaced the OVHcloud CLI binaries for v0.11.0 to v0.15.0 with a credential stealer for about 94 minutes. Anyone who downloaded the CLI for macOS, 64 bit Linux or Windows x86_64 between 12:47 and 14:21 UTC should check hashes, look for envchecker temp folders, rotate secrets and install v0.16.0.

Below: the indicators from OVHcloud's own advisory, a timeline that reconciles the time zones, which install routes were exposed, commands for macOS, Linux, Windows, Homebrew and CI, a rotation order, and what any team shipping a CLI through GitHub Releases can change this week.

What happened

The OVHcloud CLI (ovhcloud) is OVHcloud's open source tool for managing its cloud services. It is written in Go, built with GoReleaser and shipped as GitHub release archives, through an install script, a Homebrew cask in ovh/homebrew-tap, go install and a Docker image.

OVHcloud's advisory GHSA-862f-55f5-vmvw, published on 20 October, says a compromised developer GitHub account was used to replace the release binaries for v0.11.0 through v0.15.0 with malicious builds. It says the source code and build chain were not affected and no other repositories were changed. OVHcloud rates it High, with no CVSS score and no CVE.

Three conditions made the swap possible, according to the primary sources:

  1. A contributor account with write access was in someone else's hands. The account in the public record is HassenBF (GitHub user ID 4911006), a legitimate OVHcloud contributor. OpenSourceMalware, the researcher group that first analysed the account, says it had no commits in ovh/ovhcloud-cli and suggests it held write access through the @ovh/ovhcloud-cli-owners team that CODEOWNERS assigns to every path. That last point is the researchers' inference.
  2. Releases were not immutable. OVHcloud's advisory and CEO Octave Klaba's posts on X, as reported by BleepingComputer and SecurityWeek, name this as the root cause: with GitHub's release immutability off, an account with the right permission could change assets on releases published months earlier, without CI.
  3. The checksum file sat next to the binaries it described. The attacker replaced checksums.txt too, so a check against that file would have passed. IT-Connect makes this point directly.

That third point is how the incident was found. A customer, GitHub user alink-tothepast, pins the SHA-256 of ovhcloud-cli_Linux_arm64.tar.gz in CI. When that check failed on 19 October, they opened issue #300 with the old and new hashes and the replacement binary's build metadata. OVHcloud credits that report as the trigger for its investigation.

How the account was compromised

OVHcloud's advisory says the developer's account was compromised months earlier through a malicious extension on a personal laptop, and the attacker later used stolen keys to replace the assets. The Stack, updating its story on 21 October with OVHcloud's post mortem, adds that the developer installed an extension named color-picker-universal 2.8.91 on a personal MacBook on 20 October 2025. The extension made calls involving the Solana wallet BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC, which OVHcloud linked to GlassWorm, and a persistent Node.js service registered through a macOS plist talked to 162.33.177[.]177. Per The Stack, the release swap itself was done with stolen keys, not from that laptop, and the developer's company laptop was not known to be compromised.

GlassWorm was first described publicly in October 2025. A security firm's write up dated 21 October 2025 describes it as a self propagating worm in code editor extensions on OpenVSX and later the Microsoft VS Code marketplace, hiding code in invisible Unicode characters and using the Solana blockchain in its command channel. It lists jeronimoekerdt.color-picker-universal@2.8.91 among OpenVSX extensions with malicious versions, in a wave dated 17 October 2025, three days before the developer's install.

One wording difference: OVHcloud's advisory, as we fetched it, calls it a "malicious browser extension," while GlassWorm reporting lists this name as an editor extension. The lesson for defenders is the same either way.

What OpenSourceMalware found in the account's history

Before OVHcloud published a root cause, OpenSourceMalware found force pushes on 5 and 6 March 2026 that rewrote four of the account's personal projects. Three gained a preinstall.js file run by npm install; the fourth inlined the same loader into src/index.js. The researchers decoded the loader: it hides code in Unicode variation selector characters, decrypts a second stage with AES-256-CBC, skips machines that look Russian by language plus time zone, writes a throttle file at ~/init.json on macOS, and reads its next payload address from a memo on the same BjVe... Solana wallet, a known GlassWorm indicator.

OpenSourceMalware called the link between March and the October swap its inference. OVHcloud's post mortem, as The Stack reports it, makes the same connection on its own evidence and moves the start of the compromise back to October 2025.

Timeline

All times UTC. OVHcloud's advisory uses UTC+2 (Paris summer time); we converted it.

Date and time (UTC)EventSource
20 Oct 2025Developer installs color-picker-universal 2.8.91 on a personal MacBookOVHcloud post mortem via The Stack
2 Mar 2026v0.10.0 released (last clean release before the affected range)GitHub releases, Homebrew tap
5 Mar 2026, 02:41:17 to 02:41:59Force pushes add the loader to three personal repositories of HassenBF; a fourth follows on 6 Mar at 22:49:53OpenSourceMalware
14 Apr to 31 Aug 2026v0.11.0 (14 Apr), v0.12.0 (29 May), v0.13.0 (25 Aug), v0.14.0 (31 Aug) releasedGitHub releases, Homebrew tap
11 Sep 2026, 14:51:47v0.15.0 tag commit 760fd72Go module proxy
16 Sep 2026, about 16:52Original v0.15.0 archives uploadedGitHub release assets
13 Oct 2026, 08:35:05Commit 5ead8ef merged to main; the malicious binary was later built from itIssue #300, OpenSourceMalware
16 Oct 2026, 14:38:10Commit 6ef70c2, later tagged v0.16.0Go module proxy
19 Oct, 12:47First malicious upload (v0.15.0 Darwin)GHSA, issue #300
19 Oct, 12:57All v0.15.0 binaries and checksums.txt replacedGHSA, issue #300
19 Oct, 13:01 to 13:04v0.14.0 Linux arm64 and checksums replacedIssue #300
19 Oct, 13:08 to 13:11v0.13.0 Linux arm64 and checksums replacedIssue #300
19 Oct, about 13:12Last replacement the reporter observedIssue #300
19 Oct, 13:30All v0.11.0 to v0.15.0 binaries replaced, per OVHcloudGHSA
19 Oct, 13:50:09Customer opens issue #300 after a pinned hash check failsGHSA, OpenSourceMalware
19 Oct, 13:57OVHcloud begins investigatingGHSA
19 Oct, 14:20Scan of all OVHcloud GitHub repositories beginsGHSA
19 Oct, 14:21Malicious binaries removedGHSA, issue #300
19 Oct, 16:00:23v0.16.0 tag push starts the build and release workflowsOpenSourceMalware
19 Oct, 16:52Public announcement on XGHSA
19 Oct, 17:16:43v0.16.0 assets uploaded by ovh-cds, published as an immutable release with a release attestationGitHub release assets, GHSA
19 OctHomebrew cask moved to v0.16.0 by ovh-cds (commit 2e9aa7d)ovh/homebrew-tap
20 OctGHSA-862f-55f5-vmvw published; private vulnerability reporting enabled; issue closedIssue #300, GHSA
21 OctThe Stack adds post mortem detailsThe Stack

Two time disputes. The reporter saw the last change at about 13:12 on the releases they checked (v0.13.0 to v0.15.0); OVHcloud says all five were done by 13:30, which covers v0.11.0 and v0.12.0 as well. And several outlets printed "14:47" and "16:21" without a time zone: those are Paris times. In UTC the window is 12:47 to 14:21.

Affected and fixed versions

ReleaseOriginally publishedReplaced on 19 OctNot replacedStatus now
v0.10.0 and earlier2 Mar 2026 and earlierNothingAllNot affected (GHSA)
v0.11.014 Apr 2026Darwin arm64 and x86_64, Linux arm64 and x86_64, Windows x86_64, checksums (per OVHcloud)Linux i386, Windows i386, Windows arm64, Js wasmAffected; replaced assets deleted
v0.12.029 May 2026Same, per OVHcloudSame fourAffected; replaced assets deleted
v0.13.025 Aug 2026Linux arm64 and checksums confirmed by reporter; rest per OVHcloudSame fourAffected; replaced assets deleted
v0.14.031 Aug 2026Linux arm64 and checksums confirmed by reporter; rest per OVHcloudSame fourAffected; replaced assets deleted
v0.15.016 Sep 2026All five platforms and checksums.txt confirmedSame fourAffected; replaced assets deleted
v0.16.019 Oct 2026Not applicableNot applicableFixed: immutable, signed tag, release attestation

When we fetched v0.15.0 on 22 October, only the four untouched archives (uploaded 16 September) and the source archives remained. The five replaced archives and the checksum file were deleted, not restored. A job still asking for v0.15.0 on a common platform now fails with a missing file, which is safe but tempts people to "fix" it by unpinning.

Install routes

RouteExposed in the window?WhySource
install.sh from mainYesFetches the latest release archive; no checksum or signature checkOur reading of install.sh; OpenSourceMalware, IT-Connect
Direct download from GitHub ReleasesYesThese were the replaced filesGHSA
CI jobs and Dockerfiles that download an archive without a pinned hashYesSame filesOpenSourceMalware
CI jobs that pin the archive SHA-256No; they failHow this was foundIssue #300
Homebrew cask ovh/tap/ovhcloud-cliDisputedThe cask pins the original hashes; see belowOpenSourceMalware, SecurityWeek; our reading of the cask
go install .../cmd/ovhcloud@...NoBuilds tagged source through the Go module proxyGHSA, OpenSourceMalware, Go module proxy
Building from sourceNoSource unchangedGHSA
ovhcom/ovhcloud-cli Docker imageNoBuilt by OVHcloud's internal pipelineOpenSourceMalware, BleepingComputer
i386, Windows arm64, wasm buildsNoNot replacedIssue #300
Installed before 12:47 UTC, no update in the windowNoThe original file was already on diskOpenSourceMalware
The CLI's own update featurePossiblyIT-Connect says "in principle"; not confirmedIT-Connect

The Homebrew question

Early reports list brew install --cask ovh/tap/ovhcloud-cli and brew upgrade as exposed. We think that needs a closer look. The v0.15.0 cask in ovh/homebrew-tap (commit b70a654, generated by GoReleaser on 16 September) pins a sha256 per platform. Its Linux arm64 value, 5c2beb56e6d83a4b426565a1557791ef759316abcc4effb38dd627c90aeb80e0, is exactly the original hash the reporter recorded. The tap shows no change between 16 September and the v0.16.0 update. Homebrew checks cask downloads against that value, so during the window a Homebrew install should have failed with a checksum mismatch.

We have not tested this and OVHcloud has not said it, so treat it as our reading. Anyone who hit that error and then downloaded the archive by hand would still be exposed. If you saw a checksum error for ovhcloud-cli on 19 October, Homebrew probably protected you; check what is on disk anyway.

Technical details

Values below come from OVHcloud's advisory, issue #300, GitHub release metadata, the Go module proxy and OpenSourceMalware, attributed per row. Use them to search your systems; do not run a file you suspect.

Malicious binary hashes (OVHcloud)

The advisory's appendix lists MD5 hashes of the extracted binaries, not split by release version.

FilePlatformMD5
ovhcloudDarwin arm646f2f086239f3d88eb2750e871901927d
ovhcloudDarwin x86_64347e65eb7f2d78d1c6872690272a6cc3
ovhcloudLinux arm6401f57206d37c28cf711677f612052913
ovhcloudLinux x86_64301bff0b51980b9e76c93633e7cfd3bd
ovhcloud.exeWindows x86_646a3cf153338c416169cda2fe704c8848

Archive hashes (SHA-256)

FileHashMeaningSource
v0.15.0 ovhcloud-cli_Linux_arm64.tar.gz3833daffcaaaab27e4e0e0833240ffcb1df8df648f4ec0bc4a75c176e28933cbMaliciousIssue #300
v0.15.0 ovhcloud-cli_Linux_arm64.tar.gz5c2beb56e6d83a4b426565a1557791ef759316abcc4effb38dd627c90aeb80e0OriginalIssue #300, cask b70a654
v0.15.0 ovhcloud-cli_Linux_x86_64.tar.gz1109d157dcf677bf3a7616e4b27b4fe3b5cc984e1cc82987592016541a6c88edOriginalCask b70a654
v0.15.0 ovhcloud-cli_Darwin_arm64.tar.gz26b78ca0045dd21db040b2991d8a301bafcb898454ea80b5285a04e184132142OriginalCask b70a654
v0.15.0 ovhcloud-cli_Darwin_x86_64.tar.gz5aadbdc9f9eae915fdb13e189fa8e5b492b302418c8b9ef3b57d720128fbbee8OriginalCask b70a654

We found no public SHA-256 values for the other replaced archives.

Clean v0.16.0 assets (SHA-256, GitHub asset metadata)

Uploaded 2026-10-19T17:16:43Z by ovh-cds. The checksum file is now ovhcloud-cli_0.16.0_checksums.txt.

AssetSHA-256
ovhcloud-cli_Darwin_arm64.tar.gz79437d459e25e2594b47c976f765e3abc51668e1dd809d08eaac448c6ca075a0
ovhcloud-cli_Darwin_x86_64.tar.gz36363eb9dfe95ee403ad4645b831c7d48e8dd288d45c642afd9c22707d84ffb0
ovhcloud-cli_Linux_arm64.tar.gz6ad093b46729269dca35a85a0df864557891add5c98a099ad1631980fe0aa1be
ovhcloud-cli_Linux_x86_64.tar.gz527fb1c9bde0680837601e08bc064783b57eb301ab5a81fb9c1fc17f466f2898
ovhcloud-cli_Windows_x86_64.zipb3ee6dc089758f0569fa34bd558ec0cec9ce039254315fa91bcbbc2468f49263
ovhcloud-cli_0.16.0_checksums.txtb6e44d42a17b46ca9b842d951cab3ee84fe6af0d94f9560e89540b4597ad7234

Verify with GitHub's tooling as shown later rather than trusting any printed value, including these.

Build fingerprints of the malicious binary

The reporter ran go version -m on the replaced Linux arm64 binary before deletion. Go embeds build information in every binary, and this one gave itself away:

FieldMalicious buildRelease build
Module versionv0.15.1-0.20261013083505-5ead8ef9b43d+dirtyThe tag, for example v0.15.0
vcs.revision5ead8ef9b43d354dfb5caadff803b11afc8c5c58Absent
vcs.time2026-10-13T08:35:05ZAbsent
vcs.modifiedtrueAbsent
ldflags-s -w only-s -w -extldflags -static -X github.com/ovh/ovhcloud-cli/internal/version.Version= plus the tag
-trimpathNot usedUsed, per .goreleaser.yaml

Sources: issue #300, OpenSourceMalware, .goreleaser.yaml. The v0.15.0 tag still points at 760fd72de93cbea3b507eb36dc0095991e4048e5, which the Go module proxy serves. The replacement was built with plain go build from a later main commit with uncommitted changes, which is where the stealer code would have lived.

What the malware collects

OVHcloud describes the payload as a recompiled CLI with embedded code that steals credentials and files. Its appendix lists:

CategoryPaths named in the advisory
Editor and AI assistant data~/.config/Code/User/globalStorage/state.vscdb, ~/.config/Cursor/User/globalStorage/state.vscdb, Continue index databases (continue.continue/index/index.sqlite), Cody chat history (sourcegraph.cody-ai/chat/chat-history.db), ~/.local/share/opencode/opencode.db, ~/.config/opencode/opencode.db, ~/.hermes/sessions, ~/.hermes/.hermes_history, ~/.claude
AWS~/.aws/credentials, ~/.aws/config, ~/.aws/cli/cache
Google Cloud~/.config/gcloud/credentials.db, ~/.config/gcloud/access_tokens.db, ~/.config/gcloud/configurations/config_default
Azure~/.azure/azureProfile.json, ~/.azure/clouds.config, ~/.azure/config, ~/.azure/msal_token_cache.json
Containers and infrastructure~/.docker/config.json, ~/.dockercfg, ~/.kube/config, ~/.terraform.d/credentials.tfrc.json, ~/.config/doctl/config.yaml
Other~/.ssh, ~/.netrc, ~/.gitconfig, npm configuration

The AI assistant entries stand out. Chat histories and editor state often hold pasted tokens and connection strings. OpenSourceMalware and IT-Connect also advise rotating ~/.ovh.conf, .npmrc, .pypirc and .env files; that is sensible advice rather than a confirmed target list.

Indicators of compromise

TypeValueContextSource
Temp folderenvchecker- plus 9 or 10 digitsCreated on each run and never deleted, so its presence means the malware ranGHSA
IP and port45.32.148[.]174:443, path /uploadArchive upload target; may changeGHSA
Solana RPC hostssolana.drpc[.]org, solana-rpc.publicnode[.]com, solana.api.pocket[.]network, api.mainnet-beta.solana[.]com, solana.api.onfinality[.]io/publicChosen at random by the CLI payloadGHSA
Solana walletBHaDaVbdCYFNaKZGn7yLXFneWoJHZgLiBYkHxVrjyALFUsed by the CLI payloadGHSA
Solana walletBjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SCInitial compromise and March loader; GlassWorm indicatorGHSA, The Stack, OpenSourceMalware
IP162.33.177[.]177Persistent Node.js service via plist on the developer's personal MacGHSA, The Stack
File~/init.jsonGlassWorm loader throttle file on macOSOpenSourceMalware
Filepreinstall.js, SHA-256 43df42fd79f6f81e530892b54634598770d6e496796a863c596e616bcb3fec70Loader in the personal repositoriesOpenSourceMalware
Stage twoSHA-256 0f79989912572a24ba13baabb32b3fd084496271c51448d47b66fe1aedbf293c, 4,407 bytesDecrypted second stageOpenSourceMalware
Stage one key and IVKey zetqHyfDfod88zloncfnOaS9gGs90ONX, IV a041fdaa0521fb5c3e26b217aaf24115AES-256-CBC parameters stored in the loaderOpenSourceMalware
GitHub accountHassenBF (ID 4911006)Uploader of the replaced assets; suspended per OVHcloudIssue #300, OpenSourceMalware, GHSA

The advisory names two wallets: one for the CLI payload, one for the original infection. Hunt for both. Public Solana RPC hosts are legitimate services, so alert on them from developer machines and build runners with no reason to use a blockchain rather than blocking them company wide.

How many people were exposed

OVHcloud's advisory gives a window of about 1 hour 33 minutes and roughly 250 downloads. It does not say how many ran or how they split by platform. Some were probably CI jobs, which hold different secrets from laptops.

How to check if you were affected

Steps 1 to 4 tell you whether a bad binary reached a machine; step 5 tells you whether it ran. Never run a suspect binary to read its version; go version -m reads the file without executing it.

1. Find every copy

# macOS and Linux
command -v ovhcloud
find / -xdev -type f -name 'ovhcloud' 2>/dev/null
ls -l "$HOME/.local/bin/ovhcloud" ./bin/ovhcloud 2>/dev/null
ls -l "$(brew --prefix 2>/dev/null)/Caskroom/ovhcloud-cli" 2>/dev/null
# Windows
Get-Command ovhcloud -ErrorAction SilentlyContinue
Get-ChildItem -Path C:\ -Filter ovhcloud.exe -Recurse -ErrorAction SilentlyContinue |
  Select-Object FullName, LastWriteTime, Length

A file written on 19 October between 12:47 and 14:21 UTC deserves the most attention.

2. Read the build info

go version -m /path/to/ovhcloud

A pipeline release has no vcs.* lines and a tag as its version. vcs.modified=true, a +dirty suffix, or v0.15.1-0.20261013083505-5ead8ef9b43d+dirty means the malicious build.

3. Compare hashes

md5 -q /path/to/ovhcloud                 # macOS
md5sum /path/to/ovhcloud                 # Linux
shasum -a 256 ovhcloud-cli_*.tar.gz      # macOS archives
sha256sum ovhcloud-cli_*.tar.gz          # Linux archives
Get-FileHash -Algorithm MD5 C:\path\to\ovhcloud.exe

A match with a malicious value is conclusive. No match is reassuring only if step 2 is also clean.

4. Check Homebrew and download caches

brew list --cask --versions ovhcloud-cli
ls -l "$(brew --cache)"/downloads/ 2>/dev/null | grep -i ovhcloud

Hash any cached v0.15.0 archive.

5. Look for signs it ran

ls -ld /tmp/envchecker-* 2>/dev/null                                  # Linux
ls -ld "$TMPDIR"envchecker-* /tmp/envchecker-* 2>/dev/null            # macOS
ls -l ~/init.json 2>/dev/null                                         # GlassWorm loader, macOS
grep -rl '162.33.177.177' ~/Library/LaunchAgents /Library/LaunchAgents /Library/LaunchDaemons 2>/dev/null
Get-ChildItem $env:TEMP -Directory -Filter 'envchecker-*' -ErrorAction SilentlyContinue |
  Select-Object FullName, CreationTime

An envchecker folder means the CLI payload ran; its creation time tells you when. Record it before deleting anything.

6. Check network evidence

Search DNS, proxy, firewall and EDR logs from 19 October onward for 45.32.148.174, 162.33.177.177 and the five Solana RPC hostnames above. A connection to 45.32.148.174 on 443 from a machine with an envchecker folder means assume the files left.

7. Check CI and container builds

git grep -nE 'ovhcloud-cli/(releases/download|main/install\.sh)' -- \
  '*.yml' '*.yaml' 'Dockerfile*' '*.sh' 'Makefile' '*.tf'

gh search code 'ovhcloud-cli/releases/download' --owner YOUR_ORG
gh search code 'ovhcloud-cli/main/install.sh' --owner YOUR_ORG

gh run list --repo YOUR_ORG/REPO --created 2026-10-19 --limit 200 \
  --json databaseId,createdAt,name,conclusion

Read logs for runs inside the window. For images built then, docker history --no-trunc IMAGE:TAG | grep -i ovhcloud shows the layer; to hash the binary, use docker create and docker export and inspect from outside rather than starting the container.

What to rotate, and in what order

If a malicious binary ran, treat every secret readable by that user on that machine as stolen. Rotate from a different, clean machine.

  1. OVHcloud API credentials. The CLI's own keys (~/.ovh.conf or environment variables) reach your OVHcloud account directly. Revoke the application and consumer keys, create new ones on a clean machine, and review recent API activity.
  2. Cloud providers. Deactivate AWS access keys (aws iam list-access-keys, then aws iam update-access-key --status Inactive), revoke Google Cloud credentials and service account keys, and revoke Azure sessions and refresh tokens. Read audit logs from 12:47 UTC on 19 October.
  3. Kubernetes and registries. Rotate kubeconfig credentials for every cluster in ~/.kube/config and registry tokens in ~/.docker/config.json.
  4. SSH keys. Generate new keys and remove the old public keys from servers and Git hosts (gh ssh-key list).
  5. Git and package tokens. ~/.netrc, Git credential helpers, npm (npm token list, npm token revoke), PyPI, Terraform Cloud, DigitalOcean.
  6. AI assistant keys and histories. Rotate provider keys and any secret pasted into a chat.
  7. .env files and project folders on that machine.

Then delete the binary, install v0.16.0 through a verified route, and rebuild images built in the window. If you also find ~/init.json, the plist talking to 162.33.177.177, or color-picker-universal 2.8.91, the machine is a GlassWorm victim in its own right: reimage it and review what your Git identity pushed since the extension went in.

Installing a clean version

v0.16.0 is an immutable release with a signed tag and a release attestation. GitHub documents two verification commands:

gh release verify v0.16.0 --repo ovh/ovhcloud-cli

gh release download v0.16.0 --repo ovh/ovhcloud-cli \
  --pattern 'ovhcloud-cli_Linux_x86_64.tar.gz' \
  --pattern 'ovhcloud-cli_0.16.0_checksums.txt'
gh release verify-asset v0.16.0 ./ovhcloud-cli_Linux_x86_64.tar.gz --repo ovh/ovhcloud-cli
grep 'Linux_x86_64' ovhcloud-cli_0.16.0_checksums.txt | sha256sum -c -

On macOS use shasum -a 256 -c -. Or build from tagged source:

go install github.com/ovh/ovhcloud-cli/cmd/ovhcloud@v0.16.0

The Go module proxy lists v0.16.0 at 6ef70c2389cd22c72821cf8d435365d0dcbe2729, and Go's checksum database refuses later changes to a tag's content. A source build lacks the release ldflags, so its version output differs from the release binary; that is not the same signal as +dirty.

For CI, pin the archive hash. This is the control that caught the incident:

ARG OVHCLOUD_CLI_VERSION=v0.16.0
ARG OVHCLOUD_CLI_SHA256=527fb1c9bde0680837601e08bc064783b57eb301ab5a81fb9c1fc17f466f2898
RUN curl -fsSLo /tmp/ovhcloud.tgz \
      "https://github.com/ovh/ovhcloud-cli/releases/download/${OVHCLOUD_CLI_VERSION}/ovhcloud-cli_Linux_x86_64.tar.gz" \
 && echo "${OVHCLOUD_CLI_SHA256}  /tmp/ovhcloud.tgz" | sha256sum -c - \
 && tar -xzf /tmp/ovhcloud.tgz -C /usr/local/bin ovhcloud \
 && rm /tmp/ovhcloud.tgz

Take the hash from your own gh release verify-asset run, not from this post.

What changed since the first reports

  • Root cause. On 19 October Klaba framed it as a configuration issue, release immutability being off. The advisory and post mortem added the deeper cause: an account compromised in October 2025 through an extension, with stolen keys used a year later.
  • Attribution. BleepingComputer and SecurityWeek reported that OVHcloud had not mentioned GlassWorm and that the link came from OpenSourceMalware. OVHcloud's post mortem, per The Stack, then tied the infection wallet to GlassWorm itself.
  • Payload. OpenSourceMalware could not get a sample before deletion. OVHcloud's advisory, crediting Ivan Kwiatkowski and VirLabs.ai for malware analysis, now gives target paths, the envchecker folder, the upload IP and the wallets.
  • Downloads. Early coverage treated about 250 as unconfirmed; OVHcloud's advisory now states it.
  • Scope and fixes. OVHcloud says it scanned all its GitHub repositories, found no other changes, suspended the account, enabled immutable releases organisation wide, enabled private vulnerability reporting, and plans commit signing and release attestations.

Open questions

  • Which archives on v0.11.0 and v0.12.0 were replaced. The reporter checked only v0.13.0 to v0.15.0, and most replaced archives have no public SHA-256.
  • The Homebrew route. Our reading says the cask's hash check should have blocked it; nobody has confirmed either way.
  • Browser or editor extension. The advisory and GlassWorm reporting differ.
  • What the "stolen keys" were, and whether organisation level secrets such as the Homebrew tap token were reachable, a question OpenSourceMalware raises.
  • Where the dirty build was made. It came from a real main commit with local changes; The Stack says not from the personal laptop.
  • Two deleted branches in ovh/ovhcloud-docs on 15 October (dev/test-changest, test/trigger-probe), flagged by OpenSourceMalware. No explanation yet.
  • GoReleaser settings. On 22 October, .goreleaser.yaml on main still had replace_existing_artifacts: true, which GoReleaser's documentation says deletes and reuploads an existing asset. With immutable releases on, GitHub should refuse that on new releases. We note it because other projects copy GoReleaser examples.

What would have helped

Practices any team can use

If you publish binaries through GitHub Releases:

  • Turn on immutable releases for the organisation. GitHub's documentation says this locks a release's tag and assets, leaves only title and notes editable, and generates a release attestation. IT-Connect notes it applies to releases published after it is enabled, so consider republishing your current version.
  • Publish only from a service identity. v0.16.0 was uploaded by the ovh-cds automation account. Make that the only identity able to create or change releases.
  • Do not let your release tool overwrite assets. Review replace_existing_artifacts in GoReleaser, and use its preflight check, which can abort when a tag already has an immutable release.
  • Sign and attest. A checksum next to the binary proves only that whoever uploaded one uploaded both. Signatures from a key the uploader lacks, or build provenance tied to a CI workflow, break that circle.
  • Verify in your installer. By our reading the OVHcloud install.sh does no check at all.
  • Alert on late uploads. An asset uploaded weeks after its release is unusual. This lists every asset with uploader and timestamps:
gh api repos/OWNER/REPO/releases --paginate \
  --jq '.[] | .tag_name as $t | .published_at as $p | .assets[] |
        [$t, $p, .name, .uploader.login, .created_at, .updated_at] | @tsv'
  • Enable private vulnerability reporting so the next finder does not have to post in public.
  • Keep release rights narrow and treat every contributor laptop with those rights as part of the supply chain: hardware backed two factor authentication, short lived fine grained tokens, and extension allowlists.

If you consume CLIs and binaries:

  • Pin hashes in CI and Dockerfiles, stored in your repository and reviewed like code. That is what caught this.
  • Prefer source or signed routes. go install at a tag, the hash pinned Homebrew cask, and the vendor built image were all unaffected.
  • Verify attestations with gh release verify and gh release verify-asset.
  • Give jobs only the secrets they use, and prefer short lived OIDC credentials, so a tool download step does not sit next to every deploy key.
  • Know where you use each tool. See SBOM incident response and our playbook for a compromised dependency.

This is the second October incident aimed at release machinery rather than source. The tensorlake compromise showed provenance helps only if consumers check it; this one shows a checksum helps only if it lives where the attacker cannot write.

Where Safeguard fits

Safeguard would not have prevented this. The attacker used a real contributor account to replace files on GitHub. Safeguard's part is answering "where do we use this" quickly and doing the cleanup with approval.

Inventory. safeguard scan --dir . builds an SBOM for a repository, safeguard scan --image <name> does the same for a container image, and safeguard scan --sbom <file> ingests an existing SBOM; results land as JSON under ./output. The platform then shows which projects contain a given component and version, so a Go module dependency on github.com/ovh/ovhcloud-cli is a lookup. A binary fetched with curl in a Dockerfile is different: whether it appears in an image SBOM depends on what can be read from it, so pair the platform view with the searches in step 7.

Policy. Platform policies and gates evaluate what a build contains, which helps keep a retired version from returning through an old branch.

Cleanup. The Safeguard CLI's assistant can edit code and run commands, asking before each one in the default flow; /permissions offers Ask, Accept edits, Plan, Auto and Strict. Ask it to find every Dockerfile and workflow that downloads ovhcloud-cli, switch each to a pinned v0.16.0 download with a sha256sum -c check, and run the build. The desktop and phone apps can send that work to a computer running the CLI and approve each command; safeguard exec runs checks in CI. Rotating credentials at OVHcloud and your cloud providers is still yours to do.

Zero-Day Discovery and Gold

  • Zero-Day Discovery looks for previously unknown vulnerabilities before a CVE exists, in an agentic mode and a deterministic scanner mode, across seven package ecosystems, and issues findings as SGZ advisories before they reach public CVE databases. A tampered release binary is not a code vulnerability and has no CVE.
  • Gold Open Source at gold.safeguard.sh is free with no login: CVE intelligence with CVSS, EPSS, KEV status, affected and fixed versions and a recommended safe version, plus a free read API and a CI gate GitHub Action.
  • Gold Registry at registry.safeguard.sh is paid: hardened drop in components and container images held to zero exploitable critical or high CVEs, built to SLSA L3 or higher, Sigstore signed, with evidence pages. An artifact loses its badge on regression.

The link to this incident is a habit, not a record: pull components from a curated source that signs and attests what it ships, and check those signatures. For the month's other incidents, see October 2026 security incidents and what would have helped.

Common mistakes

  • Checking against the checksum file on the same release. It was replaced too.
  • Running the binary to see its version. That runs the stealer. Use go version -m.
  • Reading "14:47 to 16:21" as UTC. Those are Paris times; UTC is 12:47 to 14:21.
  • Checking only v0.15.0. OVHcloud lists v0.11.0 to v0.15.0.
  • Deleting the binary and stopping. If it ran, rotation is the remediation.
  • Rotating from the infected machine. Use a clean one.
  • Forgetting runners and images. Self hosted runners keep files; images built in the window carry the binary until rebuilt.
  • Unpinning to fix a broken build. Move to v0.16.0 with a new pinned hash, not to latest without one.

FAQ

Which OVHcloud CLI versions were affected?

Release binaries for v0.11.0 to v0.15.0 on Darwin arm64 and x86_64, Linux arm64 and x86_64 and Windows x86_64, downloaded between 12:47 and 14:21 UTC on 19 October 2026. v0.10.0 and earlier and v0.16.0 and later are not affected, per GHSA-862f-55f5-vmvw.

I installed v0.15.0 in September. Am I affected?

Not by this, if you did not download it again in the window. Hash it against the "Original" values above and upgrade to v0.16.0.

Was the source code or the Go module changed?

OVHcloud says no. The v0.15.0 tag still points at 760fd72, served by the Go module proxy. go install and source builds were not affected.

Was Homebrew affected?

Reports say yes. The v0.15.0 cask pinned the original hashes, so by our reading Homebrew should have refused the replaced archives. That is unconfirmed; check the binary on disk.

Is this GlassWorm?

OVHcloud's post mortem, per The Stack, links the initial infection to GlassWorm through a Solana wallet and an extension on GlassWorm lists, and OpenSourceMalware reached the same link independently. The CLI payload used a different wallet. Well supported, not formally proven.

What does the malware steal?

Per OVHcloud: AWS, Google Cloud and Azure credentials, Docker, Kubernetes, Terraform and DigitalOcean configuration, SSH keys, .netrc, Git and npm configuration, and data from VS Code, Cursor, Continue, Cody, OpenCode, Hermes and Claude. It stages files in an envchecker temp folder and uploads them.

Is v0.16.0 safe?

It was published by OVHcloud's automation account as an immutable release with a signed tag and attestation after containment. Verify your download with gh release verify-asset.

Why didn't the checksum file catch it?

The attacker replaced checksums.txt in the same session. Only a value stored elsewhere, like the customer's pinned hash, caught it.

Fix it without waiting for upstream

This incident was a tampered artifact, not a vulnerable dependency, and OVHcloud shipped a clean rebuild the same afternoon. The habits below, verified versions and signed components, are also what keep a tampered artifact out of your builds.

In incidents like this one, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would have helped

None of these demos show this incident. Each is a real Safeguard session; the text says how the same flow applies here.

The first demo is a CLI session that fixes a lodash CVE: the assistant reads the finding and advisory, asks before each command, edits package.json, runs the install and tests with your approval, and pushes nothing. Here you would ask the same session to find every Dockerfile and workflow that downloads ovhcloud-cli, switch each to a pinned v0.16.0 archive with a sha256sum -c check, and run the build, approving each step.

Click through it at your own pace, or open it full screen.

The second demo is a desktop app session: the CLI session shows in the conversation list, you ask for work on your computer, pick the folder, then Approve or Deny each command and file edit, ending with an upgrade done and tests passing. Here you would pick your infrastructure folder, ask it to run the go version -m and hash checks from this post on any ovhcloud binary it finds, and deny anything that would execute the binary itself.

Click through it at your own pace, or open it full screen.

The third demo is a real Android session: work is sent to a computer running the CLI, and the command is approved on the phone with Allow once, Allow for turn or Deny. In this incident the on call engineer could approve each Dockerfile change and image rebuild from a phone while rotation happens elsewhere.

Click through it at your own pace, or open it full screen.

To try it on your own projects, download Safeguard and follow the CLI documentation.

Quick check

Your CI verified the OVHcloud CLI download against checksums.txt from the same release. Were you protected on 19 October?

No. The attacker replaced checksums.txt along with the archives, so the check would have passed. Only a hash recorded before the swap and stored outside the release would have failed the build.

You find an envchecker folder in your temp directory but the ovhcloud binary is already gone. What does that mean?

The malware ran on that machine. OVHcloud says it creates the folder on each run and never deletes it. Treat every secret on the machine as stolen and rotate from a clean host.

Sources

Last reviewed: 22 October 2026. This is a developing story; check the linked advisories for the latest status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.