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:
- 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 inovh/ovhcloud-cliand suggests it held write access through the@ovh/ovhcloud-cli-ownersteam thatCODEOWNERSassigns to every path. That last point is the researchers' inference. - 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.
- The checksum file sat next to the binaries it described. The attacker replaced
checksums.txttoo, 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) | Event | Source |
|---|---|---|
| 20 Oct 2025 | Developer installs color-picker-universal 2.8.91 on a personal MacBook | OVHcloud post mortem via The Stack |
| 2 Mar 2026 | v0.10.0 released (last clean release before the affected range) | GitHub releases, Homebrew tap |
| 5 Mar 2026, 02:41:17 to 02:41:59 | Force pushes add the loader to three personal repositories of HassenBF; a fourth follows on 6 Mar at 22:49:53 | OpenSourceMalware |
| 14 Apr to 31 Aug 2026 | v0.11.0 (14 Apr), v0.12.0 (29 May), v0.13.0 (25 Aug), v0.14.0 (31 Aug) released | GitHub releases, Homebrew tap |
| 11 Sep 2026, 14:51:47 | v0.15.0 tag commit 760fd72 | Go module proxy |
| 16 Sep 2026, about 16:52 | Original v0.15.0 archives uploaded | GitHub release assets |
| 13 Oct 2026, 08:35:05 | Commit 5ead8ef merged to main; the malicious binary was later built from it | Issue #300, OpenSourceMalware |
| 16 Oct 2026, 14:38:10 | Commit 6ef70c2, later tagged v0.16.0 | Go module proxy |
| 19 Oct, 12:47 | First malicious upload (v0.15.0 Darwin) | GHSA, issue #300 |
| 19 Oct, 12:57 | All v0.15.0 binaries and checksums.txt replaced | GHSA, issue #300 |
| 19 Oct, 13:01 to 13:04 | v0.14.0 Linux arm64 and checksums replaced | Issue #300 |
| 19 Oct, 13:08 to 13:11 | v0.13.0 Linux arm64 and checksums replaced | Issue #300 |
| 19 Oct, about 13:12 | Last replacement the reporter observed | Issue #300 |
| 19 Oct, 13:30 | All v0.11.0 to v0.15.0 binaries replaced, per OVHcloud | GHSA |
| 19 Oct, 13:50:09 | Customer opens issue #300 after a pinned hash check fails | GHSA, OpenSourceMalware |
| 19 Oct, 13:57 | OVHcloud begins investigating | GHSA |
| 19 Oct, 14:20 | Scan of all OVHcloud GitHub repositories begins | GHSA |
| 19 Oct, 14:21 | Malicious binaries removed | GHSA, issue #300 |
| 19 Oct, 16:00:23 | v0.16.0 tag push starts the build and release workflows | OpenSourceMalware |
| 19 Oct, 16:52 | Public announcement on X | GHSA |
| 19 Oct, 17:16:43 | v0.16.0 assets uploaded by ovh-cds, published as an immutable release with a release attestation | GitHub release assets, GHSA |
| 19 Oct | Homebrew cask moved to v0.16.0 by ovh-cds (commit 2e9aa7d) | ovh/homebrew-tap |
| 20 Oct | GHSA-862f-55f5-vmvw published; private vulnerability reporting enabled; issue closed | Issue #300, GHSA |
| 21 Oct | The Stack adds post mortem details | The 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
| Release | Originally published | Replaced on 19 Oct | Not replaced | Status now |
|---|---|---|---|---|
| v0.10.0 and earlier | 2 Mar 2026 and earlier | Nothing | All | Not affected (GHSA) |
| v0.11.0 | 14 Apr 2026 | Darwin arm64 and x86_64, Linux arm64 and x86_64, Windows x86_64, checksums (per OVHcloud) | Linux i386, Windows i386, Windows arm64, Js wasm | Affected; replaced assets deleted |
| v0.12.0 | 29 May 2026 | Same, per OVHcloud | Same four | Affected; replaced assets deleted |
| v0.13.0 | 25 Aug 2026 | Linux arm64 and checksums confirmed by reporter; rest per OVHcloud | Same four | Affected; replaced assets deleted |
| v0.14.0 | 31 Aug 2026 | Linux arm64 and checksums confirmed by reporter; rest per OVHcloud | Same four | Affected; replaced assets deleted |
| v0.15.0 | 16 Sep 2026 | All five platforms and checksums.txt confirmed | Same four | Affected; replaced assets deleted |
| v0.16.0 | 19 Oct 2026 | Not applicable | Not applicable | Fixed: 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
| Route | Exposed in the window? | Why | Source |
|---|---|---|---|
install.sh from main | Yes | Fetches the latest release archive; no checksum or signature check | Our reading of install.sh; OpenSourceMalware, IT-Connect |
| Direct download from GitHub Releases | Yes | These were the replaced files | GHSA |
| CI jobs and Dockerfiles that download an archive without a pinned hash | Yes | Same files | OpenSourceMalware |
| CI jobs that pin the archive SHA-256 | No; they fail | How this was found | Issue #300 |
Homebrew cask ovh/tap/ovhcloud-cli | Disputed | The cask pins the original hashes; see below | OpenSourceMalware, SecurityWeek; our reading of the cask |
go install .../cmd/ovhcloud@... | No | Builds tagged source through the Go module proxy | GHSA, OpenSourceMalware, Go module proxy |
| Building from source | No | Source unchanged | GHSA |
ovhcom/ovhcloud-cli Docker image | No | Built by OVHcloud's internal pipeline | OpenSourceMalware, BleepingComputer |
| i386, Windows arm64, wasm builds | No | Not replaced | Issue #300 |
| Installed before 12:47 UTC, no update in the window | No | The original file was already on disk | OpenSourceMalware |
| The CLI's own update feature | Possibly | IT-Connect says "in principle"; not confirmed | IT-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.
| File | Platform | MD5 |
|---|---|---|
ovhcloud | Darwin arm64 | 6f2f086239f3d88eb2750e871901927d |
ovhcloud | Darwin x86_64 | 347e65eb7f2d78d1c6872690272a6cc3 |
ovhcloud | Linux arm64 | 01f57206d37c28cf711677f612052913 |
ovhcloud | Linux x86_64 | 301bff0b51980b9e76c93633e7cfd3bd |
ovhcloud.exe | Windows x86_64 | 6a3cf153338c416169cda2fe704c8848 |
Archive hashes (SHA-256)
| File | Hash | Meaning | Source |
|---|---|---|---|
v0.15.0 ovhcloud-cli_Linux_arm64.tar.gz | 3833daffcaaaab27e4e0e0833240ffcb1df8df648f4ec0bc4a75c176e28933cb | Malicious | Issue #300 |
v0.15.0 ovhcloud-cli_Linux_arm64.tar.gz | 5c2beb56e6d83a4b426565a1557791ef759316abcc4effb38dd627c90aeb80e0 | Original | Issue #300, cask b70a654 |
v0.15.0 ovhcloud-cli_Linux_x86_64.tar.gz | 1109d157dcf677bf3a7616e4b27b4fe3b5cc984e1cc82987592016541a6c88ed | Original | Cask b70a654 |
v0.15.0 ovhcloud-cli_Darwin_arm64.tar.gz | 26b78ca0045dd21db040b2991d8a301bafcb898454ea80b5285a04e184132142 | Original | Cask b70a654 |
v0.15.0 ovhcloud-cli_Darwin_x86_64.tar.gz | 5aadbdc9f9eae915fdb13e189fa8e5b492b302418c8b9ef3b57d720128fbbee8 | Original | Cask 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.
| Asset | SHA-256 |
|---|---|
ovhcloud-cli_Darwin_arm64.tar.gz | 79437d459e25e2594b47c976f765e3abc51668e1dd809d08eaac448c6ca075a0 |
ovhcloud-cli_Darwin_x86_64.tar.gz | 36363eb9dfe95ee403ad4645b831c7d48e8dd288d45c642afd9c22707d84ffb0 |
ovhcloud-cli_Linux_arm64.tar.gz | 6ad093b46729269dca35a85a0df864557891add5c98a099ad1631980fe0aa1be |
ovhcloud-cli_Linux_x86_64.tar.gz | 527fb1c9bde0680837601e08bc064783b57eb301ab5a81fb9c1fc17f466f2898 |
ovhcloud-cli_Windows_x86_64.zip | b3ee6dc089758f0569fa34bd558ec0cec9ce039254315fa91bcbbc2468f49263 |
ovhcloud-cli_0.16.0_checksums.txt | b6e44d42a17b46ca9b842d951cab3ee84fe6af0d94f9560e89540b4597ad7234 |
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:
| Field | Malicious build | Release build |
|---|---|---|
| Module version | v0.15.1-0.20261013083505-5ead8ef9b43d+dirty | The tag, for example v0.15.0 |
vcs.revision | 5ead8ef9b43d354dfb5caadff803b11afc8c5c58 | Absent |
vcs.time | 2026-10-13T08:35:05Z | Absent |
vcs.modified | true | Absent |
| ldflags | -s -w only | -s -w -extldflags -static -X github.com/ovh/ovhcloud-cli/internal/version.Version= plus the tag |
-trimpath | Not used | Used, 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:
| Category | Paths 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
| Type | Value | Context | Source |
|---|---|---|---|
| Temp folder | envchecker- plus 9 or 10 digits | Created on each run and never deleted, so its presence means the malware ran | GHSA |
| IP and port | 45.32.148[.]174:443, path /upload | Archive upload target; may change | GHSA |
| Solana RPC hosts | solana.drpc[.]org, solana-rpc.publicnode[.]com, solana.api.pocket[.]network, api.mainnet-beta.solana[.]com, solana.api.onfinality[.]io/public | Chosen at random by the CLI payload | GHSA |
| Solana wallet | BHaDaVbdCYFNaKZGn7yLXFneWoJHZgLiBYkHxVrjyALF | Used by the CLI payload | GHSA |
| Solana wallet | BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC | Initial compromise and March loader; GlassWorm indicator | GHSA, The Stack, OpenSourceMalware |
| IP | 162.33.177[.]177 | Persistent Node.js service via plist on the developer's personal Mac | GHSA, The Stack |
| File | ~/init.json | GlassWorm loader throttle file on macOS | OpenSourceMalware |
| File | preinstall.js, SHA-256 43df42fd79f6f81e530892b54634598770d6e496796a863c596e616bcb3fec70 | Loader in the personal repositories | OpenSourceMalware |
| Stage two | SHA-256 0f79989912572a24ba13baabb32b3fd084496271c51448d47b66fe1aedbf293c, 4,407 bytes | Decrypted second stage | OpenSourceMalware |
| Stage one key and IV | Key zetqHyfDfod88zloncfnOaS9gGs90ONX, IV a041fdaa0521fb5c3e26b217aaf24115 | AES-256-CBC parameters stored in the loader | OpenSourceMalware |
| GitHub account | HassenBF (ID 4911006) | Uploader of the replaced assets; suspended per OVHcloud | Issue #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.
- OVHcloud API credentials. The CLI's own keys (
~/.ovh.confor 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. - Cloud providers. Deactivate AWS access keys (
aws iam list-access-keys, thenaws 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. - Kubernetes and registries. Rotate kubeconfig credentials for every cluster in
~/.kube/configand registry tokens in~/.docker/config.json. - SSH keys. Generate new keys and remove the old public keys from servers and Git hosts (
gh ssh-key list). - Git and package tokens.
~/.netrc, Git credential helpers, npm (npm token list,npm token revoke), PyPI, Terraform Cloud, DigitalOcean. - AI assistant keys and histories. Rotate provider keys and any secret pasted into a chat.
.envfiles 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
envcheckerfolder, 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
maincommit with local changes; The Stack says not from the personal laptop. - Two deleted branches in
ovh/ovhcloud-docson 15 October (dev/test-changest,test/trigger-probe), flagged by OpenSourceMalware. No explanation yet. - GoReleaser settings. On 22 October,
.goreleaser.yamlonmainstill hadreplace_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-cdsautomation account. Make that the only identity able to create or change releases. - Do not let your release tool overwrite assets. Review
replace_existing_artifactsin GoReleaser, and use itspreflightcheck, 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.shdoes 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 installat a tag, the hash pinned Homebrew cask, and the vendor built image were all unaffected. - Verify attestations with
gh release verifyandgh 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
latestwithout 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 situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull 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
- OVHcloud: GHSA-862f-55f5-vmvw, ovhcloud-cli security incident, October 19th, 2026
- ovh/ovhcloud-cli issue #300: release assets of v0.13.0 to v0.15.0 replaced
- ovh/ovhcloud-cli releases
- ovh/ovhcloud-cli v0.15.0 release assets
- ovh/ovhcloud-cli v0.16.0 release assets
- ovh/ovhcloud-cli README
- ovh/ovhcloud-cli install.sh
- ovh/ovhcloud-cli .goreleaser.yaml
- ovh/homebrew-tap commit history
- ovh/homebrew-tap ovhcloud-cli cask at v0.15.0 (commit b70a654)
- Go module proxy: ovhcloud-cli version list
- Go module proxy: ovhcloud-cli v0.15.0 info
- Go module proxy: ovhcloud-cli v0.16.0 info
- GitHub Docs: Immutable releases
- GitHub Docs: Verifying the integrity of a release
- GoReleaser: release customization
- OpenSourceMalware: GlassWorm hacks OVHcloud's CLI tool
- BleepingComputer: OVHcloud CLI releases backdoored on GitHub to steal developer secrets
- SecurityWeek: Hijacked developer account used to plant stealer in OVHcloud CLI
- The Stack: OVHcloud CLI repo compromised, customer reports incident
- IT-Connect: OVHcloud CLI binaries on GitHub were tampered with by an infostealer
- Truesec: GlassWorm, self propagating VS Code extension worm
Last reviewed: 22 October 2026. This is a developing story; check the linked advisories for the latest status.