~/f4n6 $ grep -r "GlassWorm swaps OVHcloud CLI binaries to steal cloud and AI credentials" ./investigations/ --include="*.md"

GlassWorm swaps OVHcloud CLI binaries to steal cloud and AI credentials

Jeff Davies 24 Oct 2026 11 min read

1. Executive summary

On 19 October 2026, between 12:47 and 14:21 UTC, an attacker using the GitHub account HassenBF replaced the release binaries on five published releases of OVHcloud's official CLI (v0.11.0 through v0.15.0) with a credential stealer, and replaced checksums.txt last so the published hashes matched the swapped files. OVHcloud deleted the assets after 94 minutes and published a clean, immutable, attested v0.16.0; the advisory discloses roughly 250 downloads during the window. The stealer targets cloud CLI credentials, Kubernetes and Docker configs, Terraform and npm tokens, SSH keys, .gitconfig, .netrc, and AI-agent credential stores (Claude, Cursor, VS Code, Continue, Cody, OpenCode, Hermes), staging the collection in never-deleted /tmp/envchecker-* directories and POSTing it to https://45.32.148[.]174/upload as Solana-RPC-shaped JSON. No CVE has been assigned and there is no CISA KEV entry; the risk to EMEA financial services is concentrated in engineering and platform teams that installed the CLI from GitHub releases or the install script during the window, where any credential the host could read should be treated as exposed. The GlassWorm attribution is unconfirmed: the actor has no MITRE ATT&CK profile, OVHcloud has not named GlassWorm in its statements, and the link between the March 2026 account compromise and the October swap is OpenSourceMalware's inference.

2. Regulatory framing

Article Trigger (the fact in this item) Practical impact
DORA Art. 28 The compromise entered through a third-party software supplier's release channel (OVHcloud's GitHub releases), not through the client's own code, and the malicious build was never produced by OVHcloud's CI/CD. Clients using ovhcloud-cli must treat it as an ICT third-party dependency: confirm whether the tool is in the estate, obtain OVHcloud's GHSA-862f-55f5-vmvw and postmortem, and record the supplier's release-integrity failure (immutability not enabled) in the third-party register.
DORA Art. 19 If a host that ran a swapped binary held credentials or data tied to regulated services, the exposure is an ICT-related incident that may meet the major-incident thresholds and require reporting to the competent authority. Run the incident classification before deciding; the four-second install-to-exfiltration interval in the StepSecurity replay means any execution should be assumed to have completed exfiltration, which bears on the "clients affected" and "duration" criteria.
NIS2 Art. 21(2)(d) The failure was in the supplier's release pipeline (GitHub release immutability off), which is a supply chain security control the client relies on when consuming the tool. In-scope entities should verify that their own dependency intake pins hashes and verifies attestations, and should require equivalent controls of suppliers under Art. 21(2)(d).

3. Technical analysis & attack chain

The attack replaced release assets on existing GitHub releases without moving a tag, running a CI workflow, or touching the source repository. GitHub release immutability was not enabled on ovh/ovhcloud-cli, so any account with write access could overwrite assets on months-old releases at unchanged URLs. The attacker worked newest-first over about 25 minutes: v0.15.0 at 12:47, its checksums.txt at 12:57, then v0.14.0 and v0.13.0 through 13:12, with v0.11.0 and v0.12.0 also replaced per OVHcloud. The swap was caught at 13:50 by a customer whose CI pins the SHA-256 of the Linux arm64 tarball (issue #300), and OVHcloud deleted the assets at 14:21.

Confirmed steps:

  1. Initial access, October 2025. Per OVHcloud's postmortem, the developer installed a malicious VS Code extension, color-picker-universal 2.8.91, on a personal MacBook on 20 October 2025. It installed a persistent NodeJS service registered via plist that communicated with 162.33.177[.]177. OVHcloud does not believe the corporate laptop was compromised; the release uploads used stolen keys.
  2. Dormancy. Between May and September 2026 the account kept landing ordinary pull requests in ovh/ovhcloud-docs. On 15 October it deleted branches named dev/test-changest and test/trigger-probe, four days before the swap.
  3. Dry run, March 2026. On 5-6 March, four of the account's personal repos were force-pushed with hidden preinstall.js loaders using the amend-and-force-push technique StepSecurity documented in ForceMemo, with the same Solana dead-drop wallet.
  4. The swap. The attacker built the CLI locally from the 13 October tip of main (commit 5ead8ef) with an embedded credential stealer, uploaded over five existing releases, and replaced checksums.txt to match.

Build fingerprint. The swapped binary was not built by the release pipeline. go version -m showed pseudo-version v0.15.1-0.20261013083505-5ead8ef9b43d+dirty, built from commit 5ead8ef (a main-branch commit, never a release tag) with -ldflags="-s -w" and without GoReleaser's -X .../internal/version.Version ldflag. StepSecurity's rebuild of the same tree with a plain go build -ldflags="-s -w" reproduces the pseudo-version; it notes that such a build shows vcs.modified=false where the reporter captured +dirty, so the pseudo-version and the missing -X version ldflag are the reliable markers, not the dirty bit.

User-visible tell. Running ovhcloud version on a swapped build prints undefined in the version column alongside last_commit 5ead8ef9b43d354dfb5caadff803b11afc8c5c58 and last_commit_time 2026-10-13T08:35:05Z. ovhcloud upgrade refuses with "upgrade is not available in development builds."

Payload behaviour. The stealer collects AI-agent credential stores (VS Code, Cursor, Continue, Cody, OpenCode, Claude, Hermes), cloud and container credentials (AWS, GCP, Azure, Docker, Kubernetes, Terraform, npm, doctl), SSH keys, .netrc, and .gitconfig. It stages a tar.gz in a never-deleted /tmp/envchecker-XXXX/ directory and POSTs it to https://45.32.148[.]174/upload as Solana-RPC-shaped JSON with the archive base64-encoded in params. Every subsequent run re-steals; two invocations produced two exfil archives nine seconds apart. In StepSecurity's chamber replay, install to completed exfiltration took four seconds, and the collection arrived in a single 1,949-byte archive covering 18 secret paths.

Delivery and the checksum gap. install.sh fetches releases/latest, parses tag_name, downloads ovhcloud-cli_Linux_x86_64.tar.gz, and performs no checksum verification at any point; it never reads checksums.txt. Because the swapped checksums matched the swapped binaries, any verifier trusting the repository's own checksums passed. Only an externally pinned hash caught the swap. Homebrew's cask pins per-arch SHA-256, so a brew install during the window would have hash-mismatched and refused; Homebrew was not an exposure path. go install, source builds, and the Docker image were also unaffected.

Exposure corrections. StepSecurity's replay found that install.sh never exposed arm64 Linux: the script builds the asset name from uname -m (aarch64), but GoReleaser publishes Linux_arm64, so script installs on arm64 Linux always 404'd. Those users were exposed only via ovhcloud upgrade (which maps runtime.GOARCH correctly and verifies nothing) or manual download. Published exposure tables, including The Hacker News's, list the arm64 install.sh path; OpenSourceMalware and Help Net Security also list brew install --cask ovh/tap/ovhcloud-cli as an infection path. Both are contradicted by the replay evidence.

GlassWorm loader. On 21 October the four loader repos were force-pushed again, restoring pre-compromise history. StepSecurity recovered preinstall.js from orphaned commit 227207ce (SHA-256 43df42fd79f6f81e530892b54634598770d6e496796a863c596e616bcb3fec70) and detonated it against a controlled dead drop. The file is 181 bytes of visible JavaScript; the rest is invisible variation-selector characters (U+FE00-FE0F encode 0-15, U+E0100-E01EF encode 16-79) that decode to an AES-256-CBC wrapper with hardcoded key and IV. Stage 2 waits 10 seconds, exits on Russian locale/timezone/UTC+2..+12, honours a 48-hour ~/init.json re-run throttle, then polls getSignaturesForAddress on nine public Solana RPC providers for the dead-drop wallet. The newest transaction memo carries {"link":}, on-chain C2 configuration. Stage 3 is fetched with an os: linux fingerprint header, served base64-encoded with AES key material in response headers named ivbase64 and secretkey, then executed inside a Node vm context with require passed in; the sandbox does not constrain module loading. With the drop live, memo to stage-3 fetch to vm execution to RCE marker completed in 35 seconds. With the drop cold, the loader polls every 10 seconds indefinitely; with all nine RPC endpoints unreachable, it crashes with an unhandled atob InvalidCharacterError, which leaves a visible crash in Node logs rather than a silent poller. The dead-drop wallet returns an empty signature list on the public RPCs queried, consistent with the CrowdStrike-coordinated takedown that disrupted all four GlassWorm C2 channels on 26 May 2026; the operator can re-arm it by publishing a new memo transaction.

Confidence caveats. The GlassWorm attribution rests on shared tooling and infrastructure (the Solana dead-drop wallet, the variation-selector obfuscation, the geofence) documented by OpenSourceMalware and StepSecurity, not on OVHcloud, which has not named GlassWorm. OpenSourceMalware states that whether the same actor did both the March compromise and the October swap is its inference. The payload's target list and exfil endpoint come from OVHcloud's advisory as relayed by StepSecurity; OVHcloud deleted the malicious files before OpenSourceMalware could obtain a sample, so the real binary was never independently analysed. StepSecurity's chamber replay used a rebuild from the untouched source under the attacker's build flags plus a collection routine matching the advisory's target list, not the original binary. The loader detonation used the recovered preinstall.js, whose SHA-256 matches public IOC feeds. Treat the payload behaviour as single-sourced to OVHcloud's advisory and the loader chain as single-sourced to StepSecurity; verify before enforcement.

4. Mitigation & containment

P1 within 24 hours

  • Search the estate for ovhcloud binaries installed or updated between 12:47 and 14:21 UTC on 19 October 2026. On any host found, run ovhcloud version: undefined plus commit 5ead8ef9b43d354dfb5caadff803b11afc8c5c58 confirms a swapped build. Also run go version -m $(which ovhcloud) | head -5 and check for /tmp/envchecker-* directories.
  • Treat every host that ran a swapped binary as fully compromised and assume exfiltration succeeded. From a different machine, rotate everything the host could read: OVH API keys, AWS/GCP/Azure credentials, kubeconfigs, container registry credentials, npm tokens, SSH keys, .netrc, git credentials, and the AI-agent stores (Claude, Cursor, VS Code, Continue, Cody, OpenCode, Hermes). Updating or reinstalling the CLI revokes nothing.
  • Block egress to 45.32.148[.]174 and 162.33.177[.]177 at the perimeter and on developer workstations.
  • Audit CI jobs that fetched releases/download/v0.1[1-5].0/* between 12:47 and 14:21 UTC on 19 October and treat runner-hosted credentials reachable from those runners as exposed.

P2 within 72 hours

  • Reinstall from the clean, immutable v0.16.0 and verify the attestation: gh release verify-asset v0.16.0. Delete the old binary and any /tmp/envchecker-* directories.
  • Revoke and reissue PATs and tokens used on affected machines since 19 October. If a machine cannot be established clean, rebuild it.
  • Pin SHA-256 hashes for every third-party CLI and release artifact your pipelines fetch, and fail the job on mismatch. The reporter caught this swap with one pinned hash; install.sh and the repository's own checksums.txt would both have passed.
  • Hunt for the GlassWorm loader on developer endpoints: Node processes polling Solana RPC endpoints, ~/init.json throttle files, and Node crashes with an atob InvalidCharacterError in logs.

P3 within 7 days

  • Require release immutability and release attestations from software suppliers whose tooling you consume, and record the requirement in third-party contracts.
  • Remove or restrict the malicious VS Code extension color-picker-universal 2.8.91 and audit for persistent NodeJS services registered via plist that communicate with 162.33.177[.]177.
  • Where Solana RPC egress is not needed, block it. StepSecurity's replay shows the loader crashes visibly rather than polling silently when all nine RPC endpoints are unreachable.

5. Indicators of compromise

Type Value Confidence Source
sha256 3833daffcaaaab27e4e0e0833240ffcb1df8df648f4ec0bc4a75c176e28933cb high StepSecurity / issue #300 (Linux arm64 malicious archive)
sha256 5c2beb56e6d83a4b426565a1557791ef759316abcc4effb38dd627c90aeb80e0 high StepSecurity (original Linux arm64 archive, from Homebrew tap history)
sha256 26b78ca0045dd21db040b2991d8a301bafcb898454ea80b5285a04e184132142 high StepSecurity (original Darwin arm64 archive)
sha256 5aadbdc9f9eae915fdb13e189fa8e5b492b302418c8b9ef3b57d720128fbbee8 high StepSecurity (original Darwin x86_64 archive)
sha256 1109d157dcf677bf3a7616e4b27b4fe3b5cc984e1cc82987592016541a6c88ed high StepSecurity (original Linux x86_64 archive)
md5 6f2f086239f3d88eb2750e871901927d high OVHcloud advisory via StepSecurity (Darwin arm64 malicious binary)
md5 347e65eb7f2d78d1c6872690272a6cc3 high OVHcloud advisory via StepSecurity (Darwin x86_64 malicious binary)
md5 01f57206d37c28cf711677f612052913 high OVHcloud advisory via StepSecurity (Linux arm64 malicious binary)
md5 301bff0b51980b9e76c93633e7cfd3bd high OVHcloud advisory via StepSecurity (Linux x86_64 malicious binary)
md5 6a3cf153338c4161697cda2fe704c8848 high OVHcloud advisory via StepSecurity (Windows x86_64 malicious binary)
sha256 43df42fd79f6f81e530892b54634598770d6e496796a863c596e616bcb3fec70 high StepSecurity (preinstall.js stage 1, orphaned commit 227207ce)
sha256 0f79989912572a24ba13baabb32b3fd084496271c51448d47b66fe1aedbf293c high StepSecurity (decrypted stage-2 script)
url hxxps://45.32.148[.]174/upload high OVHcloud advisory via StepSecurity
ipv4 162.33.177[.]177 high OVHcloud postmortem via StepSecurity
build commit 5ead8ef9b43d354dfb5caadff803b11afc8c5c58 high StepSecurity / issue #300
wallet BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC high StepSecurity / OpenSourceMalware
path /tmp/envchecker-* high OVHcloud advisory via StepSecurity
path ~/init.json high StepSecurity
github account HassenBF high StepSecurity / OVHcloud
aes key zetqHyfDfod88zloncfnOaS9gGs90ONX high StepSecurity (stage-2 wrapper)
aes iv a041fdaa0521fb5c3e26b217aaf24115 high StepSecurity (stage-2 wrapper)
sha256  3833daffcaaaab27e4e0e0833240ffcb1df8df648f4ec0bc4a75c176e28933cb
sha256  5c2beb56e6d83a4b426565a1557791ef759316abcc4effb38dd627c90aeb80e0
sha256  26b78ca0045dd21db040b2991d8a301bafcb898454ea80b5285a04e184132142
sha256  5aadbdc9f9eae915fdb13e189fa8e5b492b302418c8b9ef3b57d720128fbbee8
sha256  1109d157dcf677bf3a7616e4b27b4fe3b5cc984e1cc82987592016541a6c88ed
md5  6f2f086239f3d88eb2750e871901927d
md5  347e65eb7f2d78d1c6872690272a6cc3
md5  01f57206d37c28cf711677f612052913
md5  301bff0b51980b9e76c93633e7cfd3bd
md5  6a3cf153338c4161697cda2fe704c8848
sha256  43df42fd79f6f81e530892b54634598770d6e496796a863c596e616bcb3fec70
sha256  0f79989912572a24ba13baabb32b3fd084496271c51448d47b66fe1aedbf293c
url  hxxps://45.32.148[.]174/upload
ipv4  162.33.177[.]177
path  /tmp/envchecker-*
path  ~/init.json
wallet  BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC
github  HassenBF

Behavioural indicators

Behaviour Where to observe Confidence
ovhcloud version prints undefined with last_commit 5ead8ef9b43d354dfb5caadff803b11afc8c5c58 CLI stdout on developer hosts high
ovhcloud upgrade returns "upgrade is not available in development builds" CLI stdout high
Stealer writes a tar.gz into /tmp/envchecker-XXXX/ and never deletes it Filesystem on macOS and Linux hosts high
POST of Solana-RPC-shaped JSON with base64 archive in params Proxy and egress logs to 45.32.148[.]174 high
Node process polling getSignaturesForAddress on public Solana RPC providers every 10 seconds EDR process and network telemetry high
Node crash with unhandled atob InvalidCharacterError when RPC egress is blocked Node and application logs high
~/init.json created to throttle re-runs for 48 hours macOS filesystem high
Persistent NodeJS service registered via plist beaconing to 162.33.177[.]177 macOS launchd and network telemetry high

6. Detection

rule GlassWorm_OVHcloud_CLI_Stealer_Markers
{
    meta:
        author = "Adverse Trace"
        date = "2026-10-24"
        reference = "https://www.stepsecurity.io/blog/ovhcloud-cli-github-release-swap-glassworm"
        description = "Detects build and staging artefacts of the swapped ovhcloud-cli credential stealer"
    strings:
        $commit = "5ead8ef9b43d354dfb5caadff803b11afc8c5c58" ascii
        $pseudo = "v0.15.1-0.20261013083505-5ead8ef9b43d" ascii
        $staging = "/tmp/envchecker-" ascii
        $exfil = "45.32.148.174/upload" ascii
        $devbuild = "upgrade is not available in development builds" ascii
    condition:
        uint32(0) == 0x464c457f and 2 of them
}
rule GlassWorm_Loader_VariationSelector
{
    meta:
        author = "Adverse Trace"
        date = "2026-10-24"
        reference = "https://www.stepsecurity.io/blog/ovhcloud-cli-github-release-swap-glassworm"
        description = "Detects the GlassWorm preinstall.js loader: short visible JS with variation-selector payload"
    strings:
        $vs1 = { E1 80 [0-1] }   // U+E0100..U+E01EF range lead bytes
        $vs2 = { EF B8 [0-1] }   // U+FE00..U+FE0F range lead bytes
        $rpc = "getSignaturesForAddress" ascii
        $hdr1 = "ivbase64" ascii
        $hdr2 = "secretkey" ascii
        $fp = "os: linux" ascii
    condition:
        filesize < 200KB and $rpc and $hdr1 and $hdr2 and 1 of ($vs*)
}
title: OVHcloud CLI Swapped Build Execution
id: 8f3c2a1e-ovh-glassworm-2026
status: experimental
author: Adverse Trace
date: 2026/10/24
references:

  - https://www.stepsecurity.io/blog/ovhcloud-cli-github-release-swap-glassworm
description: Detects execution of a swapped ovhcloud-cli build or its stealer staging behaviour
logsource:
  category: process_creation
detection:
  selection_version:
    Image|endswith: '/ovhcloud'
    CommandLine|contains: 'version'
  selection_upgrade:
    Image|endswith: '/ovhcloud'
    CommandLine|contains: 'upgrade'
  selection_staging:
    CommandLine|contains: '/tmp/envchecker-'
  condition: selection_version or selection_upgrade or selection_staging
falsepositives:

  - Legitimate local development builds of ovhcloud-cli print undefined in the version column; confirm last_commit is 5ead8ef9b43d354dfb5caadff803b11afc8c5c58 before escalating
level: high
title: GlassWorm Solana Dead-Drop Polling
id: 2b7d9c4f-glassworm-solana-2026
status: experimental
author: Adverse Trace
date: 2026/10/24
references:

  - https://www.stepsecurity.io/blog/ovhcloud-cli-github-release-swap-glassworm
description: Detects Node processes polling Solana RPC getSignaturesForAddress for the GlassWorm dead drop
logsource:
  product: windows
  category: network_connection
detection:
  selection_proc:
    Image|endswith: '\node.exe'
  selection_rpc:
    DestinationPort: 443
    QueryName|contains: 'getSignaturesForAddress'
  condition: selection_proc and selection_rpc
falsepositives:

  - Legitimate Solana tooling on developer hosts
level: medium

7. Sources

  • StepSecurity, "OVHcloud CLI Compromised via GitHub Release Swap: Detonating the Real GlassWorm Loader and What We Found", https://www.stepsecurity.io/blog/ovhcloud-cli-github-release-swap-glassworm, 2026-10-24.
  • OVHcloud security advisory GHSA-862f-55f5-vmvw, referenced in the StepSecurity report, 2026-10-20.
  • ovh/ovhcloud-cli issue #300, "Release assets of v0.13.0-v0.15.0 replaced", referenced in the StepSecurity report, 2026-10-19.
  • SecurityWeek, "Hijacked Developer Account Used to Plant Stealer in OVHcloud CLI", https://www.securityweek.com/hijacked-developer-account-used-to-plant-stealer-in-ovhcloud-cli/, October 2026.
  • Help Net Security, "Info-stealing malware slipped into OVHcloud CLI releases on GitHub", https://www.helpnetsecurity.com/2026/10/21/ovhcloud-cli-github-malware/, 2026-10-21.
  • The Hacker News, "Hackers Hijack Five OVHcloud CLI Releases to Distribute Secret-Stealing Malware", https://thehackernews.com/2026/10/hackers-hijack-five-ovhcloud-cli.html, October 2026.
  • OpenSourceMalware, "Glassworm Hacks OVH Clouds CLI tool", referenced in the StepSecurity report.
  • CrowdStrike, "Inside the Takedown of a Developer-Targeting Botnet", referenced in the StepSecurity report, May 2026.

8. Adverse Trace position

No CVE has been assigned and there is no CISA KEV entry, so this is a supply chain integrity failure rather than a patchable vulnerability, and the severity sits with exposure: roughly 250 downloads in 94 minutes, a stealer that completed collection in four seconds in the replay, and a target list that includes cloud, Kubernetes, and AI-agent credentials. The GlassWorm attribution is unconfirmed; the actor has no MITRE ATT&CK profile, OVHcloud has not named it, and the March-to-October link is OpenSourceMalware's inference, so we treat the loader chain as single-sourced to StepSecurity and the payload behaviour as single-sourced to OVHcloud's advisory. For EMEA financial services clients, the practical exposure is engineering and platform workstations and CI runners that pulled ovhcloud-cli from GitHub releases or install.sh during the window; Homebrew, go install, source builds, and the Docker image were not exposed, and arm64 Linux install.sh users were not exposed despite published tables saying otherwise. We will monitor for re-arming of the Solana dead-drop wallet BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC and for further GlassWorm loader activity, and we will update this advisory if OVHcloud or an independent analyst confirms the attribution or publishes additional hashes for the non-Linux swapped archives, which were most likely never captured before deletion.


Read the original source →

Published via PulseTrace — Adverse Trace threat intelligence.

Post this to LinkedIn
Formatting is converted automatically — headings, bullets, a link back & hashtags. Paste straight in.
J
Jeff Davies