Skip to content

feat: detect phantom dependencies#753

Open
goureeshreddy7 wants to merge 1 commit into
OWASP:mainfrom
goureeshreddy7:feat/phantom-dependencies
Open

feat: detect phantom dependencies#753
goureeshreddy7 wants to merge 1 commit into
OWASP:mainfrom
goureeshreddy7:feat/phantom-dependencies

Conversation

@goureeshreddy7

Copy link
Copy Markdown
Collaborator

Fixes #731

What does this PR do? Implements the Phantom Dependency detection engine to catch unlisted imports that break strict environments like Vercel deployments (PD001 and PD002).

Key Changes:

AST Import Scanner: Upgraded scanProjectForPackageUsage in src/usage/scanner.ts to capture all bare module imports across the source code rather than just a pre-filtered list.
Overrides parsing: Added readOverridesAndResolutions to src/utils/package-json.ts to properly parse the overrides and resolutions blocks.
Cross-Referencing: Updated src/index.ts to cross-reference the import map against the declared dependencies.
Ranked Output: Output is ranked by build impact as requested, with strict deploy-breakers (PD001 - override pins) surfaced above standard transitive ghosts (PD002).

Testing: Verified locally that the CLI correctly catches ghost dependencies and exits with an error code, effectively stopping the build before deployment.

@sonukapoor

Copy link
Copy Markdown
Collaborator

Hey @goureeshreddy7 - this branch has merge conflicts against main. Could you rebase and resolve them, then force-push? Happy to review once it's up to date.

@sonukapoor

Copy link
Copy Markdown
Collaborator

Hey @goureeshreddy7, the branch has merge conflicts that need to be resolved before I can review. Could you rebase against main and push an update?

@goureeshreddy7 goureeshreddy7 force-pushed the feat/phantom-dependencies branch from 2011b8e to e056bc4 Compare June 27, 2026 02:27
@goureeshreddy7

Copy link
Copy Markdown
Collaborator Author

@sonukapoor sir, I have successfully rebased the branch against main and resolved the merge conflicts as you said, Let me know if anything to doo !

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One blocker at the file level: pr592_comments.json needs to be removed before merge. It looks like a working note that was accidentally staged - it will end up in the npm tarball and clutter the repo permanently once merged.

Comment thread src/index.ts
}

if (options.usage) {
if (scanState.pd001 && scanState.pd001.length > 0) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phantom detection is gated on options.usage, but --usage is a separate feature for import-reachability filtering with its own semantics. Users who don't pass --usage get no phantom detection at all, and users who do pass --usage get phantom detection they didn't ask for. These should be decoupled - either run phantom detection unconditionally or add a dedicated --check-phantoms flag.

Comment thread src/index.ts
console.log(chalk.yellow("\n[PD002] Transitive-only Phantom Dependencies Detected"));
scanState.pd002.forEach(pkg => console.log(` - ${pkg}`));
}
if ((scanState.pd001 && scanState.pd001.length > 0) || (scanState.pd002 && scanState.pd002.length > 0)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phantom findings aren't wired into --json output - machine consumers parsing JSON (CI pipelines, dashboards) get no phantom data at all. The pd001/pd002 results need to be included in the JSON schema before this feature is usable in CI.

Comment thread src/index.ts
scanState.sorted.some(reachesFailOn) || overrideFindings.some(reachesFailOn);
scanState.sorted.some(reachesFailOn) ||
overrideFindings.some(reachesFailOn) ||
(options.usage && ((scanState.pd001 && scanState.pd001.length > 0) || (scanState.pd002 && scanState.pd002.length > 0)));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any phantom finding triggers exit code 1 here regardless of what --fail-on is set to. A project running --fail-on critical would fail hard on any phantom import, including low-risk dev tooling. This silently breaks the CI contract --fail-on is supposed to provide.

Comment thread src/utils/package-json.ts
}
}

export function readOverridesAndResolutions(projectPath: string): Set<string> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing hasOverrideEntries just above this (line 151-153) correctly checks pnpm.overrides nested under the pnpm key. readOverridesAndResolutions only reads top-level overrides and resolutions, so pnpm projects using pnpm.overrides will have their overridden packages misclassified as pd002 instead of pd001.

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.

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

2 participants