Spectra Assure Free Trial
Get your 14-day free trial of Spectra Assure for Software Supply Chain Security
Get Free TrialMore about Spectra Assure Free Trial
RL researchers have uncovered a large-scale, coordinated supply chain attack targeting the @redhat-cloud-services npm scope. On June 1, an attacker published malicious versions of 32 packages (update) in a 72-second window, injecting obfuscated preinstall malware into every one.
The affected packages collectively represent approximately 9.8 million total downloads and cover the full breadth of Red Hat's Hybrid Cloud Console JavaScript ecosystem — UI components, API clients, build tooling, configuration utilities, and MCP servers.
Package | Malicious Version | Published (UTC) | Total Downloads |
1.2.2 | 10:54:09 | 335,889 | |
3.6.1 | 10:54:09 | 1,281,228 | |
3.2.1 | 10:54:20 | 487,137 | |
4.0.11 | 10:54:20 | 80,714 | |
1.2.1 | 10:54:21 | 13,405 | |
2.0.8 | 10:54:26 | 507,613 | |
5.0.4 | 10:54:25 | 12,448 | |
0.3.1 | 10:54:26 | 736 | |
4.9.2 | 10:54:26 | 112,034 | |
0.3.1 | 10:54:27 | 1,324 | |
0.6.1 | 10:54:27 | 3,415 | |
4.0.11 | 10:54:31 | 5,729 | |
2.3.1 | 10:54:42 | 56,869 | |
6.11.3 | 10:54:36 | 788,862 | |
4.11.2 | 10:54:36 | 874,609 | |
6.0.4 | 10:54:36 | 7,762 | |
4.4.1 | 10:54:47 | 112,768 | |
6.9.2 | 10:54:53 | 701,097 | |
@redhat-cloud-services/frontend-components-advisor-components | 3.8.2 | 10:54:58 | 28,616 |
6.1.4 | 10:54:58 | 24,738 | |
7.7.2 | 10:55:00 | 1,353,473 | |
7.4.1 | 10:54:59 | 1,491,890 | |
4.0.4 | 10:54:59 | 38,925 | |
4.7.2 | 10:54:59 | 157,274 | |
3.0.10 | 10:55:04 | 44,801 | |
5.0.3 | 10:55:09 | 226,677 | |
4.0.4 | 10:55:09 | 15,442 | |
2.1.8 | 10:55:10 | 48,286 | |
4.0.3 | 10:55:21 | 11,302 | |
4.0.4 | 10:55:21 | 31,116 | |
9.0.3 | 10:55:21 | 942,556 |
Total: 32 packages, ~9.8 million downloads
The malicious versions span from 10:54:09 to 10:55:21 UTC — 72 seconds from first to last publish. This is not a human typing at a terminal; it is automated scripted publishing. The attacker prepared all 32 poisoned archives in advance and executed a single batch push.
The packages span two different GitHub source repositories — RedHatInsights/frontend-components and RedHatInsights/javascript-clients — confirming that the attacker had access to the @redhat-cloud-services npm scope credentials directly, not to any individual repository. This is a scope-level account compromise.
The modification is identical across all 31 packages: exactly two files changed compared to the prior clean version.
package.json — a preinstall script entry is added, pointing execution at index.js:
json
"scripts": {
"preinstall": "node index.js"
}index.js — the package's main entry point is completely replaced with a malicious payload. In the clean referential versions, index.js contains the package's normal JavaScript exports. In the malicious versions, it is a single-line obfuscated dropper.
No other files are touched. The surgical precision — two files, same modification pattern, 31 packages — is another indicator of automated tooling.
Each malicious index.js follows the same outer structure — but what it conceals is a three-layer attack pipeline.
Outer wrapper:
javascript
try{eval(function(s,n){
return s.replace(/[a-zA-Z]/g, function(c) {
var b = c <= "Z" ? 65 : 97;
return String.fromCharCode((c.charCodeAt(0) - b + n) % 26 + b)
})
}([/* ~500,000 integer char codes */].map(function(c){return String.fromCharCode(c)}).join(""), /* N */))
}catch(e){console.log("wrapper:",e.message||e)}Layer 1 — ROT-N caesar cipher. The outer call encodes ~4 MB of character codes. String.fromCharCode reconstructs a string, then the function(s,n) applies a ROT-N rotation to all alphabetic characters. The rotation value N differs per package — making each blob visually distinct while using the same decode engine. For the chrome package, N = 3.
Layer 2 — AES-128-GCM decryption (two blobs). The ROT-decoded string is an async IIFE that decrypts two encrypted blobs using Node.js's crypto.createDecipheriv("aes-128-gcm", ...):
javascript
(async()=>{try{
const _c = await import("node:crypto");
const _d = (k, i, a, c) => {
const d = _c.createDecipheriv("aes-128-gcm", Buffer.from(k,"hex"), Buffer.from(i,"hex"), {authTagLength:16});
d.setAuthTag(Buffer.from(a,"hex"));
return Buffer.concat([d.update(Buffer.from(c,"hex")), d.final()])
};
const _b = _d(key_b, iv_b, tag_b, ciphertext_b); // bun downloader
const _p = _d(key_p, iv_p, tag_p, ciphertext_p); // main payloadGCM (Galois/Counter Mode) provides authenticated encryption: any tampering with the ciphertext causes decryption to fail, protecting the payload from modification. Keys, IVs, and auth tags are unique per package.
_b — bun runtime downloader. The first blob decrypts to JavaScript that downloads the bun runtime (v1.3.13) from github.com/oven-sh/bun/releases into a temp directory, makes it executable, and stores the path as globalThis.getBunPath. This is a legitimate binary from a legitimate source — a deliberate choice to avoid suspicious network traffic.
Layer 3 — Obfuscator.io payload (_p, 634 KB). The second blob — the actual payload — is written to a temp file and executed with bun. If bun is already present in the environment, it is used directly. Otherwise _b is eval'd first. After execution the temp file is deleted. The 634 KB payload is heavily obfuscated using the obfuscator.io toolchain (string array encoding, control flow flattening, identifier mangling).
The try/catch at every layer ensures silent execution: if any stage fails for any reason, no error surfaces in the build log.
Critically, every one of the 31 index.js files carries a unique SHA-256 hash. The attacker generated individually tailored payloads — rotation values, AES keys, IVs, auth tags, and ciphertexts all differ across packages. This defeats hash-based deduplication and means each package must be analyzed independently.
Full decryption of the chrome package payload reveals a cloud credential stealer with worm propagation capabilities. The _p blob is a 634 KB bundled JavaScript program that performs several distinct functions when run with the bun runtime.
Credential harvesting. The payload reads and exfiltrates credentials from the developer's build environment via multiple collectors. Confirmed targets include:
Source | Environment Variables / Files |
AWS | AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, profile files |
Azure / ARM | ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_OIDC_TOKEN_FILE_PATH, AZURE_VAULT_NAME |
Google Cloud | GOOGLE_APPLICATION_CREDENTIALS, application default credential files |
HashiCorp Vault | VAULT_TOKEN, VAULT_AUTH_TOKEN |
GitHub | GITHUB_TOKEN, GITHUB_REPOSITORY, GITHUB_WORKFLOW_REF |
npm | ~/.npmrc |
SSH | ~/.ssh/ |
CI/CD targeting. The payload checks GITHUB_REPOSITORY and GITHUB_WORKFLOW_REF to detect GitHub Actions environments. If the workflow reference matches an expected pattern, it triggers a separate, targeted exfiltration path before exiting. This allows quiet operation inside automated pipelines without leaving an unusual process running.
GitHub API exfiltration. Stolen credentials are formatted into structured reports and exfiltrated via the GitHub API. The payload contains a dedicated GitHub API client (githubFetch, githubJson) with Authorization header support. The command-and-control (C2) endpoint URL is stored encrypted under an additional layer (f4abccab2 decryptor) on top of the obfuscator.io string table, preventing static extraction.
Worm propagation. The payload contains a bQ() ROT-N function and pQ() payload generator identical in structure to the obfuscation used to encode the index.js files. This strongly suggests the payload is capable of generating new malicious package versions — using any npm credentials it steals to publish poisoned packages to additional scopes.
All 31 packages contain both _b and _p blobs — they are not specific to chrome. Decrypting _b across multiple packages confirms it carries identical bun downloader code in every case; only the AES-GCM key, IV, and authentication tag differ, making the binary blob unique while the decrypted function is the same. The _p payloads are all unique-keyed and have not been decrypted for the other 30 packages.
A structural split is visible from file sizes alone: four packages (chrome, frontend-components, frontend-components-advisor-components, sources-client) produce index.js files of ~4.05 MB, while the remaining 27 produce files of ~4.29 MB — approximately 238 KB more ciphertext in _p.
ReversingLabs Spectra Assure's behavior analysis detects the injection through a cluster of indicators that are uniformly absent in the referential versions and uniformly present in the malicious ones:
Indicator | Description |
BH13852 | Executes files during installation or upon launch |
BH15358 | Contains byte sequence decodable to printable string |
BH13525 | Evaluates code dynamically |
BH15183 | Converts binary data to string (obfuscation) |
Two additional indicators appear only in the chrome package payload:
Indicator | Description |
BH16168 | Connects through HTTP |
BH13831 | Sets a JavaScript interval (recurring execution) |
The presence of HTTP connectivity and a repeating timer in the chrome payload indicates a more capable stage — one that establishes periodic callbacks, consistent with a persistent info-stealer or C2 beacon executing from the developer's environment. The chrome package, used in the Red Hat Hybrid Cloud Console shell, runs in browser-facing build environments with access to Red Hat infrastructure credentials.
By the time of analysis, legitimate maintainers had already pushed clean follow-up versions for all 32 packages (N+1 patch versions). The malicious versions have since been removed from npm. Any project that pinned to these exact versions or ran npm install in the window between publish and removal is affected.
Recommendations
All 32 malicious index.js hashes are unique (individually crafted payloads):
Package | Version | index.js SHA-256 |
chrome | 2.3.1 | 21b6409a7b84446310daca5409ad6112ac60a1e4bef97736e53fff5f63bfdef4 |
compliance-client | 4.0.3 | 5c6cb758a3447bc7e0de34406919a933f9351e90ef04ec43f3bbb401e7004e1b |
config-manager-client | 5.0.4 | 5dabf08e2655c012e478074a2cea2b0d34e286c27265a26f3846fc45e5584501 |
entitlements-client | 4.0.11 | 2a446171b4b981d98b5af6c5606bd63b1570040334210b6ab0a10901b2606fe5 |
eslint-config-redhat-cloud-services | 3.2.1 | edd86c0efd776a6bd934fc7b0d4d6da2b256e147cfa83bb0c2814e81d849c427 |
frontend-components | 7.7.2 | 3f8e522595f32277a0013c7ab0df3ecf336460b56e6b4be9130907f419db3b6d |
frontend-components-advisor-components | 3.8.2 | d8d170af3de17bb9b217c52aaaffdf9395f35ef015a57ef676e406c121e5e223 |
frontend-components-config | 6.11.3 | 545a1838c66e1771f58d84a17b3e1841e5eeab91a73f4ccc59c9492450a6d9c0 |
frontend-components-config-utilities | 4.11.2 | c2a60face766f69f82c972375f35f8ebaa45d6c464176974e631d9a78d6bea0a |
frontend-components-notifications | 6.9.2 | 080190bffcaafffacca1f0181fc9024aaaa21500ffdc9926fa5b689ba959965d |
frontend-components-remediations | 4.9.2 | 9b99482b75ee89f0d916f2743deeff381ea727e69c71491822477e67891841ad |
frontend-components-testing | 1.2.1 | 17c4312b50d69a6f61515edcf71cfaa8271fe2538b942128cfb639d021d042a7 |
frontend-components-translations | 4.4.1 | e5f73c888f1250a8895680801975cf177e8c690defd4a999e56f6c08ff64deb8 |
frontend-components-utilities | 7.4.1 | 89f97557200bd26cc8941c9abaadac2d798a89562401016fbb2c757e3092dfdc |
hcc-feo-mcp | 0.3.1 | c611e49ea46c91013448942c26049741b434cb5dac55fff7c376ca6a4f28580e |
hcc-kessel-mcp | 0.3.1 | 7cbace2a186cab2c652305b6e33c8eeb10d4a0ec3a0c8b795de012094fa0d845 |
hcc-pf-mcp | 0.6.1 | c178cafa2b3bcbefbbc283b5ab8fc6143e46650631f72451a44327f146a609c3 |
host-inventory-client | 5.0.3 | cffc487ee978f7bc06e3856b286940940658884847d38b619a137b8272a75980 |
insights-client | 4.0.4 | 8d2a09b3727b50f3d035b58bd35b90b504d24dda73a8a24e926a010a58ba5f74 |
integrations-client | 6.0.4 | 42e165602967c8e1a6fae0113a5179adbe33e18192244fe34b872db09c85e0e6 |
javascript-clients-shared | 2.0.8 | 09b2301d1589416e0d5fb7a602427a9850dee6713ffa741c0efcfeb1eb4c8952 |
notifications-client | 6.1.4 | 85b1ed56530bb64d925af4ca50faacd89efb1b63d615238a34adbea9f00e4754 |
patch-client | 4.0.4 | df1732f5bfec12e066be44dee02ec8a243e4868d38672c1b1d065359dd735a14 |
quickstarts-client | 4.0.11 | 7b19ffc2f2bfff75989255e5e807d0f62513153de287eba9cc17003c1dcae8a8 |
rbac-client | 9.0.3 | 94e8488fd033728eee6666550d5a94b0cc1f7b231d4d85d0affecb0615116722 |
remediations-client | 4.0.4 | 396cac9e457ec54ff6d3f6311cb5cc1da8054d019ce3ffa1de5741506c7a4ea4 |
rule-components | 4.7.2 | 1a30a9abe20bab121aaa75ed040565af14e6cdfb745609ee0e7b94a2d814fb9c |
sources-client | 3.0.10 | f961d6897c0ec586cde633e100865b5b1d435cc7c301dbf0f41298ca5b42e17a |
tsc-transform-imports | 1.2.2 | b390d9f708760b799ee5482e8050ce093219140627fcaec6df8812ac9abb9a9b |
types | 3.6.1 | b86c5ae9e95bd841a595440faa3eb6317441e746f241ae8fd641ab59ed1d1966 |
vulnerabilities-client | 2.1.8 | d1999fd543085918dd542322c6455abde3c57a93b8f7ce871b8809c8bb744af7 |
On June 1, 2026, an attacker published malicious versions of 32 packages in the @redhat-cloud-services npm scope within a 72-second window. Each compromised package contained an obfuscated preinstall script that silently executed a credential-stealing payload during any npm install run. The packages span Red Hat's Hybrid Cloud Console JavaScript ecosystem — UI components, API clients, build tooling, configuration utilities, and MCP servers — and collectively represent approximately 9.8 million total downloads.
Thirty-two packages in the @redhat-cloud-services npm scope were backdoored. High-download packages in the affected set include @redhat-cloud-services/frontend-components-utilities (approximately 1.5 million total downloads), @redhat-cloud-services/frontend-components (approximately 1.35 million downloads), @redhat-cloud-services/types (approximately 1.28 million downloads), @redhat-cloud-services/rbac-client (approximately 942,000 downloads), and @redhat-cloud-services/frontend-components-config-utilities (approximately 875,000 downloads). The full list of affected packages, malicious version numbers, and SHA-256 hashes for each compromised index.js file is published in the indicators of compromise (IoCs) table in this post.
The attack was fully automated. All 32 poisoned packages were prepared in advance and deployed in a single scripted batch push between 10:54:09 and 10:55:21 UTC. Because the compromised packages span two separate GitHub source repositories — RedHatInsights/frontend-components and RedHatInsights/javascript-clients — repository-level access alone could not explain the breadth of the attack. The attacker had direct access to the @redhat-cloud-services npm scope credentials, allowing them to publish to any package in the scope without touching any individual code repository.
An npm scope is a namespace that groups related packages under a shared owner prefix — in this case, @redhat-cloud-services. A scope-level compromise means the attacker obtained the credentials controlling the entire namespace, not just a single package or repository. This gives the attacker the ability to publish malicious versions of every package in the scope simultaneously, which is precisely what occurred here. Scope-level credential theft is far more damaging than a single-package account takeover because it provides leverage across an entire ecosystem in a single operation.
Each compromised package contained identical modifications to two files. The package.json was altered to add a preinstall script that executes index.js automatically during installation. The index.js itself was completely replaced with a single-line obfuscated dropper carrying a three-layer attack pipeline: an outer ROT-N Caesar cipher decodes roughly 4 MB of character codes; the decoded layer performs AES-128-GCM decryption on two encrypted blobs; the first blob downloads the bun JavaScript runtime, and the second blob is the 634 KB main payload — a cloud credential stealer with worm propagation capabilities, obfuscated with the obfuscator.io toolchain.
The payload targets cloud and developer credentials from the build environment. Confirmed targets include AWS credentials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, and profile files), Azure/ARM credentials (ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET), Google Cloud application default credentials, HashiCorp Vault tokens, GitHub tokens and workflow references, npm configuration files (~/.npmrc), and SSH keys (~/.ssh/). Stolen credentials are formatted into structured reports and exfiltrated via the GitHub API using a dedicated client embedded in the payload.
Yes. The payload checks the GITHUB_REPOSITORY and GITHUB_WORKFLOW_REF environment variables to detect GitHub Actions environments. When it identifies an automated pipeline context, it triggers a targeted exfiltration path before exiting — designed to operate quietly within build logs without leaving an unusual process running. The @redhat-cloud-services/chrome package, used in the Red Hat Hybrid Cloud Console shell, runs in browser-facing build environments with access to Red Hat infrastructure credentials, making it a particularly sensitive target.
The payload contains a worm propagation mechanism. It includes functions — a ROT-N encoder and a payload generator — structurally identical to the obfuscation used to encode the compromised index.js files. If the malware successfully steals npm credentials from the infected build environment, it can generate new malicious package versions and publish them to additional npm scopes controlled by the victim organization. This self-replication capability means a single infected build environment could become the origin point of downstream supply chain attacks.
The payload downloads the bun JavaScript runtime (version 1.3.13) from the official github.com/oven-sh/bun/releases repository into a temporary directory before executing the main credential-stealing payload. Using a legitimate binary from a legitimate source is a deliberate evasion choice: the network request to GitHub does not trigger security alerts the way a connection to an unknown command-and-control server would. If bun is already present in the environment, the download step is skipped entirely.
The attack uses four layered obfuscation techniques to resist analysis. First, each package's payload begins with a ROT-N Caesar cipher applied with a unique rotation value per package, making each blob visually distinct. Second, the decoded layer uses AES-128-GCM authenticated encryption — which prevents tampering and requires correct decryption keys to recover the payload. Third, the final payload is obfuscated using the obfuscator.io toolchain, applying string array encoding, control flow flattening, and identifier mangling. Fourth, every index.js file across all 32 packages carries a unique SHA-256 hash because each package received individually tailored AES keys, IVs, auth tags, and ciphertexts — defeating hash-based deduplication and requiring independent decryption of each package to fully characterize the attack.
You are potentially affected if you ran npm install on June 1, 2026 between approximately 10:54 and 10:56 UTC, and any of the 32 compromised packages appear in your dependency tree — either as a direct dependency or a transitive one. Check your package-lock.json or yarn.lock for exact version pins matching the malicious versions listed in the IoC table in this post. Because several of these packages are widely used as indirect dependencies, total exposure is likely larger than direct install counts suggest.
If you ran npm install during the attack window and any of the affected packages are in your dependency tree, treat the build environment as fully compromised. Rotate all credentials, tokens, SSH keys, and secrets accessible from that environment immediately — including AWS, Azure, Google Cloud, HashiCorp Vault, GitHub, and npm credentials. Block the malicious package hashes in Artifactory, Nexus, or any other artifact proxy using the SHA-256 values in the IoC table. Monitor build agents for anomalous outbound HTTP connections initiated by Node.js processes during npm install. Investigate any CI/CD jobs that ran in the affected window for signs of unauthorized activity.
Yes. Legitimate maintainers published clean follow-up versions for all 32 packages before the time of RL's analysis, and the malicious versions have been removed from npm. However, any project that pinned to the exact compromised versions in a lock file, or ran npm install during the window between initial publication and removal, may have executed the malware. Removal from the registry does not eliminate exposure that already occurred.
ReversingLabs Spectra Assure® detected the injected malware through behavioral indicators uniformly present in the malicious versions and uniformly absent in their clean counterparts: execution of files during installation or launch (BH13852), dynamic code evaluation (BH13525), conversion of binary data to strings as an obfuscation technique (BH15183), and the presence of byte sequences decodable to printable strings (BH15358). Two additional indicators detected in the chrome package payload — HTTP connectivity (BH16168) and a JavaScript interval for recurring execution (BH13831) — confirmed the more capable, persistent stage embedded in that package. Full IoC coverage for all 32 packages is available in Spectra Assure. Teams monitoring open-source dependencies with Spectra Assure will have detected this attack via BH13852 and BH13525 across all affected versions.
This attack illustrates the leverage a single scope-level credential compromise can provide: 32 packages, 9.8 million downloads, and a payload capable of self-propagation — all published in under two minutes. The individually crafted payloads across every package, the per-package AES-GCM key material, and the ROT-N variance are deliberate choices to defeat both hash-based detection and bulk analysis. That each package requires independent decryption to fully characterize is itself a structural defense the attacker built in.
ReversingLabs is continuing to monitor the @redhat-cloud-services scope and will update this post if additional affected versions or payload variants are identified. Full IoC coverage for all 32 packages is available in Spectra Assure. Teams using Spectra Assure to monitor open-source dependencies will have detected this attack via the BH13852 and BH13525 behavior indicators present across all affected versions.
Updated to reflect the correct number of packages affected, and an FAQ was added.
Join RL's free Spectra Assure Community, which helps secure open-source development.
This post was researched by RL AI agents and written by generative AI.