METHOD
How a package moves through it.
Five stages, in the same order, every run. Nothing carries over from one run to the next.
Fresh sandbox
Each run starts from the same pristine container image.
Instrumented install
Lifecycle scripts run under system call tracing, isolated from anything real.
Import
The package is loaded as a consumer would load it, and traced as a separate record.
Attribution
Every action is tied to the process that performed it.
Correlation
Events that mean little alone are joined into findings.
TWO ENGINES
One watches it run. One reads what shipped.
Cohen triages; it does not classify. In 1984 Fred Cohen proved that no detector can decide, in general, whether a program is a virus. What remains is observation, and what you do with it.
DYNAMIC
Behaviour in a sandbox
Installed and imported in an isolated container while every system call is traced: files, processes, permissions and sockets.
Events are read in sequence. A secret read, then archived under its own name, is a finding neither event makes alone.
CORRELATED EVENT PAIRS · ONE RUN
STATIC
Contents of the tarball
The published archive, examined without being run. Rules match the source, the capabilities it holds and its registry metadata.
Every file is hashed and scored for entropy, which exposes packed or encrypted content inside otherwise readable code.
SHANNON ENTROPY PER FILE · BITS/BYTE
SANDBOX
A sandbox is table stakes. Detection strategy is the open problem.
Two branches, developed in parallel. The latest stable release runs the live feed.
Evasion resistance
Realism against payloads that go quiet once they sense they are being watched.
Heuristics on dropped binaries
Analysis of the binaries a package drops, available to download alongside the verdict.
DYNAMIC RECORD
The record, field by field.
Each phase emits one flat record under three headings. Empty fields are published as empty lists, so the absence of a signal can be queried.
{
"network": {
"package_connections": [ "216.198.79.1:443", "64.29.17.1:443" ],
"dns_lookups_non_npm": [ "a1b2c3.dnslog.cn" ],
"exfil_services_contacted": [ "webhook.site" ]
},
"canaries": {
"canary_tokens_leaked": [ "AKIA…SEEDED" ],
"accessed_by_package": [ "/root/.ssh/id_rsa", "/root/.aws/credentials" ],
"probed_but_absent": [ "/root/.config/gh/hosts.yml" ],
"sensitive_file_access": [ "/root/.npmrc", "/test/.npmrc" ]
},
"filesystem": {
"dropped_and_executed": [ "…/dist/internal/calc-mapping.bin" ],
"executed_by_package": [ "/usr/bin/tar", "/usr/bin/gzip" ],
"archives_written": [ "/tmp/.x/ssh.gz", "/tmp/.x/aws.gz" ],
"tmp_snooped": [ ],
"recon_dirs_enumerated": [ "/root", "/home" ],
"package_files_read": [ "/etc/ld.so.cache", "/usr/local/bin/node" ]
},
"no_js_entry": false
}
// install_phase and import_phase are recorded separately, then unioned for display
network.package_connectionsEvery socket opened by the package's own processes, as ip:port. Registry traffic is attributed to npm and never appears here.
network.dns_lookups_non_npmResolutions to anything other than the registry. DNS exfiltration opens no socket; the hostname is the payload.
network.exfil_services_contactedKnown out of band callback and paste services. Close to conclusive on its own, so it is kept apart from general connections.
canaries.canary_tokens_leakedThe container holds planted credentials that exist nowhere else. If one leaves, the package did not just read a secret. It sent one.
canaries.accessed_by_packageCredential paths the package opened: SSH keys, cloud credentials, wallets, CI tokens. Kept apart from ordinary reads because the targeting is deliberate.
canaries.probed_but_absentPaths it looked for and did not find. Checking for a wallet it has no use for reveals intent, whether or not the file exists.
filesystem.dropped_and_executedA path written or made executable during the run, then passed to execve. The highest value correlation the engine produces.
filesystem.archives_writtenCompressed output, recorded with names intact. Attackers label their loot: ssh.gz, aws.gz, token.gz.
filesystem.recon_dirs_enumeratedWalks across home and root that no build step needs. Raised alongside recon_dir_scan, which flags the pattern rather than the paths.
no_js_entryTrue when there was nothing to import, so "nothing ran" is never mistaken for "nothing happened".
STATIC ENGINE
Rules, families and entropy.
The archive is examined without being run. Threats and capabilities are matched separately and never mixed.
threat-*Severity low, medium or high, with MITRE tactics. Reported with the file, the line and the matched text.
capability-*What the code can do: outbound network, process spawn, write executable, delete. Context only, never scored; almost every real package holds some.
Rule familiesNames are hierarchical, and rules collapse into a family only when two or more share a stem. threat-runtime-obfuscation-{general,base64exec,steganography} becomes one node; threat-runtime-environment-read stays on its own.
Per file measuresA SHA 256 hash for every file, and an entropy score for every file under the size cap.
EntropyShannon entropy over byte values, divided by eight, on a scale of 0 to 1. Text sits at 0.52 to 0.65; packed or encrypted data sits near 1. Anything at or above 0.94 is flagged as a likely bundled binary.
Not measuredFiles over the size cap are hashed but not measured, and published as null. Missing and low are different claims.
VERDICTS
The edge cases, settled.
Two verdicts, so the only question is whether to look. Each of these cases needed its own rule.
Truncated runsA run that times out or dies mid trace resolves to review. A clean verdict with no observation behind it would be worse than none.
Delisted packagesPulled by the registry during analysis: removed from the feed, not kept as a record of something nobody can install.
Security placeholdersnpm swaps a held version for a 0.0.1-security stub. Nothing remains to observe, so it stays under review, marked as a placeholder.
Clean is published tooEvery analysed version gets a verdict. A feed of hits alone offers no baseline for the other 98 percent.