Needs Review into a high-confidence assertion failure: package install reached a temporary bun.exe from npm cache, escalated through sudo, and spawned python3.10 in a process-memory injection pattern. The kernel-level lineage is what made the failing profiles separable from the rest of the sweep.The handoff from observation to detection to check is the point of the post. Telemetry says what happened. Detections say which behaviors are security-relevant. Checks say what the developer should investigate next. The TanStack sweep is a clean illustration because the same three-layer pipeline runs on every profile, and eight of them landed on the failure side of the line.
What Garnet observed
Method: Comparative profiling of the TanStack package set with the Jibril v2.12.0 sensor on GitHub Actions runners. Across 42 package specs, the sweep rendered the same assertion contract into .garnet/profile.lock.json.
The attack chain
Execution lineage
Run 25697800810 · jadoonf/npm-analysis-feed
Garnet: TanStack Matrix
data/profiles/25697800810.json or API credentials.This embed is run 25697800810 — npm install @tanstack/react-router@1.169.8 in Garnet: TanStack Matrix on jadoonf/npm-analysis-feed. The same ancestry showed up on each of the eight failing TanStack package profiles below. Follow the tree: the package install does not stop at fetching metadata. It executes a transient Bun binary from a path inside npm's cache, then drops to dash, then sudo, then python3.10. The interesting child process never appears in the workflow's checked-in source — it comes from a path the dependency install staged at resolve time.
npm install
-> node
-> dash
-> bun.exe from npm cache
-> dash
-> sudo
-> python3.10Garnet observed the bun.exe ancestor running from cache paths in the shape of /home/runner/.npm/_cacache/tmp/git-clone.../node_modules/bun/bin/bun.exe. The same process family reached git-tanstack.com alongside expected destinations (api.github.com, codeload.github.com, registry.npmjs.org, local DNS, and the cloud metadata address 169.254.169.254). Credential-adjacent file access and hidden executable activity appeared on the same ancestry, raising confidence for the downstream check.
The eight profiles that landed on assertion failure:
@tanstack/react-router@1.169.8(the install shown in the embed)@tanstack/router-ssr-query-core@1.168.6@tanstack/router-utils@1.161.14@tanstack/router-vite-plugin@1.166.56@tanstack/solid-router-devtools@1.166.19@tanstack/solid-router-ssr-query@1.166.18@tanstack/solid-start-client@1.166.53@tanstack/vue-start-client@1.166.49
From observation to detection to check
Detections are the interpretation layer between raw telemetry and product controls. On these profiles, the key detection was process injection through /proc memory. Supporting detections were credential-adjacent access, hidden executable activity, unusual execution location, and first-seen egress from the same suspicious process family.
Checks are the product layer. They convert detections into a verdict the developer can act on.
| Product check | Input evidence | Result | What it means for the developer |
|---|---|---|---|
| Proc-memory injection | Package install reached bun.exe → sudo → python3.10 and matched the injection pattern | Assertion failed | High-confidence alert; review the package version and install script before merging |
| First-seen egress | Same process family reached git-tanstack.com outside a quiet registry-only shape | Needs Review | Review the outbound destination and ancestry before accepting the behavior |
| Self-deleting binary | No self-deleting binary behavior observed in these profiles | Passed | Do not claim a dropper erased itself when the evidence does not show that |
This layered model is why the eight failing profiles separated cleanly from the rest of the 42-package sweep. Most profiles produced quiet ancestry: npm resolving dependencies, registry traffic, no privileged child processes. The eight failures share the same bun.exe → sudo → python3.10 shape.
What changes between a clean profile and a failing one
A clean TanStack profile in the same sweep reaches registry.npmjs.org and friends, never escalates through sudo, and never spawns Python from a transient cache path. The installs use the same command — the behavioral split appears only at runtime.
| Clean profiles in the sweep | The eight failing profiles | |
|---|---|---|
| Process lineage | npm install → node and stop | npm install → node → dash → bun.exe → dash → sudo → python3.10 |
| Executable location | Resolved binaries from project paths | bun.exe from ~/.npm/_cacache/tmp/git-clone.../node_modules/bun/bin/bun.exe |
| Network (observed) | Registry + GitHub + local DNS | Registry + GitHub + git-tanstack.com + cloud metadata 169.254.169.254 |
| File / execution signals | None | Credential-adjacent access, hidden executable activity on the same ancestry |
| Assertion result | Passed or Needs Review | Proc-memory injection control failed (high confidence) |
hidden_elf_exec is bad. The checked-in .garnet/profile.lock.json carries the interpreted assertion contract — observation became detection, and the failing detections became one actionable check per profile.What the developer sees on the PR
The PR comment leads with the check result, not the detection ID. The evidence drawer keeps the raw record one click away.
Garnet Security — High-confidence alert
8 TanStack package profiles launched python3.10 through sudo during
package install.
Process chain:
npm install
-> node
-> bun.exe from npm cache
-> sudo
-> python3.10
Why this assertion failed:
- The behavior matched Garnet's proc-memory injection control.
- The same process family made outbound connections to git-tanstack.com.
- Credential-adjacent access appeared on the same ancestry.
Recommended action:
Review this dependency change before merging.
If this behavior is expected, isolate it in a job without publish tokens
or repository secrets.The headline stays clear. Assertion IDs, detection metadata, process trees, peer lists, timestamps, and links to the full Garnet run stay available in the expanded drawer for anyone reproducing the result.
Real-world impact
TanStack ships pieces that land deep in front-end and meta-framework toolchains — routers, devtools, start clients. A workflow that installs any one of them without a pinned, profile-checked version inherits whatever the resolve graph produces on that day. The eight profiles in this sweep are a reminder that the runtime split between a quiet install and a sudo → python3.10 install is invisible to lockfiles and static scanners; it only resolves when the install actually executes against an instrumented runner.
This is the same trust boundary recorded in earlier field notes — Axios, Trivy, Checkmarx KICS, LiteLLM, and Telnyx. Different ecosystems, the same blind spot: untrusted install code runs in CI with full secrets access, and only kernel-level lineage separates the eight failing profiles from the 34 quiet ones.
Explore the run profile above, or start observing your own workflows with Garnet.