Repository Used: https://github.com/PedroTortoriello/Shai-Hulud-Open-Source Last known commit: da10861 — "Shai-Hulud: A Gift From TeamPCP"

The analysis of the repository shared by TeamPCP was mainly done statically as well as some other elements from here confirmed dynamically using a VM hosted on a VPS I own.

Goes without saying but don’t try doing this yourself unless you know what you are doing

Network IoCs

C2 and Exfiltration Endpoints

Indicator Type Notes/Context
https://git-tanstack.com:443/router C2 domain Primary exfiltration endpoint. Healthcheck expects HTTP 400 or 404.
https://api.github.com/user/repos GitHub API Used to create public exfil repositories
https://api.github.com/user GitHub API Polled by deadman monitor every 60s
https://api.github.com/user/orgs GitHub API Org scope check during token validation
https://api.github.com/graphql GitHub API Branch mutation via createCommitOnBranch
https://api.github.com/search/commits?q=IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner GitHub API Token broker commit search
https://api.github.com/search/commits?q=thebeautifulmarchoftime%20 GitHub API Signed fallback C2 domain discovery

npm Registry Endpoints

Indicator Type Notes/Context
https://registry.npmjs.org/-/npm/v1/tokens npm API Token inventory and validation
https://registry.npmjs.org/-/whoami npm API Token identity check
https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/ npm API OIDC trusted publishing attack path

Malware Loader Download

Indicator Type Notes/Context
https://github.com/oven-sh/bun/releases/download/bun-v1.3.13/ Download Bun runtime fetched by all loader variants(bash and python loaders)

Cloud Metadata Endpoints

Indicator Type Notes
http://169.254.169.254/latest/ AWS IMDS EC2 IMDSv2 credential harvesting
http://169.254.170.2 AWS ECS ECS container credential endpoint
http://127.0.0.1:8200 HashiCorp Vault Default Vault address (overridden by VAULT_ADDR)

Note about the hashicorp vault, while thats the default/hardcoded one; you’ll have to refer to your own if its been configured with the environment variable

Sigstore (npm OIDC / OpenSearch Attack Path)

Indicator Type Notes
https://fulcio.sigstore.dev/api/v2/signingCert Sigstore Used to generate fraudulent SLSA provenance
https://rekor.sigstore.dev/api/v1/log/entries Sigstore Transparency log submission for fake provenance

String / Commit Message IoCs

These strings may appear in GitHub commit messages, npm package contents, or repository descriptions.

I cant comment as to how well or high confidence of indicators these are and so take them all with a grain of salt and excersize your experience/best judgement.

String Location / Context
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner GitHub commit messages for token broker C2 channel
thebeautifulmarchoftime GitHub commit search query for signed fallback C2 discovery
thebeautifulsnadsoftime Regex in fallback parser for signature verification string
Shai-Hulud: Here We Go Again GitHub repository description on exfil repos
chore: update dependencies Commit message used for repository backdoor commits
Co-authored-by: claude <claude@users.noreply.github.com> Git trailer on backdoor commits for camouflage and easy evasion
github-advanced-security[bot] Committer identity used when creating malicious workflow
Run Copilot Malicious GitHub Actions workflow name
format-results GitHub Actions artifact name containing dumped secrets
format-results.txt File within the artifact containing toJSON(secrets)
dependabot/github_actions/format/setup-formatter Malicious branch name which mimics Dependabot
.github/workflows/codeql_analysis.yml Malicious workflow file path
release-drafter.yml Targeted workflow name in OpenSearch OIDC preflight
/opensearch-js Targeted GitHub repository in OIDC preflight

The dependebot masquaring/mimicking I thought was pretty cool/funny and interesting to see. I can overall see how any of these elements fly so well under the radar.

File and Path IoCs

Deadman Monitor

These files indicate an active persistence installation. My suggestions would be do not revoke associated GitHub tokens while these files/services are present and the host is online.

Best approach would be to take the host offline, or quarantine it in some way, kill the services/background persistence’s and then rotate. That’s only because otherwise you risk having your file system blown up

Path Platform/OS Description
~/.local/bin/gh-token-monitor.sh Linux / macOS Monitor script
~/.config/gh-token-monitor/token Linux / macOS Stored GitHub token
~/.config/gh-token-monitor/handler Linux / macOS Stored handler (rm -rf ~/)
~/Library/LaunchAgents/com.user.gh-token-monitor.plist macOS LaunchAgent persistence
~/.config/systemd/user/gh-token-monitor.service Linux systemd user service

Repository Autorun Backdoor Files

These files are planted via GitHub branch commits. Any repository containing these should be treated as backdoored.

Path Notes/Context
.vscode/tasks.json Contains "runOn": "folderOpen" task executing node .claude/setup.mjs
.vscode/setup.mjs Bun downloader/loader
.claude/settings.json Claude Code SessionStart hook executing node .vscode/setup.mjs
.claude/setup.mjs Bun downloader/loader
.claude/opensearch_init.js Malware bundle copy in repository

npm Package Injection Files

These files in npm package roots indicate a backdoored tarball.

File Notes/Context
setup.mjs Bun downloader; triggers malware on preinstall
opensearch_init.js Malware bundle (named by SCRIPT_NAME in source)
ai_init.js Expected entry point name in config.mjs (it looks like there is a naming mismatch present)
router_runtime.js Expected entry point name in Python loader
package.json “name”: “voicefromtheouterworld” Note that this is the builder’s package name, not the backdoored package name

Lock File

Only a single lock file is created as it seems so it should be simple enough to pick up and detect on the relevant one.

Path Notes
<TMPDIR>/tmp.ts018051808.lock Process lock file; presence indicates active or recent execution

npm Package Indicators

The following entries here just are patterns I have noticed that may or may not be present in a normal npm package.

Plenty of tools exist to detect and pickup compromised packages but some manual things include:

  • preinstall script set to node setup.mjs (or any Bun-loader variant) with no other scripts present
  • Patch version bump (x.y.N → x.y.N+1) with no substantive code changes
  • New root-level files: setup.mjs, opensearch_init.js, ai_init.js, router_runtime.js
  • Optional dependency @opensearch/setup pointing to github:opensearch-project/opensearch-js#d446803f4c3bc116263faa3499a1d3f95b2825de
  • Package published using a token with bypass_2fa: true
  • npm client User-Agent: npm/11.13.1 node/v24.10.0 <platform> <arch> workspaces/false
  • TLS certificate validation disabled for registry PUT requests (detectable via network inspection)

GitHub Repository Indicators

Description

Public repositories with description: Shai-Hulud: Here We Go Again

Naming Pattern

Format: <adjective>-<noun>-<0-999>

Adjectives used:
sardaukar, mentat, fremen, atreides, harkonnen, gesserit, prescient, fedaykin, tleilaxu, siridar, kanly, sayyadina, ghola, powindah, prana, kralizec

Nouns used:
sandworm, ornithopter, heighliner, stillsuit, lasgun, sietch, melange, thumper, navigator, fedaykin, futar, phibian, slig, cogitor, laza, ghola

Result File Path

results/results-<unix-timestamp>-<counter>.json
Files >30 MB will be split with .p1, .p2, etc. suffixes.

Asset SHA256 Hashes

These hashes were generated by cloning the repostiory and running the powershell native function to generate the SHA256. These files may not have the same hashes but in case they do, here they are : )

File SHA256
src/assets/config.mjs 14B3CD76031FBDC8A746E53D82B38D8957FA60F57BE7B805754574709E213BE6
src/assets/BASH_LOADER.sh A04331F51CDF7F64B4E3C86C482E2DCCFFAD89A16BF4B98CB0848C9CC23F3D53
src/assets/PYTHON_LOADER.py 71A4A4FCA98C8E6DD19BE3D90058D9D0ADEE191764D19629DF602BEB21EF2CF3
src/assets/DEADMAN_SWITCH.sh D4B7F880EA824BC79169E2EB5DD33AB8742049DC588E812AC9F1D9FCF09FA9BF
src/assets/workflow.yml 516AE3BB740DACE53F31FF53B2D97394157043F9598E74E23EB8E62F47B3B64C
src/assets/task.json F1E2354937735939340793AAD37BEF7C32C9122BFD9B9E32B135916F8FDCF90B
src/assets/enc_key.pub 61C984F1D5439154ECEABC726554D23606D711EA03F564879DFCC409DF716744
src/assets/verify_key.pub 38607C8748C6199CB571BA5339B005B48DF3D12B28AD0E444758A7E8BFF58EF2

Process / Host Behavioral Indicators

Indicator Notes and Thoughts
sudo python3 pipe with tr -d '\0' | grep -aoE GitHub Actions runner memory scraping
/proc/*/mem access where Runner.Worker is the target process Memory read in GitHub Actions Linux runners
Process with __DAEMONIZED=1 in environment spawned by npm lifecycle Daemonization during npm install
Lock file present at <TMPDIR>/tmp.ts018051808.lock Active or recent malware execution
systemd user service named gh-token-monitor Deadman monitor installed
LaunchAgent with label com.user.gh-token-monitor Deadman monitor on macOS

Credential Regex Patterns (from Malware Source)

These are the exact patterns the malware uses to extract tokens from harvested files. One thing to do is to search for instances of the following regex being ran within your SIEM OR instead if you cant beat them; join them and use these defensively to map out your credential/secrets exposure

# GitHub PAT / OAuth
gh[op]_[A-Za-z0-9]{36}

# GitHub Actions token (JWT form)
ghs_\d+_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+

# GitHub Actions token (legacy form)
ghs_[A-Za-z0-9]{36,}

# npm automation token
npm_[A-Za-z0-9]{36,}

# Kubernetes JWT
eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-\.]+

# AWS Access Key
AKIA[0-9A-Z]{16}

# HashiCorp Vault token
hvs\.[A-Za-z0-9_-]{24,}

# Stripe key
(sk|pk)_(test|live)_[0-9a-zA-Z]{24,}

# Slack token
xox[baprs]-[0-9a-zA-Z\-]{10,}

# Twilio key
SK[0-9a-f]{32}

# Private key header
-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----

# Docker auth blob
"auth":\s*"[A-Za-z0-9+\/=]{20,}"

Quick Reference: Potential Containment Checklist to Consider

Having worked on a few IR cases involving the supply chain compromise, I had time to think about how the containment could be handled but having observed the source code; that resulted in a new set of insights I thought to combine with the IR context.

This list is by no means comprehensive, but is a good starting point to handle the infection in the first 30 minutes to an hour of compromise

[ ] Disconnect host from network BEFORE revoking any tokens to prevent the failsafe from rm -rf the whole filesystem
[ ] Check for gh-token-monitor service/files (paths above)
[ ] Stop and remove deadman service if present
[ ] Revoke GitHub tokens from a CLEAN, separate machine
[ ] Audit npm packages you maintain for backdoor indicators
[ ] Audit GitHub orgs for malicious branch / workflow / artifact
[ ] Audit GitHub account for Dune-named public repos
[ ] Rotate: npm, GitHub, AWS, K8s, Vault, GCP, Azure, SSH, Docker, Slack, Stripe, Twilio, DB
[ ] Review GitHub Actions workflow run deletion logs (malware attempts cleanup)
[ ] Check CI/CD logs for runner memory access patterns

Hope this has been a worthy and helpful reference. Thanks teamPCP for the dump : )