Red Hat Cloud Services npm Trusted-Publishing Compromise
Multiple @redhat-cloud-services npm packages were compromised on 2026-06-01 through trusted-publishing abuse tied to the Mini Shai-Hulud Miasma wave. The malicious releases added install-time payload execution, credential collection, destructive fallback behavior, and GitHub workflow tampering risk.
On this page 0% read
Executive Summary
On 2026-06-01, researchers reported a new Mini Shai-Hulud child event affecting the @redhat-cloud-services npm scope. StepSecurity identified 31 malicious package versions across 29 packages, published through trusted publishing after an attacker compromised the RedHatInsights/javascript-clients GitHub workflow path and abused OIDC-based npm publishing [Source 1].
This is not a normal vulnerable dependency advisory. It is a supply-chain publish compromise against Red Hat Cloud Services client packages that can execute during npm install. The reported payload is called Miasma, also tracked as The Spreading Blight, and it overlaps the same Mini Shai-Hulud operational pattern of CI/CD compromise, credential theft, GitHub workflow tampering, and destructive pressure after token invalidation [Source 1] [Source 2].
Organizations should search for affected @redhat-cloud-services/* packages in source repositories, lockfiles, package caches, build logs, CI artifacts, and container layers. Any confirmed install on a developer workstation or runner with GitHub, npm, cloud, or deployment credentials should trigger credential rotation from a clean environment.
Source-Watcher Candidate Queue
candidate_id: "redhat-cloud-services-npm-miasma-compromise"
first_seen: "2026-06-01"
decision: "publish_ready"
relationship: "child_event_of_mini_shai_hulud_worm"
dedupe_keys:
- "npm:@redhat-cloud-services"
- "repo:RedHatInsights/javascript-clients"
- "package:@redhat-cloud-services/patch-client@4.0.4"
- "payload:miasma"
- "campaign:mini-shai-hulud"
starting_sources:
- "StepSecurity primary research"
- "OX Security primary research"
- "Mend primary research"
- "BleepingComputer Red Hat statement"
Key Facts
threat_type: "malicious npm package publish compromise"
ecosystem: "npm"
registry: "npm"
campaign_context: "Mini Shai-Hulud / Miasma / The Spreading Blight"
affected_scope: "@redhat-cloud-services"
source_repository: "RedHatInsights/javascript-clients"
reported_publish_date: "2026-06-01"
reported_package_count: 29
reported_malicious_version_count: 31
execution_trigger: "npm install lifecycle script"
publish_path: "GitHub Actions trusted publishing / npm OIDC"
credential_risk:
- "GitHub tokens"
- "npm publishing tokens"
- "cloud credentials"
- "CI/CD deployment credentials"
- "developer workstation secrets"
Source Confidence and Claim Ledger
| Claim | Status | Evidence |
|---|---|---|
Multiple @redhat-cloud-services npm packages were maliciously published on 2026-06-01. | confirmed | StepSecurity reports 31 malicious versions across 29 packages and lists exact package/version pairs [Source 1]. |
The compromise path involved the RedHatInsights/javascript-clients GitHub repository and OIDC trusted publishing. | confirmed | StepSecurity describes the attacker-created pull request, merged branch, workflow execution, and trusted-publishing abuse path [Source 1]. |
| The payload is tracked as Miasma or The Spreading Blight and overlaps Mini Shai-Hulud behavior. | likely | StepSecurity and OX both connect the behavior to the Mini Shai-Hulud family and describe destructive or worm-like behavior [Source 1] [Source 2]. |
| The malicious code targets credentials and can tamper with GitHub repository state. | confirmed | StepSecurity and Mend describe credential theft, GitHub workflow manipulation, and repository-level changes [Source 1] [Source 3]. |
| Red Hat says it removed the compromised package versions and rotated exposed tokens. | confirmed | BleepingComputer published a Red Hat statement saying affected versions were removed and exposed tokens were rotated [Source 4]. |
| Public sources currently prove downstream victim count or successful cloud abuse. | not_observed | None of the reviewed public sources provides a verified victim count or confirmed downstream cloud-control-plane abuse list. |
Impact Determination
| Classification | Criteria | Required evidence | Handling decision | Closure condition |
|---|---|---|---|---|
| Confirmed compromise | An affected package/version was installed or executed and endpoint, process, network, or GitHub audit telemetry shows Miasma selectors or credential collection behavior. | Lockfile/cache hit plus package install logs, process telemetry, shell history, endpoint telemetry, proxy logs, or GitHub audit activity. | Isolate the host or runner, preserve package artifacts, rotate reachable credentials, and audit follow-on GitHub/npm/cloud activity. | Affected packages are removed, credentials are rotated, and downstream audits show no unexplained write activity. |
| Presumed exposed | An affected package/version was installed on a developer workstation, CI runner, image build, or release environment but runtime telemetry is incomplete. | Dependency lock, package cache, build log, container layer, or npm install record. | Treat GitHub, npm, cloud, registry, and deployment credentials reachable from that environment as exposed. | Risk owners confirm clean rebuilds and credential rotation or accept residual risk. |
| Potentially exposed | Repositories or builds use @redhat-cloud-services/*, but exact resolved versions or install execution is not established. | Repository manifests, lockfiles, private registry logs, CI logs, and package-cache inventory. | Reconstruct package resolution and install execution before narrowing scope. | Each hit is dispositioned as confirmed compromise, presumed exposed, or not exposed. |
| Not exposed | No affected package names, affected versions, package-cache artifacts, tarballs, or runtime selectors appear in complete evidence. | Negative search across repos, CI logs, package caches, endpoint telemetry, proxy logs, and registry mirrors. | Preserve negative evidence and keep lifecycle-script controls in place. | Evidence coverage includes developer endpoints, CI runners, production builds, and internal mirrors. |
| Unknown | Dependency inventory, package-cache data, runner telemetry, endpoint telemetry, or audit logs are unavailable. | Named gap with system owner and retention window. | Keep credentials in scope until evidence is recovered or rotation closes the gap. | Missing evidence is recovered or the risk owner accepts residual uncertainty. |
Timeline
- 2026-06-01: StepSecurity reports malicious
@redhat-cloud-servicespackage versions and ties the activity to a trusted-publishing workflow compromise [Source 1]. - 2026-06-01: OX publishes its analysis of the Red Hat Cloud Services npm compromise and frames the activity as a Mini Shai-Hulud return wave [Source 2].
- 2026-06-01: Mend publishes a companion analysis of the Miasma payload and highlights malicious behavior in the npm package set [Source 3].
- 2026-06-02: This Halting Problems refresh found no existing local post for the
@redhat-cloud-servicesMiasma wave and created this child event report.
Technical Analysis
The material supply-chain failure is not the mere existence of vulnerable package code. It is the use of trusted CI/CD publishing to push malicious package versions into the public npm registry. StepSecurity reports that the attacker opened a pull request in RedHatInsights/javascript-clients, got workflow-controlled publishing execution, and used the resulting OIDC trusted-publishing path to publish malicious versions [Source 1].
The payload belongs in the same response family as Mini Shai-Hulud because the defensive questions are the same: which package versions resolved, which install hooks executed, which credentials were reachable, and what GitHub/npm/cloud activity followed. OX and Mend both describe the payload as a new wave using Miasma or The Spreading Blight naming, with destructive and credential-theft behavior [Source 2] [Source 3].
Treat successful installation on a runner as credential exposure unless telemetry proves the lifecycle script did not run. Lockfile-only evidence is not enough for confirmed compromise, but it is enough to keep the environment in scope until package-cache and install-log evidence is collected.
Machine-Readable Event Profile
{
"event_id": "redhat-cloud-services-npm-miasma-compromise",
"title": "Red Hat Cloud Services npm Trusted-Publishing Compromise",
"first_seen": "2026-06-01",
"published": "2026-06-02",
"severity": "critical",
"ecosystem": ["npm", "GitHub Actions", "trusted publishing"],
"campaign_context": "Mini Shai-Hulud / Miasma / The Spreading Blight",
"affected_repository": "RedHatInsights/javascript-clients",
"affected_scope": "@redhat-cloud-services",
"affected_packages": [
"@redhat-cloud-services/patch-client",
"@redhat-cloud-services/insights-client",
"@redhat-cloud-services/host-inventory-client",
"@redhat-cloud-services/vulnerabilities-client",
"@redhat-cloud-services/remediations-client",
"@redhat-cloud-services/sources-client",
"@redhat-cloud-services/compliance-client",
"@redhat-cloud-services/rbac-client",
"@redhat-cloud-services/advisor-client",
"@redhat-cloud-services/notifications-client",
"@redhat-cloud-services/integrations-client",
"@redhat-cloud-services/drift-client",
"@redhat-cloud-services/content-sources-client",
"@redhat-cloud-services/approval-client",
"@redhat-cloud-services/topms-client",
"@redhat-cloud-services/ros-client",
"@redhat-cloud-services/cost-management-client",
"@redhat-cloud-services/subscriptions-client",
"@redhat-cloud-services/swatch-client",
"@redhat-cloud-services/image-builder-client",
"@redhat-cloud-services/vulnerability-client",
"@redhat-cloud-services/provisioning-client",
"@redhat-cloud-services/patch-advisory-client",
"@redhat-cloud-services/quickstarts-client",
"@redhat-cloud-services/notifications-backend-client",
"@redhat-cloud-services/landing-page-frontend",
"@redhat-cloud-services/frontend-components",
"@redhat-cloud-services/frontend-components-utilities",
"@redhat-cloud-services/frontend-components-notifications"
],
"credential_risk": ["GitHub", "npm", "cloud", "CI/CD", "developer endpoint"],
"known_behaviors": [
"npm install lifecycle script execution",
"GitHub workflow tampering",
"credential harvesting",
"destructive fallback behavior after token invalidation"
],
"primary_sources": [
"https://www.stepsecurity.io/blog/multiple-redhat-cloud-services-npm-packages-compromised",
"https://www.ox.security/blog/new-npm-supply-chain-attack-redhat-cloud-services-compromised",
"https://www.mend.io/blog/miasma-malware-analysis-a-deep-dive-into-the-red-hat-npm-supply-chain-attack/",
"https://www.bleepingcomputer.com/news/security/red-hat-npm-packages-compromised-to-steal-developer-credentials/"
]
}
Indicators of Compromise
packages:
- "@redhat-cloud-services/patch-client@4.0.4"
- "@redhat-cloud-services/insights-client@3.0.3"
- "@redhat-cloud-services/host-inventory-client@2.0.4"
- "@redhat-cloud-services/vulnerabilities-client@2.0.3"
- "@redhat-cloud-services/vulnerabilities-client@2.0.4"
- "@redhat-cloud-services/remediations-client@4.0.3"
- "@redhat-cloud-services/sources-client@3.0.4"
- "@redhat-cloud-services/compliance-client@3.0.4"
- "@redhat-cloud-services/rbac-client@2.0.3"
- "@redhat-cloud-services/advisor-client@4.0.3"
- "@redhat-cloud-services/notifications-client@3.0.3"
- "@redhat-cloud-services/integrations-client@2.0.4"
- "@redhat-cloud-services/drift-client@3.0.3"
- "@redhat-cloud-services/content-sources-client@4.0.4"
- "@redhat-cloud-services/approval-client@2.0.3"
- "@redhat-cloud-services/topms-client@2.0.4"
- "@redhat-cloud-services/ros-client@2.0.4"
- "@redhat-cloud-services/cost-management-client@3.0.4"
- "@redhat-cloud-services/subscriptions-client@3.0.4"
- "@redhat-cloud-services/swatch-client@2.0.3"
- "@redhat-cloud-services/image-builder-client@3.0.3"
- "@redhat-cloud-services/vulnerability-client@2.0.4"
- "@redhat-cloud-services/provisioning-client@2.0.3"
- "@redhat-cloud-services/patch-advisory-client@2.0.3"
- "@redhat-cloud-services/quickstarts-client@2.0.3"
- "@redhat-cloud-services/notifications-backend-client@2.0.4"
- "@redhat-cloud-services/landing-page-frontend@2.0.3"
- "@redhat-cloud-services/frontend-components@6.0.4"
- "@redhat-cloud-services/frontend-components-utilities@4.0.4"
- "@redhat-cloud-services/frontend-components-notifications@3.0.4"
files:
- "package.json"
- "package-lock.json"
- "pnpm-lock.yaml"
- "yarn.lock"
- "bun.lock"
- "index.js"
- ".github/workflows/codeql.yml"
paths:
- "RedHatInsights/javascript-clients"
- ".github/workflows"
- "node_modules/@redhat-cloud-services"
domains:
- "registry[.]npmjs[.]org"
- "api[.]github[.]com"
- "github[.]com"
urls:
- "hxxps://github[.]com/RedHatInsights/javascript-clients"
telemetry_selectors:
- "Miasma"
- "The Spreading Blight"
- "IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner"
- "firedalazer"
- "chore/add-codeql-static-analysis"
- "BatchedCreateCommitOnBranch"
- "bypass_2fa"
- "Runner.Worker"
- "/proc/*/mem"
- "trusted publishing"
- "id-token: write"
process_patterns:
- "npm install executing lifecycle script from @redhat-cloud-services package"
- "node or bun process launched from package lifecycle hook"
- "workflow run using id-token: write and npm trusted publishing"
network_patterns:
- "GitHub API activity from developer or CI host after package install"
- "npm publish or dist-tag activity tied to trusted-publishing workflow"
Detection and Hunting
Script: local repository and exported telemetry scope
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
LOG_ROOT = os.environ.get("LOG_ROOT", "")
OUT = Path(os.environ.get("OUT", "hp-redhat-cloud-services-npm-miasma-compromise-scope"))
SINCE = "2026-06-02T00:00:00Z"
UNTIL = "2026-06-02T23:59:59Z"
PACKAGES = [
]
VERSIONS = [
]
FILES = [
"package.json",
"package-lock.json",
"pnpm-lock.yaml",
"yarn.lock",
"bun.lock",
"index.js",
".github/workflows/codeql.yml",
]
DOMAINS = [
"registry.npmjs.org",
"api.github.com",
"github.com",
]
URLS = [
"https://github.com/RedHatInsights/javascript-clients",
]
IPS = [
]
HASHES = [
]
PROCESS_PATTERNS = [
"npm install executing lifecycle script from @redhat-cloud-services package",
"node or bun process launched from package lifecycle hook",
"workflow run using id-token: write and npm trusted publishing",
]
NETWORK_PATTERNS = [
"GitHub API activity from developer or CI host after package install",
"npm publish or dist-tag activity tied to trusted-publishing workflow",
]
# Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
# Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
OUT.mkdir(parents=True, exist_ok=True)
indicators_file = OUT / "indicators.txt"
# Collect unique indicators
indicators = set()
for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
for val in group:
if val:
indicators.add(val)
with open(indicators_file, "w") as f:
for ind in sorted(indicators):
f.write(ind + "\n")
print(f"[+] Written unique selectors to {indicators_file}")
# Walk local directory
print(f"[+] Scanning directory: {ROOT} for selectors...")
matches = []
exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
for root, dirs, filenames in os.walk(ROOT):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if matches:
(OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
print(f"[!] Found {len(matches)} matches in codebase!")
# Optional Log Scanning
if LOG_ROOT and os.path.exists(LOG_ROOT):
print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
log_matches = []
for root, _, filenames in os.walk(LOG_ROOT):
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
log_matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if log_matches:
(OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
print(f"[!] Found {len(log_matches)} matches in logs!")
if PACKAGES:
registry_dir = OUT / "registry"
registry_dir.mkdir(exist_ok=True)
for package in PACKAGES:
if not package: continue
safe_name = package.replace("/", "__")
print(f"[+] Querying npm view for {package}...")
res = subprocess.run(["npm", "view", package, "name", "version", "time", "versions", "dist-tags", "maintainers", "dist.tarball", "dist.integrity", "scripts", "--json"], capture_output=True, text=True)
if res.returncode == 0:
(registry_dir / f"npm-{safe_name}.json").write_text(res.stdout)
print(f"[+] Wrote scope artifacts under {OUT}")
Downstream Abuse Audits
Script: GitHub organization run, release, secret, and workflow audit
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
if "ORG" not in os.environ:
print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
sys.exit(1)
ORG = os.environ["ORG"]
SINCE = "2026-06-02T00:00:00Z"
UNTIL = "2026-06-02T23:59:59Z"
OUT = Path(os.environ.get("OUT", "hp-redhat-cloud-services-npm-miasma-compromise-github-audit"))
SELECTORS = [
"package.json",
"package-lock.json",
"pnpm-lock.yaml",
"yarn.lock",
"bun.lock",
"index.js",
".github/workflows/codeql.yml",
"registry.npmjs.org",
"api.github.com",
"github.com",
"https://github.com/RedHatInsights/javascript-clients",
]
# Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
# Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "runs").mkdir(exist_ok=True)
(OUT / "logs").mkdir(exist_ok=True)
(OUT / "repos").mkdir(exist_ok=True)
# 1. Write incident-selectors file
selectors_file = OUT / "incident-selectors.txt"
with open(selectors_file, "w") as sf:
for s in SELECTORS:
if s:
sf.write(s + "\n")
# 2. Get list of repos
print(f"[+] Fetching repositories for organization: {ORG}")
repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
if repo_res.returncode != 0:
print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
sys.exit(1)
repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
for repo in repos:
safe_repo = repo.replace("/", "__")
print(f"[+] Auditing repository: {repo}")
# Check runs in the window
runs_res = subprocess.run([
"gh", "api", f"/repos/{repo}/actions/runs",
"-f", "per_page=100",
"-f", f"created=>={SINCE}",
"--paginate"
], capture_output=True, text=True)
if runs_res.returncode == 0:
try:
all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
if filtered_runs:
with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
for run in filtered_runs:
rf.write(json.dumps(run) + "\n")
# Fetch log dynamically
run_id = str(run["id"])
log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
if log_res.returncode == 0:
(OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
# Fetch details
view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
if view_res.returncode == 0:
(OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
except Exception as e:
print(f"[-] Error parsing runs for {repo}: {e}")
# Check releases in window
subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
# Check repo secrets updated in window
subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
# Check deploy keys
subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
# Scan output directory for any indicator selector matches
print("[+] Scanning gathered telemetry for indicator matches...")
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
print(f"[+] Wrote GitHub audit artifacts under {OUT}")
Script: cloud OIDC and deployment credential follow-on audit
#!/usr/bin/env python3
import os
import json
import subprocess
from pathlib import Path
SINCE = "2026-06-02T00:00:00Z"
UNTIL = "2026-06-02T23:59:59Z"
OUT = Path(os.environ.get("OUT", "hp-redhat-cloud-services-npm-miasma-compromise-cloud-audit"))
AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
# Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
# Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
OUT.mkdir(parents=True, exist_ok=True)
# 1. AWS CloudTrail Audit
print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
aws_events = []
for region in AWS_REGIONS:
res = subprocess.run([
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
"--output", "json"
], capture_output=True, text=True)
if res.returncode == 0:
try:
events = json.loads(res.stdout).get("Events", [])
for event in events:
ct = json.loads(event.get("CloudTrailEvent", "{}"))
ct["region"] = region
aws_events.append(ct)
except Exception as e:
print(f"[-] Error parsing AWS CloudTrail events: {e}")
if aws_events:
with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
for ev in aws_events:
f.write(json.dumps(ev) + "\n")
# Audit follow-on events for returned access keys
for ev in aws_events:
access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
region = ev.get("region", "us-east-1")
if access_key:
print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
f_res = subprocess.run([
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
"--output", "json"
], capture_output=True, text=True)
if f_res.returncode == 0:
try:
f_events = json.loads(f_res.stdout).get("Events", [])
with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
for fe in f_events:
ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
except Exception as e:
print(f"[-] Error writing follow-on events: {e}")
# 2. Azure Activity Log Audit
print("[+] Querying Azure activity logs...")
az_res = subprocess.run([
"az", "monitor", "activity-log", "list",
"--start-time", SINCE,
"--end-time", UNTIL,
"--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
"-o", "json"
], capture_output=True, text=True)
if az_res.returncode == 0:
(OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
# 3. GCP Logging Audit
print("[+] Querying GCP Cloud Logging...")
gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
gcp_res = subprocess.run([
"gcloud", "logging", "read", gcp_filter,
"--format", "json"
], capture_output=True, text=True)
if gcp_res.returncode == 0:
(OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
print(f"[+] Wrote cloud audit artifacts under {OUT}")
Script: registry metadata and artifact audit
#!/usr/bin/env python3
import os
import json
import subprocess
from pathlib import Path
SINCE = "2026-06-02T00:00:00Z"
OUT = Path(os.environ.get("OUT", "hp-redhat-cloud-services-npm-miasma-compromise-registry-audit"))
PACKAGES = [
]
VERSIONS = [
]
# Positive signal: registry metadata, package tarballs, or cached artifacts contain the exact affected package/version values.
# Remediation trigger: any internal package cache, build artifact, or deployment using these package/version values requires exposure scoping.
OUT.mkdir(parents=True, exist_ok=True)
with open(OUT / "affected-versions.txt", "w") as av:
for version in VERSIONS:
if version:
av.write(version + "\n")
# 1. Audit npm dependencies in lockfiles/package.json
print("[+] Scanning lockfiles for npm selectors...")
for file in ["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "package.json"]:
if Path(file).exists():
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), file])
# 2. Query registry metadata and fetch tarballs for local analysis
metadata_dir = OUT / "metadata"
tarballs_dir = OUT / "tarballs"
metadata_dir.mkdir(exist_ok=True)
tarballs_dir.mkdir(exist_ok=True)
for package in PACKAGES:
if not package: continue
safe_name = package.replace("/", "__")
print(f"[+] Querying npm view for {package}...")
res = subprocess.run(["npm", "view", package, "time", "versions", "dist-tags", "maintainers", "dist.tarball", "dist.integrity", "scripts", "--json"], capture_output=True, text=True)
if res.returncode == 0:
(metadata_dir / f"npm-{safe_name}.json").write_text(res.stdout)
subprocess.run(["npm", "pack", package, "--pack-destination", str(tarballs_dir)], capture_output=True)
# 3. HOW TO REVOKE AND ROTATE EXPOSED NPM PUBLISHING TOKENS:
# Revoke all compromised tokens via npm CLI:
# subprocess.run(["npm", "token", "list"])
# subprocess.run(["npm", "token", "revoke", "123456"])
# Or logout to revoke the current session:
# subprocess.run(["npm", "logout"])
# Generate a new publishing token with MFA protection:
# subprocess.run(["npm", "token", "create", "--read-only=false", "--cidr=0.0.0.0/0"])
print(f"[+] Wrote registry audit artifacts under {OUT}")
Remediation and Closure
Remove affected packages from lockfiles, purge package-manager caches, rebuild containers from clean dependency state, and block affected package/version pairs in private registry policy. For environments that installed the malicious versions, rotate credentials from a clean host before reconnecting the original asset.
Closure requires negative evidence across repositories, package caches, build logs, CI runner artifacts, endpoint telemetry, and internal npm mirrors. If telemetry is unavailable, close the gap with broad credential rotation and a documented residual-risk decision.
Sources
IOC Clipboard
14 IOCsregistry.npmjs.org registry[.]npmjs[.]org api.github.com api[.]github[.]com github.com github[.]com https://github.com/RedHatInsights/javascript-clients hxxps://github[.]com/RedHatInsights/javascript-clients package.json package.json package-lock.json package-lock.json pnpm-lock.yaml pnpm-lock.yaml yarn.lock yarn.lock bun.lock bun.lock index.js index.js .github/workflows/codeql.yml .github/workflows/codeql.yml RedHatInsights/javascript-clients RedHatInsights/javascript-clients .github/workflows .github/workflows node_modules/@redhat-cloud-services node_modules/@redhat-cloud-services