Axios npm Package Compromise (UNC1069)
On March 31, 2026, the popular JavaScript HTTP client Axios was compromised when attackers hijacked a lead maintainer's npm account, publishing malicious versions containing a phantom dependency to drop a cross-platform Remote Access Trojan (RAT).
On this page 0% read
Executive Summary
On March 31, 2026, the widely utilized JavaScript HTTP library Axios was the target of a major software supply chain attack GitHub Security Advisory (GHSA-fw8c-xr5c-95f9). Attackers compromised the npm publishing account of a lead Axios maintainer (jasonsaayman) and published two backdoored versions: [email protected] (tagged as latest) and [email protected] (tagged as legacy) Google Threat Intelligence Group (GTIG).
The malicious versions injected a “phantom” dependency named [email protected] Wiz Threat Research. This package executed a postinstall script (setup.js / SILKBELL dropper) upon installation Elastic Security Labs. The script evaluated the host operating system (Windows, macOS, or Linux) and downloaded/deployed a cross-platform Remote Access Trojan (RAT) identified as WAVESHAPER.V2 Google Threat Intelligence Group (GTIG).
Threat researchers from Google and Microsoft attributed the attack to the North Korean state-sponsored threat group tracked as UNC1069 Microsoft Threat Intelligence. The exposure window lasted approximately three hours before the npm security team removed the compromised artifacts from the registry GitHub Security Advisory (GHSA-fw8c-xr5c-95f9). The primary defensive action is to verify that developer systems and CI/CD environments did not install these specific versions during the three-hour window on March 31, 2026, and immediately rotate all credentials if exposure is suspected CISA Security Advisory.
Key Facts
threat_type: maintainer account compromise, malicious package, credential theft, token exfiltration
ecosystem: npm
registry: npm
affected_packages:
- "axios"
- "plain-crypto-js"
malicious_versions:
- "[email protected]"
- "[email protected]"
- "[email protected]"
fixed_versions:
- "[email protected]"
- "[email protected]"
safe_versions:
- "[email protected]"
- "[email protected]"
exposure_window: ~3 hours (2026-03-31T00:21:00Z to 2026-03-31T03:30:00Z)
execution_trigger: install-time postinstall lifecycle hook
primary_impact: Credential theft (GitHub PATs, cloud keys, SSH keys), Remote Access Trojan (RAT) execution, remote command execution
known_iocs:
- "sfrclak[.]com"
- "142.11.206[.]73"
- "e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09"
- "92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a"
- "617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101"
- "fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf"
- "com.apple.act.mond"
- "wt.exe"
- "ld.py"
confidence: high
canonical_source: https://github.com/advisories/GHSA-fw8c-xr5c-95f9
Source Confidence & Evidence Mapping
- confirmed: Compromise of Axios lead maintainer
jasonsaayman’s npm account, publication of malicious versions[email protected]and[email protected], injection of the phantom dependency[email protected], the use ofpostinstallto triggersetup.js(SILKBELL), and the removal of the packages after a three-hour window are fully backed by direct registry telemetry and vendor security advisories GitHub Security Advisory (GHSA-fw8c-xr5c-95f9) CISA Security Advisory Google Threat Intelligence Group (GTIG). - likely: Highly targeted social engineering of
jasonsaaymanvia a fake branded Slack workspace to drop an info-stealer that harvested npm publishing credentials Huntress Labs. The bypass of GitHub Actions OIDC Trusted Publishing via long-lived classic npm access tokens configured on the maintainer’s registry account Wiz Threat Research. - unclear: The exact initial malware used to compromise
jasonsaayman’s local environment during the fake Slack workspace interaction is not fully documented Huntress Labs. - not_observed: No direct modification of the primary Axios source repository on GitHub occurred; the compromise was executed purely at the registry publishing level GitHub Security Advisory (GHSA-fw8c-xr5c-95f9).
Impact Determination
| Classification | Criteria | Required evidence | Required action | Closure condition |
|---|---|---|---|---|
| Confirmed compromise | [email protected], [email protected], or [email protected] is present and npm postinstall launches setup.js / SILKBELL or the reported process, file, or network indicators is observed. | Artifact inventory plus runtime telemetry showing npm postinstall launches setup.js / SILKBELL or listed C2/process/file indicators. | Isolate affected hosts or runners, preserve artifacts, and rotate reachable credentials from a clean environment. | Affected artifacts are removed, exposed credentials are replaced, and downstream audit modules show no suspicious follow-on use. |
| Presumed exposed | [email protected], [email protected], or [email protected] was installed, pulled, imported, built, or executed during the exposure window, but telemetry cannot prove exfiltration. | Lockfile, package cache, workflow, image pull, extension inventory, build log, or deployment record tied to the exposure window. | Rebuild from clean artifacts and rotate credentials available to the affected environment. | Credential owners confirm revocation of old material and clean artifacts are deployed. |
| Potentially exposed | The package, workflow, image, extension, or module appears in dependency or deployment records, but npm lifecycle execution is not established. | Manifest, lockfile, build, deployment, or endpoint records plus a named telemetry gap. | Collect the missing execution and telemetry evidence before narrowing scope. | Every hit is dispositioned as confirmed compromise, presumed exposed, or not exposed. |
| Not exposed | No affected version, artifact, mutable reference, or indicator appears in source, lockfiles, build outputs, deployments, package caches, or runtime telemetry. | Repository search, dependency inventory, build/deployment export, package cache query, and runtime telemetry query results. | Preserve the negative search output and keep the prevention controls active. | Search evidence covers developer endpoints, CI runners, production deployments, and package or image caches. |
| Unknown | Required inventory, build, endpoint, network, or audit telemetry is unavailable. | A gap statement naming unavailable systems, owners, and time windows. | Keep the asset in scope and make conservative rotation or rebuild decisions for high-value environments. | The missing evidence is recovered or the risk owner accepts residual uncertainty. |
Minimum Evidence To Collect
minimum_evidence:
- "Dependency, workflow, extension, image, or module inventory covering developer endpoints, CI runners, and production deployments."
- "Positive or negative search results for [email protected], [email protected], [email protected]."
- "Execution evidence for npm postinstall launches `setup.js` / SILKBELL."
- "Process, file, DNS, proxy, firewall, or package-manager telemetry for listed indicators."
- "Inventory of credentials, tokens, deployment paths, and downstream systems reachable from exposed environments."
Timeline
- 2026-03-31T00:21:00Z The compromised account of Axios lead maintainer
jasonsaaymanpublishes backdoored versions[email protected](tagged as latest) and[email protected](tagged as legacy) to the npm registry. Source: GitHub Security Advisory (GHSA-fw8c-xr5c-95f9) - 2026-03-31T00:30:00Z Automated security scanners at Huntress and Elastic flag suspicious behavioral anomalies in the newly published Axios versions, noting the execution of child processes during installation. Source: Huntress Labs Elastic Security Labs
- 2026-03-31T01:30:00Z Security researchers confirm the presence of the
[email protected]phantom dependency, its execution ofsetup.js(SILKBELL), and its outbound C2 beaconing tosfrclak[.]com. Source: Wiz Threat Research Trend Micro - 2026-03-31T03:30:00Z The npm security team removes both malicious Axios versions and the
plain-crypto-jspackage from the registry, closing the exposure window. Source: GitHub Security Advisory (GHSA-fw8c-xr5c-95f9) - 2026-03-31T09:00:00Z CISA, Google, and Microsoft issue coordinated advisories detailing the attack mechanism, attributing it to North Korean state-sponsored threat group UNC1069, and providing remediation guides. Source: CISA Security Advisory Google Threat Intelligence Group (GTIG) Microsoft Threat Intelligence
What Happened
On March 31, 2026, the popular Axios HTTP client library was compromised in a supply chain attack orchestrated by North Korean state-sponsored actors tracked as UNC1069 Google Threat Intelligence Group (GTIG). The threat actors used highly targeted social engineering to compromise the account of the Axios maintainer, jasonsaayman Huntress Labs. By impersonating a company founder and inviting the maintainer to a fake, branded Slack workspace, the attackers tricked them into installing malware that provided persistent access to their environment, including npm publishing credentials Huntress Labs Elastic Security Labs.
Using these stolen credentials, the attackers manually published two backdoored versions of Axios: 1.14.1 and 0.30.4 GitHub Security Advisory (GHSA-fw8c-xr5c-95f9). The malicious packages bypass GitHub Actions OIDC “Trusted Publishing” by using long-lived classic npm access tokens that were configured on the maintainer’s account, which took precedence over the more secure OIDC workflow in the configuration used at the time Wiz Threat Research.
The backdoor itself was implemented as a “phantom dependency” named [email protected] Wiz Threat Research. By placing the malicious logic in a separate dependency rather than modifying the main Axios codebase, the attackers minimized the chance of a casual reviewer noticing changes in the Axios GitHub repository, as the compromise was executed purely on the registry level via direct package publishing GitHub Security Advisory (GHSA-fw8c-xr5c-95f9) Elastic Security Labs.
Technical Analysis
Initial Access
The threat actors compromised the local development environment of Axios maintainer jasonsaayman via highly targeted social engineering Huntress Labs. The attackers impersonated a prominent company founder and invited jasonsaayman to join a fake, branded Slack workspace Huntress Labs. During the interaction, the maintainer was tricked into running malware disguised as a workspace application, which compromised his host machine and exfiltrated long-lived classic npm publishing tokens stored locally Huntress Labs Wiz Threat Research.
Package or Artifact Manipulation
Once in possession of the maintainer’s classic npm token, the attackers bypassed GitHub Actions OIDC “Trusted Publishing” entirely Wiz Threat Research. They manually published two compromised versions of Axios—[email protected] and [email protected]—directly to the npm registry GitHub Security Advisory (GHSA-fw8c-xr5c-95f9). The primary code files of Axios itself were not altered. Instead, the attackers added a “phantom” dependency in the package.json file Wiz Threat Research:
package_json_dependency_diff:
plain-crypto-js: "^4.2.1"
This package (plain-crypto-js) was an attacker-controlled package published specifically to deliver the payload, mimicking the legitimate crypto-js library name Elastic Security Labs.
Execution Trigger
The plain-crypto-js package manifest included a postinstall script Elastic Security Labs. When a developer or CI/CD runner executed npm install (and scripts were not disabled), this script immediately executed an obfuscated JavaScript dropper (setup.js / SILKBELL) Elastic Security Labs Google Threat Intelligence Group (GTIG).
Payload Behavior
The setup.js (SILKBELL) dropper fingerprinted the host operating system (Windows, macOS, or Linux) and contacted a command-and-control (C2) server at sfrclak[.]com:8000 to download a platform-specific Remote Access Trojan (RAT) named WAVESHAPER.V2 Google Threat Intelligence Group (GTIG) Elastic Security Labs.
The RAT was engineered to harvest high-value credentials, establish persistent access, and execute remote commands Microsoft Threat Intelligence. Once executed, the WAVESHAPER.V2 RAT harvested:
- GitHub Personal Access Tokens and repository contents CISA Security Advisory.
- AWS, Azure, and Google Cloud authentication keys CISA Security Advisory.
- Local SSH keys and browser-saved credentials Elastic Security Labs.
- Keystroke logs and interactive shell commands Microsoft Threat Intelligence.
Exfiltration / C2
domains:
- "sfrclak[.]com"
ips:
- "142.11.206[.]73"
urls:
- "https://sfrclak[.]com/api/v1/beacon"
- "https://sfrclak[.]com/payloads/"
- "http://sfrclak[.]com:8000"
protocols:
- "HTTP/HTTPS"
- "TCP/8000"
endpoints:
- "/api/v1/beacon"
- "/payloads/"
confidence: high
Propagation
The attack did not spread laterally downstream via automated self-propagation (it was not a worm) Google Threat Intelligence Group (GTIG). However, due to the widespread integration of Axios in modern web applications, the backdoor propagated passively to thousands of developer systems and CI/CD pipelines that pulled the latest npm packages during the 3-hour window on March 31, 2026 Wiz Threat Research Huntress Labs.
Obfuscation or Evasion
To evade detection and complicate forensic analysis, the malware executed several evasion techniques Elastic Security Labs:
- Self-Deletion: Once the RAT was executing as a background child process, the
setup.jsscript deleted the malicious installer files fromnode_modules/plain-crypto-jsand replaced them with clean dummy files Elastic Security Labs Trend Micro. - Process Masquerading (macOS): The macOS RAT payload was dropped into
/Library/Caches/com.apple.act.mond, masquerading as an Apple reverse-DNS Activity Monitor daemon (com.apple.act.mond) to evade host-based security tools Palo Alto Networks. - Binary Masquerading (Windows): On Windows, the malware copied the legitimate PowerShell binary to
%PROGRAMDATA%\wt.exe(mimicking the “Windows Terminal” executable name) to bypass script-execution policies and launch the secondary PowerShell-based RAT payload Palo Alto Networks. - Temp Smuggling (Linux): On Linux, the payload was dropped into the volatile
/tmpdirectory as a simple Python-based script (/tmp/ld.py) Palo Alto Networks.
Affected Assets and Blast Radius
affected_assets:
ecosystems:
- "npm"
packages:
- "axios"
- "plain-crypto-js"
versions:
- "[email protected]"
- "[email protected]"
- "[email protected]"
repositories: []
container_images: []
CI_CD_systems:
- "GitHub Actions"
- "GitLab CI"
- "CircleCI"
- "Jenkins"
developer_tools:
- "npm cli"
- "yarn cli"
- "pnpm cli"
environments:
- developer workstations
- CI runners
- build pipelines
- containers
- production systems
credentials_at_risk:
- npm tokens
- GitHub tokens
- cloud credentials
- SSH keys
- environment variables
not_currently_known_to_affect:
- Axios source repository on GitHub (the code repository itself was not compromised or modified) [GitHub Security Advisory (GHSA-fw8c-xr5c-95f9)](https://github.com/advisories/GHSA-fw8c-xr5c-95f9).
Indicators of Compromise
domains:
- value: "sfrclak[.]com"
source: "https://google.com"
confidence: "high"
ips:
- value: "142.11.206[.]73"
source: "https://google.com"
confidence: "high"
urls:
- value: "https://sfrclak[.]com/api/v1/beacon"
source: "https://google.com"
confidence: "high"
- value: "https://sfrclak[.]com/payloads/"
source: "https://google.com"
confidence: "high"
- value: "http://sfrclak[.]com:8000"
source: "https://google.com"
confidence: "high"
hashes:
- value: "e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09"
source: "https://elastic.co"
confidence: "high"
- value: "92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a"
source: "https://elastic.co"
confidence: "high"
- value: "617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101"
source: "https://elastic.co"
confidence: "high"
- value: "fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf"
source: "https://elastic.co"
confidence: "high"
files:
- value: "/Library/Caches/com.apple.act.mond"
source: "https://paloaltonetworks.com"
confidence: "high"
- value: "%PROGRAMDATA%\\wt.exe"
source: "https://paloaltonetworks.com"
confidence: "high"
- value: "/tmp/ld.py"
source: "https://paloaltonetworks.com"
confidence: "high"
package_versions:
- value: "[email protected]"
source: "https://github.com/advisories/GHSA-fw8c-xr5c-95f9"
confidence: "high"
- value: "[email protected]"
source: "https://github.com/advisories/GHSA-fw8c-xr5c-95f9"
confidence: "high"
- value: "[email protected]"
source: "https://github.com/advisories/GHSA-fw8c-xr5c-95f9"
confidence: "high"
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-axios-npm-compromise-scope"))
SINCE = "2026-03-31T00:21:00Z"
UNTIL = "2026-03-31T23:59:59Z"
PACKAGES = [
"axios",
"plain-crypto-js",
]
VERSIONS = [
"[email protected]",
"[email protected]",
"[email protected]",
]
FILES = [
"/Library/Caches/com.apple.act.mond",
"%PROGRAMDATA%\\\\wt.exe",
"/tmp/ld.py",
]
DOMAINS = [
"sfrclak.com",
"com.apple.act.mond",
]
URLS = [
"https://sfrclak.com/api/v1/beacon",
"https://sfrclak.com/payloads/",
"http://sfrclak.com:8000",
"https://google.com",
"https://elastic.co",
"https://paloaltonetworks.com",
"https://github.com/advisories/GHSA-fw8c-xr5c-95f9",
]
IPS = [
"142.11.206.73",
]
HASHES = [
"e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09",
"92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a",
"617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101",
"fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf",
]
PROCESS_PATTERNS = [
]
NETWORK_PATTERNS = [
]
# 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-03-31T00:21:00Z"
UNTIL = "2026-03-31T23:59:59Z"
OUT = Path(os.environ.get("OUT", "hp-axios-npm-compromise-github-audit"))
SELECTORS = [
"axios",
"plain-crypto-js",
"[email protected]",
"[email protected]",
"[email protected]",
"/Library/Caches/com.apple.act.mond",
"%PROGRAMDATA%\\\\wt.exe",
"/tmp/ld.py",
"sfrclak.com",
"com.apple.act.mond",
"https://sfrclak.com/api/v1/beacon",
"https://sfrclak.com/payloads/",
"http://sfrclak.com:8000",
"https://google.com",
"https://elastic.co",
"https://paloaltonetworks.com",
"https://github.com/advisories/GHSA-fw8c-xr5c-95f9",
"142.11.206.73",
"e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09",
"92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a",
"617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101",
"fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf",
]
# 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-03-31T00:21:00Z"
UNTIL = "2026-03-31T23:59:59Z"
OUT = Path(os.environ.get("OUT", "hp-axios-npm-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-03-31T00:21:00Z"
OUT = Path(os.environ.get("OUT", "hp-axios-npm-compromise-registry-audit"))
PACKAGES = [
"axios",
"plain-crypto-js",
]
VERSIONS = [
"[email protected]",
"[email protected]",
"[email protected]",
]
# 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}")
Sources
- GitHub Security Advisory: GHSA-fw8c-xr5c-95f9. Role: DIRECT_SOURCE Impact: Primary source mapping the registry vulnerability, package removals, affected package versions, and official mitigation targets.
- Google Threat Intelligence Group (GTIG). Role: PRIMARY_RESEARCH Impact: Attributed campaign to UNC1069, identified the WAVESHAPER.V2 RAT, and detailed the platform-specific payloads.
- Microsoft Threat Intelligence. Role: PRIMARY_RESEARCH Impact: Coordinated advisory detailing threat actor attribution, WAVESHAPER.V2 behaviors, and Windows PowerShell payload mechanics.
- CISA: Joint Security Advisory on UNC1069 Supply Chain Attacks. Role: ENRICHMENT_DATA Impact: Comprehensive federal security warning detailing recovery recommendations, host isolation guidelines, and credential rotation workflows.
- Wiz Threat Research: The Axios Supply Chain Incident and the Trusted Publishing Gap. Role: PRIMARY_RESEARCH Impact: Detailed the classic npm access token vulnerability that allowed attackers to bypass GitHub Actions OIDC Trusted Publishing.
- Elastic Security Labs: Behavior Detection and IOCs for plain-crypto-js Dropper. Role: PRIMARY_RESEARCH Impact: Documented the postinstall execution chain, the setup.js (SILKBELL) dropper behavior, and SHA256 hashes for all payloads.
- Huntress Labs: Threat Analysis of the Axios Supply Chain Compromise. Role: PRIMARY_RESEARCH Impact: Uncovered the initial social engineering entry point involving the fake Slack workspace invitation targeting maintainer
jasonsaayman. - Palo Alto Networks Unit 42: WAVESHAPER Host-Based Evasion Techniques. Role: SECONDARY_ANALYSIS Impact: Deep dive into process and binary masquerading, cataloging indicators like com.apple.act.mond and wt.exe.
- Trend Micro: supply-chain threat profile of plain-crypto-js. Role: SECONDARY_ANALYSIS Impact: Analyzed the evasion and self-deletion routines executed by the setup.js script post-execution.
IOC Clipboard
17 IOCssfrclak.com sfrclak[.]com com.apple.act.mond com[.]apple[.]act[.]mond https://sfrclak.com/api/v1/beacon hxxps://sfrclak[.]com/api/v1/beacon https://sfrclak.com/payloads/ hxxps://sfrclak[.]com/payloads/ http://sfrclak.com:8000 hxxp://sfrclak[.]com:8000 https://google.com hxxps://google[.]com https://elastic.co hxxps://elastic[.]co https://paloaltonetworks.com hxxps://paloaltonetworks[.]com https://github.com/advisories/GHSA-fw8c-xr5c-95f9 hxxps://github[.]com/advisories/GHSA-fw8c-xr5c-95f9 142.11.206.73 142[.]11[.]206[.]73 e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09 e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf /Library/Caches/com.apple.act.mond /Library/Caches/com.apple.act.mond %PROGRAMDATA%\\wt.exe %PROGRAMDATA%\\wt.exe /tmp/ld.py /tmp/ld.py