Skip to content

fix(PD002): false positives on workspace roots — member-declared dependencies reported as transitive-only phantoms #966

Description

@alamb-hex

Summary

When scanning a monorepo root, PD002 reports dependencies that are declared directly in a workspace member's package.json as transitive-only phantoms. Declaration appears to be resolved against the root manifest only, while imports are collected from the whole tree — so every workspace-member dependency that is imported in that member's source is flagged.

Version: 1.28.0 · Package manager: pnpm (workspace via pnpm-workspace.yaml)

Minimal reproduction

repro/
├── package.json            { "name": "pd002-repro", "private": true, "version": "1.0.0" }
├── pnpm-workspace.yaml     packages: ["apps/*"]
└── apps/web/
    ├── package.json        { "name": "@repro/web", "dependencies": { "js-yaml": "^4.1.0" } }
    └── src/index.ts        import yaml from 'js-yaml';
pnpm install
cve-lite . overrides --json

Actual

PD002  medium  js-yaml
  js-yaml is imported in source but only present as a transitive dependency - declare it explicitly
  details: Imported in: apps/web/src/index.ts. If the parent package drops or changes this
           dependency, your code will break without warning. Run: pnpm add js-yaml

Expected

No finding. js-yaml is a declared direct dependency of @repro/web, which is the package that imports it.

The suggested remediation (pnpm add js-yaml) would add a redundant second declaration at the root.

The detail line points at the fix

The finding already reports Imported in: apps/web/src/index.ts. So the import's owning workspace is known at the point the finding is produced — it just isn't used to pick which manifest to resolve the declaration against. Resolving against the nearest enclosing workspace member's package.json (falling back to the root) should close this.

Corroborating: scanning the member directly is clean.

cve-lite ./apps/web overrides --json   # 0 findings
cve-lite .            overrides --json # 1 PD002 finding

Real-world impact

On a real 2-app pnpm monorepo (root manifest declares one devDependency; apps/web declares the rest), a root scan produces 11 PD002 findings, all false — next, react, vitest, tailwindcss, @tanstack/react-query, lucide-react, clsx, tailwind-merge, @testing-library/react, @testing-library/jest-dom, @vitejs/plugin-react.

For a dashboard aggregating findings across many repos, that one project dominates the view with noise, which is what led us to filter PD001/PD002 out of our integration entirely rather than lose the signal from the OA rules.

Notes

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions