Skip to content

feat: detect phantom dependencies - packages imported but not declared (PD001/PD002) #731

Description

@sonukapoor

Problem

An override or resolution pin forces a package version into the resolved graph - but it does not declare the package as a dependency. If source code imports that package directly, the import only works by accident: the package happens to be present via hoisting or override resolution.

Under pnpm strict mode (the default on Vercel deployments), hoisted packages are not importable. A package present only via an override pin or as an unlisted transitive dependency will cause a build or deploy failure that no vulnerability scanner, no npm audit, and no override hygiene check would catch today.

Real-world case: A project had js-yaml imported in yaml-engine.ts and declared only as an override pin ("js-yaml": ">=4.2.0"), not in dependencies. The project built correctly under npm (hoisting made it available) but failed on two consecutive Vercel deploys under pnpm strict layout. No existing tool flagged it before the failures.

Proposed solution

A new import-side audit with two rules:

PD001 - Override-only phantom
Package is imported in source but its only presence in the resolved graph is via an overrides/resolutions entry. Not declared in dependencies or devDependencies.

PD002 - Transitive-only phantom
Package is imported in source but not declared in dependencies or devDependencies - present only as a transitive dependency of another package. Ranked lower than PD001 (less likely to break, but still a latent risk if the parent drops or replaces it).

How it works

CVE Lite CLI already parses imports from source files via the --usage flag infrastructure. PD extends that to cross-reference the import map against package.json declarations:

imported packages
  - declared in dependencies/devDependencies  -> OK
  - only in overrides/resolutions             -> PD001
  - only transitively via another package     -> PD002
  - not in graph at all                       -> separate error (not in scope here)

Output ranked by build impact: pnpm strict deploy-breakers (PD001) surfaced first.

Action: declare the dependency properly.

Acceptance criteria

  • PD001 fires for packages imported but only present via an override pin
  • PD002 fires for packages imported but only present transitively
  • PD001 ranked above PD002 in output
  • Works across npm, pnpm, Yarn, and Bun lockfiles
  • Requires source files to be present (gracefully skipped if no src/ detected)

Related

Part of the dependency hygiene engine - see umbrella issue #733. OA009's safety guard (never remove an override that anchors an imported package) reads PD001 to confirm before flagging a floor as stale.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions