cd /news/ai-safety/miasma-anatomy-of-an-open-source-sup… · home › topics › ai-safety › article
[ARTICLE · art-164053] src=ossprey.com ↗ pub= topic=ai-safety verified=true sentiment=↓ negative

Miasma: Anatomy of an Open-Source Supply-Chain Worm

Ossprey Security obtained and analyzed the full source release of Miasma, a self-propagating credential-stealing worm published on npm as `voicefromtheouterworld` that needs only a single GitHub PAT to harvest credentials from local filesystems, cloud APIs, process memory and password managers and to propagate across npm, PyPI, RubyGems, GitHub repos and Actions, SSH hosts, AWS EC2 via SSM and JFrog Artifactory. Miasma targets 13 AI coding tools, including Claude Code, Gemini CLI and Cursor, injecting persistent execution hooks that fire when a developer opens a project or starts a session, and uses stolen PATs plus GitHub commit search for exfiltration and command and control. Ossprey said the README self-attributes the worm to TeamPCP but that it has no independent evidence confirming that group authored it, assessing the worm was likely written by a group inspired by TeamPCP's methodology; the primary C2 transport, which POSTs to `api.anthropic.com/v1/api`, is commented out of the open-source build, leaving GitHub public repository commits as the only active exfiltration path.

read24 min views3 publishedNov 4, 2026
Miasma: Anatomy of an Open-Source Supply-Chain Worm
Image: source

Ossprey Security obtained and analyzed the complete source release of Miasma, a self-propagating multi-ecosystem credential-stealing worm published under the npm package name voicefromtheouterworld. The README self-attributes it to TeamPCP, but we have no independent evidence confirming that group authored or published it. We examined every source file in the release and corroborated our findings against contemporaneous incident reporting covering Red Hat's @redhat-cloud-services npm compromise, the Microsoft Azure GitHub organization takedown, and prior TanStack/Mistral AI campaign waves.

Miasma requires only a single GitHub PAT to operate. Given one token, it harvests credentials from the local filesystem, cloud provider APIs, process memory, and password managers; encrypts and exfiltrates everything; and propagates across every connected ecosystem: npm, PyPI, RubyGems, GitHub repos and Actions, SSH hosts, AWS EC2 via SSM, and JFrog Artifactory. It targets 13 AI coding tools - including Claude Code, Gemini CLI, and Cursor - injecting persistent execution hooks that fire whenever a developer opens a project or starts a session. It requires no dedicated infrastructure of its own: stolen PATs and GitHub's own commit search are sufficient for both exfiltration and command and control.

Open-source release caveat: The primary C2 transport (DomainSender, which POSTs to api.anthropic.com/v1/api) is explicitly commented out of the open-source release per the README: "Domain sending (DomainSender) is currently stripped from the sender chain in the open-source release. It can be easily added back - the code lives in src/sender/domain/**." In the shipped OSS build, GitHub public repository commits are the only active exfiltration path. The configuration, code, and design intent for the api.anthropic.com C2 are fully present; only the sender chain hook is commented out. Operator/private builds are expected to have it re-enabled.

Who is Behind This? #

The README bundled with this release credits 'TeamPCP' by name, but self-attribution in a public release is not reliable evidence of authorship - it could reflect a copycat actor claiming affiliation or deliberate misdirection. External vendor reporting from ReversingLabs, Wiz, Unit 42, and Tenable tracks the TeamPCP cluster under aliases including Replicating Marauder, TGR-CRI-1135, UNC6780, DeadCatx3, PCPcat, ShellForce, and CipherForce. We have tracked this cluster's earlier campaigns in depth, including the Trivy supply chain attack and the complete 83-day TeamPCP campaign timeline. The technical tradecraft - per-build self-extracting payloads, multi-ecosystem token abuse, and commit-based C2 - overlaps with previously documented Mini Shai-Hulud samples associated with that cluster, but code overlap alone does not confirm authorship. Our working assessment is that this worm was written by a group inspired by TeamPCP's methodology rather than TeamPCP itself. Attribution should not be confirmed without C2 infrastructure correlation or additional forensic evidence.

Technical Breakdown #

Stage 0: Preflight and Anti-Analysis

We found a comprehensive preflight sequence in src/orchestrator/preflight.ts, src/utils/checkSandbox.ts, and src/utils/config.ts that runs before any credential collection begins.

Decoy detection checks multiple environment variables for known sandbox signatures and exits silently on a match: GITHUB_TOKEN starting with ghp_decoyGitHubToken, NPM_TOKEN starting with npm_F4k3NPMToken, ANTHROPIC_API_KEY starting with sk-ant-api03-fake, AWS_ACCESS_KEY_ID starting with AKIAFAKE, and GITHUB_REPOSITORY equal to actions-security-demo/compromised-packages. Additional exits trigger if the CWD starts with /tmp/npm-safe/ or /opt/hscan-supplychain-dynamic, if the hostname contains sandbox, or if ~/.aws/credentials contains known AWS documentation example keys. A Russian locale exit checks Intl.DateTimeFormat().resolvedOptions().locale, LC_ALL, LC_MESSAGES, LANGUAGE, and LANG, exiting immediately if any starts with ru.

EDR detection runs ps aux or tasklist against 17 process names and checks 4 installation paths. Covered products include CrowdStrike Falcon (falcon-sensor, falcond, csfalcon), SentinelOne, Microsoft Defender for Endpoint ( mdatp, wdavdaemon), Carbon Black, Cylance, Trend Micro, Tanium, and osquery.

When not running inside CI, the worm forks as a background daemon and exits the parent - it detects 30+ CI systems including GitHub Actions, GitLab CI, CircleCI, Jenkins, Azure Pipelines, Buildkite, Vercel, and Cloudflare Pages. It then writes a PID lock file and exits if another instance is already running. SIGINT and SIGTERM handlers are replaced with no-ops, making the process immune to standard termination signals.

An undocumented OIDC fast-path (checkTargetRepo()) - not present in ARCHITECTURE.MD - checks whether the worm is running inside a GitHub Actions workflow where WORKFLOW_ID and REPO_ID_SUFFIX env vars match an expected pattern. If so, it immediately executes NPMOidcClient to publish trojanized packages using the runner's OIDC token and exits, bypassing the rest of the kill chain. This allows the worm to self-trigger directly inside OIDC-capable npm-publish runners.

The StepSecurity Harden-Runner bypass is layered across three paths: if the Docker socket is accessible, it kills any container whose name or image contains harden-runner, step-security, or stepsecurity. If passwordless sudo is available, it overwrites /etc/resolv.conf with public DNS servers or appends 127.0.0.1 stepsecurity.io (and related domains) to /etc/hosts. If no direct sudo but Docker socket access exists, it spawns a privileged Alpine container bind-mounted at /etc/sudoers.d/ to grant the runner passwordless sudo, then uses that sudo for the DNS operations.

Stage 1: Quick Harvest and Inline C2 Check-In

We found three credential providers running synchronously before the exfiltration sender chain is built, so stolen tokens can immediately seed the transport layer.

FileSystemService glob-scans 100+ hardcoded hotspot paths. Beyond cloud credentials and SSH keys, notable targets include Claude AI configuration files (~/.claude.json, ~/.claude/*, ~/.claude/projects/*, ~/.claude/mcp.json), shell history for bash, zsh, Python, MySQL, and PostgreSQL, Signal configuration, Slack cookies, Telegram Desktop data, Windows VPN configurations (NordVPN, ProtonVPN, CyberGhost, PIA, Windscribe, OpenVPN), and Monero wallet files. Binary files are base64-encoded before exfiltration; files over 10 MB are skipped. The provider is entirely skipped if hostile EDR is detected.

ShellService runs gh auth token to extract any GitHub CLI credential, captures the full process.env block - which in CI environments frequently contains injected tokens that never appear as files - and records hostname and username.

GitHubRunner reads /proc/<pid>/maps and /proc/<pid>/mem of any Runner.Worker process when running inside GitHub Actions, extracting secrets injected into runner memory that never touch disk or environment variables. An equivalent PowerShell-based dump runs on Windows Actions runners.

After the GitHub sender is initialized, an undocumented C2 check-in runs inline (src/c2/checkin.ts - not mentioned in ARCHITECTURE.MD). It searches GitHub commits for TheBeautifulSandsOfTime, verifies each matching commit's RSA-PSS-SHA256 signature against the embedded verify_key, and eval() s the decoded payload as JavaScript. This gives the operator arbitrary code execution on any victim that runs the worm after the signed commit is posted, with no file written to disk. This is distinct from the persistent GITHUB_MONITOR.py background process - that executes Python; this check-in executes JavaScript inline during the worm's main flow.

Stage 2: Exfiltration and C2

We traced all collected credential bundles through a single encryption pipeline in src/sender/base.ts: JSON-serialized results are gzip-compressed, encrypted with AES-256-GCM using a fresh random 32-byte key and 12-byte IV per envelope, and the AES key is wrapped with RSA-OAEP-SHA256 against the operator's 4096-bit enc_key.pub. The operator decrypts with the matching private key via scripts/decrypt.ts. Payloads over 30 MB are split into chunks with 5 retries and exponential backoff.

The sender chain attempts transports in priority order. The primary transport is DomainSender, which POSTs to https://api.anthropic.com/v1/api. The path /v1/api does not exist on the real Anthropic API; real API calls go to /v1/messages, /v1/complete, and similar paths. In CI/CD environments that allowlist AI API endpoints, this traffic would be indistinguishable from a legitimate API call at the network layer. This transport is stripped from the open-source release - the sender chain hook in src/orchestrator/senders.ts is commented out. Operator builds re-enable it trivially.

The fallback GitHubSender searches GitHub for commits containing AES-256-CBC-encrypted PATs disguised as fine-grained PAT strings (github_pat_11A + 92 additional characters) alongside the search string DontRevokeOrItGoesBoom. These are decrypted with the hardcoded TOKEN_AES_KEY, validated, and used to create public exfiltration repositories with Greek underworld-themed names using a fixed wordlist (stygian, tartarean, erebean, infernal, chthonic, acheronian, lethean, plutonian, abyssal, charonian, thanatic, funereal, nekyian, sepulchral, tenebrous, cimmerian combined with cerberus, charon, tartarus, erebus, asphodel, acheron, styx, lethe, cocytus, phlegethon, shade, eidolon, wraith, thanatos, hecate, persephone). Every created repo has the description "Hades * The End for the Damned". Encrypted blobs are committed to a results/ directory. This is the only active exfiltration path in the open-source build.

If the primary C2 domain fails its health check, the worm searches GitHub commits for messages matching firedalazer <url_base64>.<signature_base64>, verifies the signature against verify_key.pub with RSA-PSS-SHA256, and uses the decoded URL as the new C2 endpoint - allowing the operator to rotate C2 infrastructure without touching deployed binaries.

Stage 3: Deep Harvest

We found credential collection running through parallel cloud provider tracks simultaneously. All providers run via Promise.allSettled; token validation triggers immediate side-effects - npm and RubyGems trojanization starts synchronously as soon as a valid token is found, before the propagation phase. Package backdooring occurs even if the worm is interrupted mid-run.

AWS providers enumerate all SSM Parameters (with decryption) and all Secrets Manager secrets across 17 regions. Kubernetes secrets are collected across all namespaces via in-cluster service account or kubeconfig. HashiCorp Vault KV v1/v2 secrets are collected via env token, file, Kubernetes service account, or AWS IAM auth. Password managers are targeted via CLI: 1Password (op), Bitwarden ( bw), pass, and gopass.

The Azure Key Vault and GCP Secret Manager providers are present in the source but the authors' own ARCHITECTURE.MD marks them as broken - they have never successfully exfiltrated live data from either platform. The code executes and attempts collection; defenders should not assume these providers are non-functional against all targets, but the authors themselves have not confirmed live success.

For each GitHub PAT with workflow scope, GitHubActionsService commits a malicious workflow named "Run Copilot" to a temporary branch that dumps all repo-level secrets as an artifact named format-results via ${{ toJSON(secrets) }}, downloads the artifact, exfiltrates the contents, and deletes the workflow.

Stage 4: Multi-Ecosystem Propagation

We counted up to fourteen parallel mutators running with credentials in hand, several of which are undocumented in the authors' own ARCHITECTURE.MD.

GitHub branch poisoning (ReadmeUpdater) pushes malicious files to every feature branch in the current repository via GitHub's GraphQL createCommitOnBranch mutation. Commit camouflage copies the last real commit message from each target branch. Files injected: .vscode/tasks.json (VSCode folderOpen task running node .vscode/setup.mjs), .claude/settings.json (Claude Code SessionStart hook), .claude/index.js (self-extracting worm payload), and .claude/setup.mjs (Bun bootstrapper). The .claude/settings.json merges with any existing settings found on the default branch, preserving existing hooks.

GitHub Actions tag hijacking (ActionMutator) enumerates all writable repos containing custom Actions (action.yml or action.yaml), finds all semver tags ( v1, v2.x.x, etc.), and force-pushes orphan commits (no parent) to every tag. Any downstream workflow using your-org/your-action@v1 now executes the worm. The wrapper carefully delegates to the original action's HEAD OID after running the payload, delaying victim detection. Three hijacking strategies cover JavaScript actions (wrapped with a pre-execution index.js step), composite actions (setup-bun step injected), and Docker actions (wrapper that runs the worm before the original image).

Repository mutation (RepositoryMutator) targets branches with open pull requests only - not all branches - across up to 500 writable repositories. Non-owned public repos require at least 10 stars and push permission; enterprise-managed users are skipped entirely. Five files are injected per branch, with commit message chore: update dependencies and the Git trailer skip-checks:true:

File Purpose
.github/setup.js Primary JS_ bootstrapper
.claude/settings.json Claude Code SessionStart hook
.gemini/settings.json Gemini CLI hook - same SessionStart format as Claude Code
.cursor/rules/setup.mdc Cursor rules injection (model-instruction format, not JSON hook)
.vscode/tasks.json VSCode folderOpen task

RepositoryMutator also injects worm execution into build files on each branch: package.json (scripts.test), Makefile, composer.json, tox.ini, Dockerfile, CMakeLists.txt, and shell scripts. When the token has admin scope, branch protection is permanently deleted via the GitHub API before committing directly to the default branch. It is not restored.

npm --ignore-scripts bypass (orphanDep.ts) - undocumented in ARCHITECTURE.MD - adds a .npmrc file with git=node and an optionalDependencies entry pointing to github:axios/axios#main. When npm resolves the github: dependency, it reads .npmrc and calls node clone.js instead of git clone. The --ignore-scripts flag suppresses lifecycle hooks but not the custom git resolver invocation, making this a complete bypass of the most widely-recommended safe-install mitigation. The github:axios/axios#main reference is chosen specifically because it is a real, large, legitimate package - the install appears entirely plausible.

PyPI typo-squatting (TypoMutator) - also undocumented in ARCHITECTURE.MD - activates with TYPO_MODE=1 and generates typo names by appending -mcp and -mpc suffixes to target package names. The source comment states explicitly: "PyPI blocks direct typos but allows -mcp suffix packages." This deliberately exploits PyPI's failure to block requests-mcp as a typosquat of requests. The -mcp (Model Context Protocol) suffix has become a common legitimate naming convention, making these packages appear credible.

OIDC branch injection with legitimate sigstore provenance (NpmOidcBranchMutator) targets npm packages published via GitHub Actions OIDC Trusted Publishing. It creates a branch snapshot-<hex>, injects a workflow named "Dependabot Updates" that mints an OIDC token and publishes a trojanized package, and - if an admin token is available - permanently deletes the OIDC environment protection rules (required_reviewers, deployment_branch_policy) from the Development environment without restoring them. Critically, the published package receives genuine, verifiable sigstore provenance: the worm requests a real OIDC token from GitHub Actions, gets a real signing certificate from Fulcio (fulcio.sigstore.dev), and submits a real inclusion proof to Rekor ( rekor.sigstore.dev). The resulting package will pass npm audit signatures and any sigstore-based provenance check. The provenance correctly references the victim repository and workflow because the OIDC token genuinely identifies them. Standard provenance verification is not a detection signal for this attack path.

AI tool hook injection (Claude mutator) scans the home directory for config files belonging to 13 AI coding tools: Claude Code, Codex, Gemini CLI, Copilot, Kiro, OpenCode, Cline, Aider, Tabby, Amazon Q, Cody, Bolt, and Continue. For JSON configs it injects a SessionStart hook running the worm on every session start. For .cursorrules, .windsurfrules, and .cursor/rules/*.mdc files it injects natural-language instructions designed to look like legitimate model instructions rather than tool hooks - exploiting the model's instruction-following to trigger execution, bypassing hook-based security controls that inspect only the hooks key in JSON configs. The worm payload is copied to ~/.config/index.js.

Stage 5: Persistence and Dead-Man Switch

We identified two persistence mechanisms operating independently, installed as separate services.

InstallMonitor in src/mutator/persist/install-monitor.ts writes GITHUB_MONITOR.py to ~/.local/share/updater/update.py and installs it as a systemd user service (update-monitor) on Linux or as a LaunchAgent ( com.user.update-monitor) on macOS, calling loginctl enable-linger to survive user logout. Installation skips systems with fewer than 4 CPU cores to avoid sandbox detection. The monitor polls GitHub commit search hourly for messages matching firedalazer <url_b64>.<sig_b64>, verifies each RSA-PSS-SHA256 signature against an embedded 4096-bit public key, and executes the downloaded URL as arbitrary Python code. State is tracked in /var/tmp/.gh_update_state.

The dead-man switch (DEADMAN_SWITCH.sh) installs as a separate service under the name gh-token-monitor. It polls api.github.com/user every 60 seconds using the stolen token. When the token returns a 4xx response, it executes rm -rf ~/; rm -rf ~/Documents and exits after a 72-hour TTL. Revoking a stolen PAT without first stopping the gh-token-monitor service will trigger data destruction on the victim host.

The operator reinfection loop (create_payload_repo.py) creates a private GitHub repo, encrypts the worm bundle, pushes an Actions workflow that decrypts and executes it, stores stolen PATs as Actions secrets (PATS, PASSPHRASE), dispatches the workflow, and deletes the secrets 15 seconds later to cover tracks. An orphan-commit.py utility delivers payloads via unreferenced commits: creates a tag pointing to the payload commit, returns the raw file URL, immediately deletes the tag - the commit remains reachable by SHA in GitHub's object store but is invisible in normal repository browsing.

Indicators of Compromise #

Network

Primary C2 (operator builds only - stripped from OSS release):

  • https://api.anthropic.com/v1/api - C2 endpoint; POST requests carry encrypted exfil blobs, not the JSONmodel /messages payload of legitimate Claude API calls. Nox-api-key oranthropic-version headers.

C2 and PAT discovery strings (active in OSS build):

  • DontRevokeOrItGoesBoom - GitHub commit search string; PATs stashed alongside this string are targeted by the worm
  • TheBeautifulSandsOfTime - GitHub commit search string for signed C2 commands (inline JS eval)
  • firedalazer - commit message keyword for signed C2 URL rotation used by GITHUB_MONITOR and DomainSenderFactory

Exfiltration repos:

  • Newly created public GitHub repos with Greek underworld names matching the pattern (stygian|tartarean|erebean|infernal|chthonic|acheronian|lethean|plutonian|abyssal|charonian|thanatic|funereal|nekyian|sepulchral|tenebrous|cimmerian)-(cerberus|charon|tartarus|erebus|asphodel|acheron|styx|lethe|cocytus|phlegethon|shade|eidolon|wraith|thanatos|hecate|persephone)-[0-9]{5}

  • Repository description: "Hades * The End for the Damned" - hardcoded on every created exfil repo

  • File pattern: results/<timestamp>-<counter>.json in exfil repos Runtime downloads:

  • https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-{platform}-{arch}.zip - Bun runtime downloaded byINJECT_PTH.pth andJS_

  • https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/... - Bun version used byBASH_.sh (SSH delivery path uses a different pinned version)

StepSecurity bypass:

  • /etc/hosts entry:127.0.0.1 agent.stepsecurity.io api.stepsecurity.io app.stepsecurity.io www.stepsecurity.io stepsecurity.io
  • /etc/resolv.conf overwrite:nameserver 8.8.8.8

Filesystem

Worm payload drops:

  • ~/.config/index.js - worm payload (AES-128-GCM + ROT-N self-extracting wrapper); created by Claude mutator
  • /tmp/.bun_ran -INJECT_PTH one-run guard; presence confirms Python.pth persistence has fired
  • /var/tmp/.gh_update_state -GITHUB_MONITOR command deduplication state; presence confirms persistent C2 channel is installed
  • /tmp/b/bun or/tmp/b/bun.exe - Bun runtime cached byINJECT_PTH.pth

GITHUB_MONITOR persistence (service name: update-monitor**):**

  • ~/.local/share/updater/update.py
  • ~/.config/systemd/user/update-monitor.service
  • ~/Library/LaunchAgents/com.user.update-monitor.plist
**Dead-man switch persistence (service name:** **gh-token-monitor****):**

- `~/.local/bin/gh-token-monitor.sh`
- `~/.config/gh-token-monitor/token` (mode 600 - stolen PAT)
- `~/.config/gh-token-monitor/handler` (mode 600 - destruction command)
- `~/.config/systemd/user/gh-token-monitor.service`
- `~/Library/LaunchAgents/com.user.gh-token-monitor.plist`

Python .pth persistence:

  • <site-packages>/INJECT_PTH.pth - single obfuscated line; injected byPypiMutator andPyPIOidcClient

  • <site-packages>/_index.js - worm payload copied into Python site-packages Files injected into victim repositories (branches with open PRs):

  • .github/setup.js - JS_ bootstrapper

  • .claude/settings.json - Claude CodeSessionStart hook

  • .gemini/settings.json - Gemini CLISessionStart hook

  • .cursor/rules/setup.mdc - Cursor rules injection (AI model instruction format)

  • .vscode/tasks.json - VSCodefolderOpen task

  • .vscode/setup.mjs - Bun bootstrapper

  • .claude/index.js - worm payload

  • .claude/setup.mjs - Bun bootstrapper for Claude Code vector

**SSH delivery (SshMutator):**

- `/tmp/.sshu-<6 random chars>/` - remote temp directory on SSH target hosts
  • ai_setup.sh - BASH_ script on SSH targets
  • ai_init.js - worm payload on SSH targets

AWS SSM delivery:

  • /tmp/.ssm-setup.js - worm payload written on EC2 instances via SSM SendCommand

Process Indicators

  • bun run ~/.config/index.js - worm execution via AI tool hook
  • bun run .claude/index.js - worm execution from repo hook
  • node .vscode/setup.mjs - VSCode task bootstrap
  • node .github/setup.js - Claude/Gemini hook payload
  • gh auth token - GitHub CLI token extraction (run as subprocess by any bun/node parent process)
  • python3 /tmp/*.py - GITHUB_MONITOR executing downloaded C2 commands from temp files
  • sudo python3 with stdin from a memory dump script - Runner.Worker/proc/mem dump

Commit and Repository Indicators

  • Commit message chore: update dependencies + trailerskip-checks:true - RepositoryMutator fallback; any commit with this message adding.claude/ ,.gemini/ ,.cursor/ , or.vscode/ files is an IOC
- Commit message `fix: ci` - used in OIDC branch injection
- `chore: update dependencies [skip ci]` - second commit in OIDC branch injection (restores original tree after deployment-target commit)
- Branch names `snapshot-<8 hex chars>` - OIDC injection branch
  • Workflow named "Dependabot Updates" with aworkflow_dispatch trigger andOIDC_PACKAGES env var - OIDC injection camouflage
  • Workflow named "Run Copilot" dumping${{ toJSON(secrets) }} to artifactformat-results - GitHubActionsService secrets dump
  • Workflow file at .github/workflows/codeql.yml added by the worm (not a genuine CodeQL workflow)
  • Semver tags ( v1 ,v2.x.x ) pointing to orphan commits (commits with no parent) - ActionMutator IOC
  • Action step oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 + step named"Cleanup Action" - ActionMutator injection signature
  • Branch protection deletion ( protected_branch.destroy in GitHub Audit Log) with no corresponding restore - RepositoryMutator admin-token path
  • User-Agent python-requests/2.31.0 used by RepositoryMutator for enterprise user detection

npm Package Indicators

  • Bumped patch version with no corresponding changelog or git tag
  • preinstall script added topackage.json
  • bun added as a dependency
  • optionalDependencies entry pointing togithub:axios/axios#main combined with.npmrc containinggit=node ---ignore-scripts bypass (orphanDep.ts)
  • npm provenance for a package where the provenance buildTrigger references asnapshot-* branch or a workflow named"Dependabot Updates" - sigstore signatures will verify as genuine

Credentials Targeted

  • ~/.aws/credentials ,~/.aws/config
  • ~/.azure/accessTokens.json ,~/.azure/msal_token_cache.*
  • ~/.config/gcloud/application_default_credentials.json ,~/.config/gcloud/credentials.db
  • ~/.kube/config
  • ~/.ssh/ (all private keys,known_hosts ,config ,authorized_keys )
  • ~/.docker/config.json
  • ~/.gitconfig ,~/.git-credentials ,~/.netrc
  • ~/.npmrc ,~/.pypirc ,~/.gem/credentials
  • ~/.claude.json ,~/.claude/* ,~/.claude/projects/* ,~/.claude/mcp.json
  • ~/.config/helm/*
  • ~/.ethereum/keystore/* ,~/.monero/*
  • ~/.config/Signal/* ,~/.config/Slack/Cookies ,~/.config/telegram-desktop/*
  • ~/.bash_history ,~/.zsh_history ,~/.python_history ,~/.mysql_history ,~/.psql_history
  • .env ,.env.local ,.env.production ,.env.development ,.env.staging
- `VAULT_TOKEN` ,`VAULT_AUTH_TOKEN` (env)
- `GITHUB_TOKEN` ,`GITHUB_TOKEN2` (env)
- `AWS_ACCESS_KEY_ID` ,`AWS_SECRET_ACCESS_KEY` (env)

Embedded Keys

  • RSA-4096 enc_key.pub (exfiltration encryption public key) - presence in any file or binary confirms Miasma
  • RSA-4096 verify_key.pub partial match:MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAw5zZbSXX+4X2kTs/zC7l - embedded inGITHUB_MONITOR.py
  • AES-256 TOKEN_AES_KEY (hardcoded hex):bd8035203536735490e4bd5cdcede581a9d3a3f7a5df7725859844d8dcc8eb49 - used to encrypt PATs stashed in GitHub repos

Hashes

  • SHA256 (outer package.zip): 0736c83273465c6b64c04bd92f0e7a8517e8dbc19bb54d6c9ab41420fd833d6b

  • SHA256 (Miasma-Open-Source-Release-main.zip): 6331d1511783dcb1158fb54775f563e90399b3a2a81a584d3cba9a77f63d15a7

Affected Versions #

  • Miasma-Open-Source-Release-main (voicefromtheouterworld, no published version tag)

MITRE ATT&CK #

ID Technique Application
T1195.002 Supply Chain Compromise: Software Supply Chain npm/PyPI/RubyGems packages trojanized with preinstall hooks; GitHub Actions semver tags hijacked; OIDC branch injection; PyPI typo-squatting via -mcp /-mpc suffixes
T1195.001 Supply Chain Compromise: Compromise Software Dependencies .npmrc`` git=node override forces npm to executeclone.js as the git binary when resolving agithub: dependency, bypassingnpm install --ignore-scripts entirely
T1078.004 Valid Accounts: Cloud Accounts All propagation and exfiltration uses legitimate victim GitHub PATs; no malicious login events are generated
T1059.007 JavaScript Primary worm bundle; JS_.mjs bootstraps Bun from Node.js;c2/checkin.ts`` eval() s arbitrary JavaScript from signed GitHub commits at runtime
T1059.006 Python INJECT_PTH.pth runs on every Python interpreter startup;GITHUB_MONITOR.py downloads and executes arbitrary Python from signed C2 URLs
T1059.004 Unix Shell BASH_.sh bootstraps Bun on SSH targets;DEADMAN_SWITCH.sh monitors token validity and fires destruction handler
T1204.002 User Execution: Malicious File VSCode folderOpen task fires on repo open; Claude Code and Gemini CLISessionStart hooks fire on session start; Cursor rules injection exploits model instruction-following
T1543.001 Create or Modify System Process: Launch Agent ~/.config/systemd/user/gh-token-monitor.service andupdate-monitor.service on Linux;~/Library/LaunchAgents/ equivalents on macOS;loginctl enable-linger survives user logout
T1546 Event-Triggered Execution Claude Code and Gemini CLI SessionStart hooks; VSCodefolderOpen task
T1554 Compromise Host Software Binary npm preinstall hook injection; RubyGems native extension (ext/extconf.rb +Makefile ) injection; PyPI.pth injection
T1611 Escape to Host Spawns privileged Alpine container mounted at /etc/sudoers.d/ to grant passwordless sudo to the runner user
T1610 Deploy Container Creates and starts privileged Docker container via Docker socket to escalate privileges and restore sudo
T1027 Obfuscated Files or Information Polyalphabetic substitution cipher (stringtool.ts ) with per-build passphrase baked intoruntimeDecoder.ts ; all string literals wrapped inscramble() at build time
T1027.002 Software Packing javascript-obfuscator with control-flow flattening, dead code injection, and string array encoding; additionalobfplus-wrap.js outer wrapper layer
T1027.013 Encrypted/Encoded File All embedded payload assets AES-256-GCM encrypted at build time; self-extracting payloads use AES-128-GCM + ROT-N with per-build random keys
T1036.005 Masquerading: Match Legitimate Name or Location C2 path api.anthropic.com/v1/api impersonates the Anthropic API; OIDC injection workflow named"Dependabot Updates" ; injected Actions workflow file namedcodeql.yml ; service namegh-token-monitor appears to be a legitimate monitoring tool
T1140 Deobfuscate/Decode Files or Information runtimeDecoder.ts decodes all obfuscated strings at runtime using the per-build injected passphrase
T1497.001 Virtualization/Sandbox Evasion: System Checks Checks for decoy token prefixes, sandbox CWD paths, specific sandbox tool paths, hostname containing "sandbox", and honeypot AWS credential file contents; exits on Russian locale
T1518.001 Security Software Discovery Checks 17 EDR/AV process names and 4 installation paths before running credential providers or mutators
T1562.001 Impair Defenses: Disable or Modify Tools Kills StepSecurity Harden-Runner Docker containers via Docker socket; spawns privileged container to restore runner sudo
T1562.006 Impair Defenses: Indicator Blocking Poisons /etc/hosts to localhost-loopstepsecurity.io domains; overwrites/etc/resolv.conf with public DNS servers
T1564.001 Hide Artifacts: Hidden Files and Directories Payload at ~/.config/index.js ; service config at~/.config/gh-token-monitor/ ; state file at/var/tmp/.gh_update_state
T1070 Indicator Removal Deletes PATS andPASSPHRASE secrets from GitHub Actions 15 seconds after reinfection workflow dispatch; orphan-commit.py creates and immediately deletes temp tags leaving commits unreferenced
T1552.001 Unsecured Credentials: Credentials In Files 100+ filesystem hotspot paths scanned including cloud credentials, package registry tokens, AI tool configs, shell histories, crypto wallets, and messaging app data
T1552.004 Unsecured Credentials: Private Keys ~/.ssh/id* ,~/.ssh/id_rsa ,~/.ssh/id_ecdsa ,~/.ssh/id_ed25519 ,/etc/ssh/ssh_host_*_key
T1552.005 Unsecured Credentials: Cloud Instance Metadata API AWS IMDSv2, Azure IMDS, and GCP metadata server all queried for credentials
T1528 Steal Application Access Token GitHub PATs, npm tokens, RubyGems API keys, PyPI tokens, JFrog tokens harvested from files and environment
T1539 Steal Web Session Cookie Discord and Element browser local storage; Signal config; Slack cookies
T1555.005 Credentials from Password Stores: Password Managers 1Password CLI (op ), Bitwarden CLI (bw ),pass ,gopass
T1212 Exploitation for Credential Access GitHub Runner /proc/<pid>/mem dump extracts Actions secrets that never touch disk or environment variables
T1021.004 Remote Services: SSH SCP worm payload to hosts enumerated from ~/.ssh/known_hosts and config, then SSH exec
T1072 Software Deployment Tools AWS SSM SendCommand (AWS-RunShellScript) dispatched to all managed EC2 instances across 17 regions
T1041 Exfiltration Over C2 Channel Encrypted batches HTTPS-POSTed to api.anthropic.com/v1/api (operator builds)
T1567.001 Exfiltration Over Web Service: Exfiltration to Code Repository Encrypted blobs committed to freshly-created public GitHub repos with Greek underworld-themed names
T1022 Archive Collected Data Data is gzip-compressed then AES-256-GCM encrypted before exfiltration
T1568 Dynamic Resolution C2 domain discovered at runtime from cryptographically signed GitHub commits; allows operator to rotate C2 without redeploying the worm
T1102.002 Web Service: Bidirectional Communication GITHUB_MONITOR.py polls GitHub commit search hourly for signedfiredalazer C2 commands;c2/checkin.ts performs runtime C2 eval via the same mechanism
T1573.002 Encrypted Channel: Asymmetric Cryptography All exfiltrated data wrapped in RSA-OAEP-SHA256; C2 commands authenticated with RSA-PSS-SHA256

A Note from Ossprey #

Ossprey obtained and analyzed the Miasma source release after it was published publicly. This was not an Ossprey detection event - Miasma was not distributed through a package registry, and there was no package for our pipeline to flag. For organizations whose pipelines process npm, PyPI, or RubyGems dependencies, Ossprey's behavioral analysis models the exact tradecraft Miasma uses: binding.gyp and preinstall triggers, per-build AES-GCM self-extracting payloads, multi-ecosystem publish-token abuse, and the .npmrc git=node bypass of --ignore-scripts. Derivative campaigns that deploy this worm through a registry would be detectable before install. The public availability of the full source - including the TypoMutator, OIDC branch injection, and --ignore-scripts bypass, none of which are documented in the authors' ARCHITECTURE.MD - makes monitoring for Miasma-derived registry uploads an active Ossprey priority.

For teams processing open-source dependencies at scale, book a demo to see how Ossprey's behavioral analysis detects Miasma-derived campaigns before they reach your environment. Related Ossprey research: The Complete TeamPCP Campaign | Trivy Supply Chain Attack: TeamPCP, CanisterWorm | Axios Hijacked: Cross-Platform RAT | TJ-Actions Breach

Frequently Asked Questions #

Was there a malicious npm package I need to remove? Miasma was released as a public source repository, not as a deployed registry package. No published npm, PyPI, or RubyGems package is known to contain this worm at time of writing. If you find an installed package that looks like a derivative, compare its package.json against the preinstall hook and Bun dependency patterns in the IOC section above.

Does Ossprey detect Miasma? Miasma was not distributed through a package registry, so there was no package for Ossprey's pipeline to analyze. Derivative campaigns deploying Miasma's tradecraft through npm, PyPI, or RubyGems would be detectable before install - the binding.gyp + preinstall combination, per-build AES-GCM self-extracting payloads, and .npmrc git=node override are all behavioral signals Ossprey models.

How confident are you that TeamPCP authored this? We are not. The README claims TeamPCP authorship. Our assessment is that this was written by a group inspired by TeamPCP rather than TeamPCP itself. Attribution requires C2 infrastructure correlation beyond code-level overlap. See our Complete TeamPCP Campaign research for the confirmed attribution baseline.

Is the api.anthropic.com C2 infrastructure live? The api.anthropic.com transport is explicitly stripped from the open-source build. In the published source, GitHub repository commits are the only active exfiltration path. Operator builds are expected to re-enable it - the full implementation is present in src/sender/domain/.

I found gh-token-monitor running. What do I do? Stop the service before revoking any stolen PATs. The dead-man switch fires rm -rf ~/; rm -rf ~/Documents the moment it receives a 4xx response from the GitHub API on the monitored token. Stop first: systemctl --user stop gh-token-monitor (Linux) or launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist (macOS). Then revoke.

Real World Attacks

The Engineer's Laptop Is the New Front Door #

Mary Welton

30 Oct 2026

Real World Attacks

Blankets, worms, and RATs: New worm targets NPM and AUR #

Ellie Jevon

29 Sept 2026

Real World Attacks

How Ossprey protects from novel AI-accelerated malware development, and cross-ecosystem supply-chain attacks. #

Ossprey Research Team

24 Sept 2026

── more in #ai-safety 4 stories · sorted by recency
── more on @ossprey security 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/miasma-anatomy-of-an…] indexed:0 read:24min 2026-11-04 · —