Skip to content

fix(pd001,pd002): resolve imports per workspace member and ignore type-only imports - #1114

Open
osfv wants to merge 1 commit into
OWASP:mainfrom
osfv:fix/issue-966-pd-phantom-false-positives
Open

fix(pd001,pd002): resolve imports per workspace member and ignore type-only imports#1114
osfv wants to merge 1 commit into
OWASP:mainfrom
osfv:fix/issue-966-pd-phantom-false-positives

Conversation

@osfv

@osfv osfv commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

What changed and why

Fixes both defects reported in #966. They land on the same comparison in the PD detectors, so they are in one change as suggested there.

A - workspace roots

Imports were collected from the whole tree while declarations were resolved against the root manifest only, so scanning a monorepo root reported every dependency a member declares and imports in its own source as a PD002 phantom (or PD001 when an override happened to exist).

  • buildOverrideContext now discovers workspace members (root workspaces, pnpm-workspace.yaml) via a new readWorkspaceMemberManifests in src/utils/package-json.ts, reusing the same pattern expansion readDirectDependencyNames already uses for the CVE scan, and exposes ctx.workspaceMembers (dir + declared set).
  • PD001/PD002 resolve each importing file against the nearest enclosing member first, then the root (root node_modules is on every member's resolution path, so a root declaration still satisfies all files). The finding lists only the files whose owning package leaves the import undeclared. Shared in phantom-utils.undeclaredImportFiles.

B - type-only imports counted as runtime

scanAllImports / scanProjectForPackageUsage are a regex pass over raw text, so /** @type {import('postcss-load-config').Config} */ counted as a dynamic import and import type { X } from 'pkg' counted as a runtime import.

  • Comments (//, /* */) are blanked before matching, string-aware so 'https://...' is not treated as a comment.
  • import type ..., export type ..., and specifier lists where every entry is type-prefixed are skipped. A default import that happens to be named type, or a mixed list (import { type A, b }), still counts.

This applies to every consumer of the scanner, as discussed in the issue: PD001, PD002, the OA009 guard, and the --usage reachability filter. One existing assertion in tests/usage.test.ts treated import type as usage and was updated accordingly; a type-only reference to a vulnerable package no longer counts as "used".

Verification

Against the minimal repro from the issue (pnpm workspace root, apps/web declares and imports js-yaml) plus a stock postcss.config.mjs with the JSDoc annotation, main reports two PD002 findings and this branch reports none. New unit coverage in tests/usage.test.ts, tests/overrides/detectors/pd00{1,2}.test.ts, and tests/overrides/context-builder.test.ts. Rule docs for PD001/PD002 gained a short "What counts as an import" section.

Note: #966 is assigned to @alamb-hex. The maintainer's check-in on Sept 6 had no reply, so I went ahead; happy to close this in favour of theirs if they are still working on it.

Closes #966

…e-only imports

Two independent false-positive sources in the phantom-dependency rules,
both on the same comparison in the PD detectors.

Workspace roots: imports were collected from the whole tree but
declarations were resolved against the root manifest only, so scanning a
monorepo root reported every dependency a member declares and imports in
its own source as a transitive-only phantom. buildOverrideContext now
discovers workspace members (root `workspaces`, pnpm-workspace.yaml) and
their declared packages, and PD001/PD002 resolve each importing file
against the nearest enclosing member before falling back to the root.
The finding lists only the files whose owning package leaves the import
undeclared.

Type-only imports: the usage scanner is a regex pass over raw file text,
so a JSDoc annotation such as `/** @type {import('postcss-load-config')
.Config} */` counted as a dynamic import, and `import type { X } from
'pkg'` counted as a runtime import. Comments are now blanked out
(string-aware) before matching, and `import type` / `export type` /
all-`type` specifier lists are skipped. This applies to every consumer of
the scanner: PD001, PD002, the OA009 guard, and the --usage filter, where
a type-only reference to a vulnerable package no longer counts as usage.

Closes OWASP#966
@osfv
osfv requested a review from sonukapoor as a code owner September 9, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant