DepL - Dependency Detective
Think like a detective. Scan your dependencies.
DepL reads your lockfile (npm, yarn or pnpm) and checks every package you
actually have installed against the OSV advisory database,
including the transitive ones your package.json never names.
No account, no API key, no telemetry. It scans only when you ask, and it never
changes a file.
Quick start
- Open a
package.json.
- Click the magnifier in the editor title bar, or run DepL: Scan Current
package.json from the Command Palette.
- Results open in the DepL panel in the Activity Bar.
That is the whole loop. Nothing runs until you do this, and nothing keeps
running after it.
| Where |
Button |
What it does |
| DepL panel title bar |
Magnifier |
Scans the current package.json |
| DepL panel title bar |
Refresh |
Scans the same file again |
| DepL panel title bar |
Note |
Opens the whole scan as a Markdown document |
DepL panel … menu |
Show Scan Log |
Opens DepL's output channel |
DepL panel … menu |
Clear Results |
Empties the panel and removes the status bar entry |
| On a finding row |
Info |
Shows that finding's full evidence in the scan log |
| Editor title bar |
Magnifier |
Scans the package.json you are looking at |
| Status bar |
DepL: 7 |
Click to jump back to the panel |
The refresh, note and clear buttons appear once you have results. You can also
right-click a package.json in the editor or the Explorer to scan it.
Reading a result
Advisories are grouped under the package they affect, because one framework
dependency can carry dozens:
v Security · 15
> next · Critical · 15.5.22 · 6 advisories · upgrade to 15.5.28
> postcss · High · 8.4.31 · 4 advisories · fixed in 8.5.23 · transitive
Each row gives the worst severity, the version you have, how many advisories,
and the one version to move to. Three different instructions, three different
jobs:
upgrade to X: your declared range already allows X. Update the
installed version; package.json needs no edit.
raise to X: no version your declaration allows is clean, so the
declaration itself has to change.
fixed in X: the package is transitive. Update whatever requires it, or
pin it with an npm overrides entry.
To see more, pick whichever suits you: select a row for its evidence (and to
jump to the line, when it has one), hover for the same without leaving the
panel, or use the note button to open the entire scan as an unsaved Markdown
document, covering what ran, what did not, and every finding. DepL does not
write that file anywhere: save it if it is useful, close it if it is not.
What a result proves
This is the part worth knowing, because it is where DepL differs from a version
comparison.
With a lockfile, findings are labelled exact: they describe the versions
you actually have, transitive packages included.
| Lockfile |
Versions read |
package-lock.json, npm-shrinkwrap.json |
lockfileVersion 2 and 3 |
yarn.lock |
classic (v1) and Berry (v2+) |
pnpm-lock.yaml |
lockfileVersion 5 through 9 |
Without one, DepL assesses what your declarations allow and labels those
findings range-based, since it can only show that an install could resolve to a
vulnerable version, and transitive packages are out of scope.
Anything it cannot check honestly, it says so. Git, URL and local-path
declarations are not-assessed rather than guessed at. Workspace links and
patched packages are counted and reported, not assessed, because their contents
no longer match any published version. A lockfile DepL cannot parse is reported
as unreadable and the scan falls back to declarations.
A check that did not finish is never shown as a clean result. If the network
drops or a check is skipped, the scan is reported as partial in the panel, in
the status bar and in the report. Local validation runs first, so its results
survive any network failure.
Settings
| Setting |
Default |
Purpose |
depl.security.useLockfile |
true |
Read the lockfile so findings cover installed and transitive versions |
depl.network.enabled |
true |
Set to false to run fully offline |
depl.statusBar.enabled |
true |
Show the last scan's result in the status bar |
depl.dependencyGroups |
all four |
Which dependency groups to read |
depl.validation.reportUnpinnedRanges |
true |
Report * and latest as informational |
depl.outdated.reportNotAssessed |
true |
Report declarations the registry cannot resolve |
depl.network.timeoutMs |
10000 |
Per-request timeout |
depl.network.maxConcurrentRequests |
8 |
Concurrency cap for outbound requests |
What DepL will not do
Nothing runs in the background. No document, save, workspace or timer
listeners, and no file watchers. There are deliberately no squiggles in
package.json either: a squiggle reads as live, and keeping one honest across
your edits would mean watching the document.
No file is ever modified. DepL never edits package.json, never installs,
updates or removes a package, and never runs a package script. Every finding
tells you what to do; you decide whether to do it.
Nothing is overstated. Every check reports whether it completed, and a scan
where anything did not finish is reported as partial, not successful.
Privacy and network use
The outdated check sends one request per unique package name to
https://registry.npmjs.org. The security check sends package names, plus a
version where one is known, to https://api.osv.dev. That is every outbound
request DepL makes.
Neither sends your source code, your manifest, file paths, environment values or
credentials. No API key, no backend, no telemetry. Requests are deduplicated and
cached for one scan, and go through Node's HTTP modules, so VS Code's proxy
settings and injected certificates apply automatically.
Only the public npm registry is queried, so a private package is reported as not
found with a message saying it may be private. Set depl.network.enabled to
false to stop all outbound requests; validation still works offline.
Good to know
Unsaved edits are scanned: when the manifest is open, DepL reads the editor's
text, not the file on disk. If no package.json is open, DepL asks which one to
scan. Long scans can be cancelled from the progress notification, and findings
from checks that already finished are kept.
Reference
Severity
DepL does not invent severities. It maps the word the advisory states
(CRITICAL, HIGH, MODERATE, LOW) onto its own scale. When an advisory
gives only a CVSS vector, DepL shows the vector as evidence, defaults to
Moderate, and says it did not compute a score. Withdrawn advisories are ignored.
Which version it recommends
An advisory often names a fix per release line. DepL recommends the single
lowest version that clears every vulnerable interval you can still reach. Never
a prerelease, never a downgrade, and never a jump to a much newer major when the
fix on your own line already clears it. On nanoid 3.3.17 the answer is
3.3.18, not 5.1.6.
What counts as outdated
A newer version existing is not a finding. ^1.2.0 with 1.9.0 published is
current, because the range already allows it. A finding means the declaration
holds you back: ^1.2.0 when 2.4.0 is the latest stable. Prereleases never
count as the latest stable; dist-tags are compared to what they resolve to
today; npm aliases are resolved against the aliased package.
Commands
| Command |
What it does |
DepL: Scan Current package.json |
Scans the selected manifest |
DepL: Rescan Last package.json |
Scans the previous target again |
DepL: Open Scan Report |
Opens the whole scan as an unsaved Markdown document |
DepL: Clear Results |
Empties the results view |
DepL: Show Scan Log |
Opens the DepL output channel |
Rules
Security
| Rule |
Severity |
What it catches |
known-vulnerability |
From the advisory |
An advisory affecting an installed version, or one the declaration can resolve to |
not-assessed |
Info |
Declarations the advisory database cannot be queried for |
Outdated dependencies
| Rule |
Severity |
What it catches |
range-matches-nothing |
Moderate |
No published version satisfies the declared range |
unknown-dist-tag |
Moderate |
A dist-tag the registry does not publish |
package-not-found |
Moderate |
The public registry has no such package |
no-published-versions |
Moderate |
The package exists but has published nothing |
update-available |
Low |
The latest stable release falls outside the declared range |
deprecated-version |
Low |
The highest version the range allows is deprecated |
not-assessed |
Info |
Git, URL, local path, workspace: or catalog: declarations |
Validation
| Rule |
Severity |
What it catches |
invalid-json |
High |
Syntax errors, comments, trailing commas, empty file, non-object root |
duplicate-key |
High |
The same package declared twice in one group, where JSON silently keeps the last |
group-not-object |
High |
dependencies and friends that are not JSON objects |
declaration-not-string |
High |
A dependency value that is a number, object or array |
scripts-not-object |
High |
A scripts field that is not an object |
invalid-range |
Moderate |
A value that is not a valid range, tag, alias, URL or path |
empty-declaration |
Moderate |
An empty version string, which npm treats as * |
invalid-name, invalid-dependency-name |
Moderate |
Names that break npm's naming rules |
invalid-version |
Moderate |
A version that is not valid semver |
invalid-name-type, invalid-version-type, script-not-string |
Moderate |
Fields of the wrong JSON type |
duplicate-declaration |
Moderate to Info |
The same package in two groups |
script-empty |
Low |
A script with no command |
missing-name, missing-version |
Info |
Absent publish metadata, skipped for "private": true |
unpinned-declaration |
Info |
*, x or latest, which make installs unreproducible |
Legal but unusual manifests are left alone.
Requirements
VS Code 1.90 or newer. Node is not required: the checks run inside the extension
host.
License
MIT. The full licence text ships with the extension, in LICENSE.txt.