To tell whether a malicious package ran, look for four kinds of evidence: install or build logs showing it was fetched, process records showing scripts executed, network records showing outbound connections, and credential logs showing unusual use afterwards. Absence of evidence is only meaningful if those logs exist for the window.
Start with the window
Get the publish and removal times of the bad version. Only installs and builds inside that window could have fetched it, unless a cache or mirror kept serving it. Narrow every search to that window plus a margin for caches.
Log sources
| Source | What to look for | Typical gap |
|---|---|---|
| CI job logs | Install step fetching the version, unexpected commands | Logs expire |
| Package proxy or mirror logs | Who downloaded the version, and when | Not every team has a proxy |
| Endpoint process logs | Child processes of the package manager | Often not collected on laptops |
| DNS and network egress | Lookups to hosts you do not know | Short retention |
| Cloud and source audit logs | Credential use from new places | GitHub keeps 180 days in its organization audit log |
| Git history | New workflow files, new deploy keys, changed hooks | Force pushes hide changes |
Hunt routine
- List every environment that installed the package in the window, using lockfiles, SBOMs and proxy logs.
- For each, read the install log. npm runs install scripts of dependencies by default, so a script that did something odd would run at this point.
- Check egress for the same period. A build runner talking to a host outside your usual registries and source hosts is a lead.
- Check the credential audit logs for each secret that environment could read. Look for use from unfamiliar addresses.
- Look for persistence: new or changed workflow files, git hooks, shell profile entries, scheduled jobs.
- Write down what you checked and what logs were missing.
Reading the result
Found evidence of execution means move to the executed level and rotate. No evidence with complete logs is a reasonable not found. No evidence with missing logs means unknown, so rebuild clean and rotate anyway if the environment held powerful secrets.
For a broader walkthrough, see investigating a CI/CD compromise.
Keep a hunt log
Write each query you ran, the system it ran against, the time range and the result, including empty results. A hunt log turns a vague we looked into a statement someone can check later. It also stops two people repeating the same search while a gap goes unexamined. When you finish, list the logs you wished you had, such as egress records for build runners, and add them to the hardening backlog.
Quick check
What if we do not keep egress logs?
Treat the result as unknown and act on the safer assumption, then start keeping egress logs for build systems.
Is a quiet log proof of a clean run?
Only if the log covers the right systems and time. Say what was and was not covered.
Safeguard's SBOM and dependency scanning can show which projects carried the package, which narrows the hunt. A free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.