---
title: "Why an automated dependency scan flags packages that are fine. Lazaretto"
description: "We ran a behavioral scan over the most-installed npm packages. Most are clean, and the ones that get flagged are mostly legitimate code that happens to look like an attack. Here is how to tell the difference."
url: "https://lazaretto.dev/blog/why-dependency-scanners-cry-wolf"
---

[Home](https://lazaretto.dev/) / [Blog](https://lazaretto.dev/blog) / 2026-07-17

# Why an automated dependency scan flags packages that are fine

We ran a behavioral scan over the most-installed npm packages. Most are clean, and the ones that get flagged are mostly legitimate code that happens to look like an attack. Here is how to tell the difference.

We ran an automated behavioral scan over the most-installed npm packages, the kind of check you would drop into CI. About seven in eight came back with nothing flagged. The interesting part is the ones that did get flagged, because almost none of them are actually dangerous. They are ordinary, trusted libraries that happen to do something that also shows up in malware.

## The usual false alarms

A handful of patterns account for most of the noise:

- **Dynamic code generation.** lodash, vue, and webpack all build and run code at runtime with the `Function` constructor. A template engine compiling a template and a piece of malware unpacking a payload use the exact same primitive. A scanner that flags every `Function()` or `eval()` flags all of them.
- **Minified bundles.** A minified file matches almost any heuristic: long opaque strings, dense operators, base64 blobs. A normal package's published dist bundle looks, to a pattern matcher, a lot like obfuscated malware.
- **Locale and text data.** An i18n table full of non-Latin scripts, or a library with a byte-order-mark check, trips detectors built to catch hidden-unicode prompt injection.
- **Comments.** A comment that references "fetch step 12.1.1.3" reads as a hardcoded IP address to a naive regex.

## What actually separates a threat from a false alarm

The signal is not the primitive, it is the context. `Function()` on a template string is a compiler. `Function()` on a value that was just base64-decoded or fetched from the network is a payload. A `setInterval` next to a `fetch` and an `eval`, all in one small loop, is a self-updating beacon; the same three things scattered across a bundled CLI are a coincidence. Reading `process.env` is normal; reading `~/.ssh/id_rsa` and posting it to a raw IP is not.

Getting this right is most of the work. A scanner that cries wolf trains you to ignore it, which is worse than no scanner. So we tune hard for precision: exact known-bad matching, behavioral rules that require corroborating context, minified bundles handled as the noise they are, and every verdict bound to a content hash so you can confirm what you installed is what got scanned.

## See it on your own dependencies

Scan any package free from the [browser](https://lazaretto.dev/#try), or add the [CI action](https://github.com/jamesdfinance-dev/lazaretto-scan-action) to your repo. A `clear` result means no known-bad match and no rule fired: a signal, not a warranty, and the full evidence for anything flagged is in the report.

Lazaretto is the pre-install checkpoint agents call themselves. [Try it](https://lazaretto.dev/#try) · [pricing](https://lazaretto.dev/pricing) · [connect your agent](https://lazaretto.dev/agents).

---

Machine-readable index: https://lazaretto.dev/llms.txt. OpenAPI: https://lazaretto.dev/openapi.json. MCP endpoint: https://lazaretto.dev/mcp. Any page on this site is available as markdown by adding .md to its path.
