Skip to content

Check exactly pinned npm dependencies for deprecations and vulnerabilities #252

Description

@fniessink

The problem

A package.json dependency gets neither the yank check nor the vulnerability check. The README gives one reason for both: a dependency there "declares a range rather than a version, and the version that range resolves to is recorded in the lock file, which Update-time does not read; auditing that lock file is what npm audit is for."

That holds for a range, and not for "react": "17.0.2". npm reads a bare version as an exact version, and the README itself points at that spelling as the way to opt a dependency out: "pin an exact version to opt it out of automatic updates entirely, just like with Python dependencies." Such a dependency names the version the run leaves it on, which is the one thing both checks need — the same reasoning that makes a == pin in a requirements.txt checkable.

Both sources are already reachable. npmjs.deprecation() reads a version's deprecation from the registry and is what reports a deprecated jsDelivr URL in the yank's wording. OSV's npm ecosystem is already queried, for those same jsDelivr URLs. And package_json.dependency_locations() already locates each declaration at the line declaring it.

How often this happens

Checked before writing this issue rather than assumed: of the three package.json files in a real project (quality-time's docs, renderer, and frontend), none of the 50 dependencies declares a bare version — every one uses a range. npm's convention is ^, so this gap may be rare in practice, which is worth weighing against the two above before scheduling it.

Scope

  • Recognise a dependency whose spec is a bare version, in the sections dependency_locations already reads (dependencies, devDependencies, optionalDependencies).
  • Check that version for a deprecation on the npm registry, and report it in the wording a yanked dependency gets, as a deprecated jsDelivr version already is.
  • Check that version against OSV's npm advisories, and report it as any other vulnerable reference, at the line declaring it.
  • Leave every range-declared dependency exactly as it is.

Out of scope

  • Reading package-lock.json or pnpm-lock.yaml to resolve a range to a version. That is what npm audit and pnpm audit do properly, and it is the scope line the README already draws.
  • Updating. An exact pin is how a dependency is opted out of updates, npm and pnpm leave it where it is, and this issue adds warnings only.
  • Markers. package.json is strict JSON with nowhere to put a comment, so neither warning can be silenced per dependency, which is already true of everything else in that file.

Documentation

  • The per-type "Yanked dependencies" and "Vulnerable dependencies" answers for npm and pnpm dependencies, which currently say neither check applies and why.
  • The npm rows in the dependency-type tables under "Yanked dependencies" and "Vulnerable dependencies".
  • A changelog entry under [Unreleased].

Open questions

  1. Does a range that admits exactly one version count as exact — =1.2.3, or 1.2.3 - 1.2.3 — or only a bare version, which is the spelling the README already tells people to use?
  2. Do both checks apply to optionalDependencies as well as dependencies and devDependencies, given that an optional dependency may not be installed at all?
  3. Is a deprecation worth reporting for a version the run deliberately leaves alone, or does it only add noise to a pin the project has already decided to keep?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions