critical Threat analysis

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).

#npm#supply-chain#compromise#RAT#waveshaper#unc1069
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

    Impact Determination

    ClassificationCriteriaRequired evidenceRequired actionClosure 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 exposedThe 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 exposedNo 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.
    UnknownRequired 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

    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:

    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.js script deleted the malicious installer files from node_modules/plain-crypto-js and 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 /tmp directory 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

    1. 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.
    2. Google Threat Intelligence Group (GTIG). Role: PRIMARY_RESEARCH Impact: Attributed campaign to UNC1069, identified the WAVESHAPER.V2 RAT, and detailed the platform-specific payloads.
    3. Microsoft Threat Intelligence. Role: PRIMARY_RESEARCH Impact: Coordinated advisory detailing threat actor attribution, WAVESHAPER.V2 behaviors, and Windows PowerShell payload mechanics.
    4. 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.
    5. 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.
    6. 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.
    7. 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.
    8. 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.
    9. 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 IOCs
    Defang IOCs
    domain sfrclak.com sfrclak[.]com
    domain com.apple.act.mond com[.]apple[.]act[.]mond
    url https://sfrclak.com/api/v1/beacon hxxps://sfrclak[.]com/api/v1/beacon
    url https://sfrclak.com/payloads/ hxxps://sfrclak[.]com/payloads/
    url http://sfrclak.com:8000 hxxp://sfrclak[.]com:8000
    url https://google.com hxxps://google[.]com
    url https://elastic.co hxxps://elastic[.]co
    url https://paloaltonetworks.com hxxps://paloaltonetworks[.]com
    url https://github.com/advisories/GHSA-fw8c-xr5c-95f9 hxxps://github[.]com/advisories/GHSA-fw8c-xr5c-95f9
    ip 142.11.206.73 142[.]11[.]206[.]73
    hash e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09 e10b1fa84f1d6481625f741b69892780140d4e0e7769e7491e5f4d894c2e0e09
    hash 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a 92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a
    hash 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101 617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101
    hash fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf
    file /Library/Caches/com.apple.act.mond /Library/Caches/com.apple.act.mond
    file %PROGRAMDATA%\\wt.exe %PROGRAMDATA%\\wt.exe
    file /tmp/ld.py /tmp/ld.py