trail. about

What the code actually did

Trail installs npm packages in a disposable sandbox and records every file they touch, every process they start and every connection they open, then attributes each action to the code that caused it.

What this is

Each package on the feed is installed inside a fresh, throwaway container. The install runs with scripts enabled while the system calls it makes are recorded. The package is then loaded the way a consumer would load it, and recorded again.

Every recorded action is tied back to the process that produced it, so the package's own behaviour is separated from the ordinary noise of an npm install. npm reads your .npmrc on every install; that is npm doing its job, not a finding.

Trail is a triage tool rather than a classifier. Its job is to surface behaviour worth a human's attention, not to pass final judgment.

Two engines

Dynamic

Behaviour in a sandbox

The package is installed and imported inside an isolated container while its system calls are traced. Files read and written, processes spawned, permissions changed, sockets opened.

Events are correlated as well as counted. A file made executable and then run, or a secret read and then compressed into an archive named after it, means something that neither event means alone.

This engine sets the verdict.

Static

Contents of the tarball

The published archive is examined without being run. Rule matches against the source, the capabilities the code holds, and the package metadata as the registry reports it.

Every file also gets a SHA 256 and an entropy score, which reveals packed, compressed or encrypted content sitting inside an otherwise readable package.

This engine adds context to a verdict it does not set.

What a finding looks like

polymarket-stake-mathss@3.5.2 is two kilobytes of readable JavaScript with one postinstall hook. This is what the sandbox watched it do.

polymarket-stake-mathss v3.5.2 · 2 KB REVIEW
Accessed by package
/root/.ssh/id_rsa /root/.aws/credentials /root/.config/gh/hosts.yml /root/.docker/config.json /root/.electrum/wallets/default_wallet and others
Archives written
…/credentials ssh ssh.gz …/credentials aws aws.gz …/credentials gh token.gz and others
Executed by package
/usr/bin/tar/usr/bin/gzip
Package connections
216.198.79.1:44364.29.17.1:443

It opened credential file after credential file, compressed each one into a temporary directory, named every archive after whatever it had just taken, then opened outbound connections. The whole sequence took ten and a half seconds.

Read those rows one at a time and each is unremarkable. Packages read files. Build tools run gzip. Installers write to temporary directories. What makes this a finding is the order the actions happened in, which is visible only while the code is running.

Nothing was taken. The container holds nothing real and is destroyed moments later, so what the package collected was a set of files that exist nowhere else.

How a package moves through it

  1. Fresh sandboxEvery package starts from an identical, pristine container image. Nothing persists between runs.
  2. Instrumented installThe install runs with scripts enabled under full system call tracing, isolated from anything real.
  3. ImportThe package is then loaded as a consumer would load it and traced separately, so payloads that wait for import do not get a free pass.
  4. AttributionEach action is tied to the process tree that produced it: package code, npm itself, or build tooling.
  5. CorrelationEvents that mean little apart are joined into findings that mean something together.

What gets flagged

credentials read, then archived payloads dropped and executed connections outside the registry known exfiltration services filesystem reconnaissance temporary file snooping files written outside the project high entropy files

The feed has already surfaced live threats, among them dependency confusion probes beaconing to out of band callback services, and a package encoding host details inside DNS queries.

Reading the feed

REVIEW

Behaviour a human should look at. A lead, not a conviction.

CLEAN

Nothing notable observed. Behaviour outside the observation window is out of scope.

Two verdicts, deliberately. A third tier invites arguments about which shade something is instead of whether to look at it. Where a run was cut short, the result is REVIEW, because no coverage is not the same as nothing found.

If the registry pulls a package while it is being analysed, it drops off the feed entirely. Security holdings are the exception: npm replaces the real version with a 0.0.1-security placeholder, so there is nothing left to install and nothing to observe. Those stay, filed under review and marked as placeholders.

Sandbox

A sandbox is table stakes. The open problem is detection strategy, worked on across two branches in parallel.

sb-v1.3

Evasion resistance

Sandbox realism, against payloads that go quiet the moment they sense they are being watched.

Latest stable release of the sandbox is used for this feed currently. We plan to, eventually, also be able to run heuristics on the dropped binaries and make them available for download. They will come in the subsequent version of this sandbox.

Contact

A disputed verdict, a package worth a look, or just to talk. Cheers.

contact@snappyfeet.org