feat(windows): eclipse plugin detection#32
Conversation
eclipseFeatureDirs was macOS-only (/Applications/Eclipse.app/...). On Windows, Eclipse installs to variable paths. Now dynamically resolves features/dropins directories from common Windows locations: - %PROGRAMFILES%\eclipse - C:\eclipse - %USERPROFILE%\eclipse\<variant>\eclipse (Oomph installer)
Eclipse plugin detection now receives the detected IDEs list and uses the actual Eclipse install path (which may have been discovered via registry) instead of hardcoded candidates. This ensures plugins are found even when Eclipse is installed at a custom path. Also parses bundles.info on Windows (modern Eclipse uses p2 provisioning) instead of scanning features/ directory which may not exist.
Rewrites Eclipse plugin detection with a comprehensive pipeline: - Stage 1: Uses detected IDE install paths (registry-aware) - Stage 2: Well-known path probes including Oomph installer paths, vendor variants (STS, MyEclipse), and D:-Z: drive letter scanning - Stage 4: Install validation (eclipse.ini + plugins/ + configuration/) before reporting — eliminates false positives - Stage 6: bundles.info parsing (primary) + dropins/ scanning (secondary) with dedup by symbolicName@version Also expands bundled prefix list to correctly classify Eclipse platform bundles (491 bundled vs 45 user-installed on a typical install).
…tplace plugins Expanded eclipseBundledPrefixes to cover all standard Eclipse platform dependencies (JUnit, JaCoCo, Gradle tooling, crypto libs, etc.). This reduces false positives from 45 to 4 — only truly marketplace- installed plugins (Claude Code, Putman) are now classified as user_installed.
…in detection Instead of heuristic prefix-matching on bundles.info, use Eclipse's own p2 director (eclipsec.exe -listInstalledRoots) to get the authoritative list of installed root features. Marketplace-installed features are those not prefixed with org.eclipse.* or epp.*. The p2 director output also enriches bundles.info — bundles belonging to marketplace features are tagged as 'marketplace' source. Falls back to bundles.info-only parsing if eclipsec.exe is unavailable.
…s flag By default, only user-installed and marketplace plugins are included in scan output and telemetry. Bundled/platform plugins (e.g., Eclipse's 500+ OSGi bundles) are filtered out to reduce noise and payload size. Use --include-bundled-plugins to include them if needed. Payload reduction: 124KB → 21KB for a typical Eclipse install.
macOS detection doesn't produce bundled plugins in the same volume, so the filter is unnecessary there. Gate on exec.GOOS() == windows.
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
There was a problem hiding this comment.
Pull request overview
Adds improved Eclipse plugin detection on Windows and reduces noisy “bundled/platform” plugin output by default, with an opt-in flag to include bundled plugins.
Changes:
- Extend
ExtensionDetector.Detectto accept detected IDEs and use them for Eclipse plugin discovery. - Implement a multi-stage Windows Eclipse plugin discovery pipeline (candidate collection, validation, p2 roots, bundles.info, dropins).
- Add a
--include-bundled-pluginsflag and Windows-only filtering of bundled plugins in scan + telemetry flows.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/telemetry/telemetry.go | Pass IDEs into extension detection and filter bundled plugins on Windows (unless opted in). |
| internal/scan/scanner.go | Same Windows bundled-plugin filtering behavior for community scans. |
| internal/detector/jetbrains_plugins_test.go | Update test to new ExtensionDetector.Detect signature. |
| internal/detector/ide.go | Use .cmd wrappers for certain Windows IDE binaries to avoid launching GUI executables. |
| internal/detector/extension.go | Update extension detection API and route Eclipse detection through detected IDE install paths. |
| internal/detector/eclipse_plugins.go | Replace simple Eclipse scanning with Windows-specific validated discovery + parsing pipeline. |
| internal/cli/cli.go | Add IncludeBundledPlugins config and parse --include-bundled-plugins. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // filterUserInstalledExtensions removes bundled/platform extensions, | ||
| // keeping only user-installed and marketplace extensions. | ||
| func filterUserInstalledExtensions(exts []model.Extension) []model.Extension { | ||
| var filtered []model.Extension | ||
| for _, ext := range exts { | ||
| if ext.Source != "bundled" { | ||
| filtered = append(filtered, ext) | ||
| } | ||
| } | ||
| return filtered |
There was a problem hiding this comment.
filterUserInstalledExtensions is duplicated here and in internal/scan/scanner.go. Consider moving it to a shared location (e.g., an internal helper package) to avoid divergence if the filtering criteria changes (e.g., additional bundled sources besides "bundled").
| // filterUserInstalledExtensions removes bundled/platform extensions, | ||
| // keeping only user-installed and marketplace extensions. | ||
| func filterUserInstalledExtensions(exts []model.Extension) []model.Extension { | ||
| var filtered []model.Extension | ||
| for _, ext := range exts { | ||
| if ext.Source != "bundled" { | ||
| filtered = append(filtered, ext) | ||
| } | ||
| } | ||
| return filtered |
There was a problem hiding this comment.
filterUserInstalledExtensions is duplicated here and in internal/telemetry/telemetry.go. Consider centralizing this filtering logic to keep behavior consistent across scan and telemetry paths.
| ctx := context.Background() | ||
| stdout, _, exitCode, err := d.exec.RunWithTimeout(ctx, 30*time.Second, | ||
| eclipsec, "-nosplash", | ||
| "-application", "org.eclipse.equinox.p2.director", | ||
| "-listInstalledRoots") |
There was a problem hiding this comment.
queryP2InstalledRoots creates a new context.Background() instead of using the caller’s ctx. This prevents cancellation/timeout propagation from the scan/telemetry run and can leave the process waiting up to the full RunWithTimeout duration even if the overall operation was canceled. Consider threading the caller context through the Eclipse detection pipeline and using it here.
| // Drive letter probe: D:\eclipse through Z:\eclipse (fixed drives only) | ||
| for drive := 'D'; drive <= 'Z'; drive++ { | ||
| driveRoot := string(drive) + `:\eclipse` | ||
| candidates = append(candidates, driveRoot) | ||
| } |
There was a problem hiding this comment.
The drive-letter probe loop adds D:\eclipse through Z:\eclipse unconditionally, but the comment says “fixed drives only”. As implemented, DirExists/FileExists checks against mapped network drives can be slow or hang, which may significantly impact scan time on Windows. Either adjust the comment, or restrict probing to local/fixed drives (or only drives that are known to exist).
| // platform or are standard dependencies. Everything NOT matching is classified | ||
| // as "marketplace" (user-installed from Eclipse Marketplace or update sites). |
There was a problem hiding this comment.
The header comment for eclipseBundledPrefixes says “Everything NOT matching is classified as "marketplace"”, but the implementation uses multiple non-bundled sources (e.g., "user_installed", "dropins", and sometimes "marketplace"). Please align the comment with the actual Source values used, or standardize the non-bundled classification to avoid confusing downstream consumers.
| // platform or are standard dependencies. Everything NOT matching is classified | |
| // as "marketplace" (user-installed from Eclipse Marketplace or update sites). | |
| // platform or are standard dependencies. Bundles that do not match these | |
| // prefixes are treated as non-bundled and may be classified into source | |
| // categories such as "marketplace", "user_installed", or "dropins". |
| func (d *ExtensionDetector) detectEclipsePluginsWindows(ides []model.IDE) []model.Extension { | ||
| // Stage 1+2: Collect candidate paths from detected IDEs + well-known locations | ||
| candidates := d.gatherEclipseCandidates(ides) | ||
|
|
||
| // Stage 4: Validate each candidate | ||
| seen := make(map[string]bool) | ||
| var validInstalls []string | ||
| for _, path := range candidates { | ||
| key := strings.ToLower(filepath.Clean(path)) | ||
| if seen[key] { | ||
| continue | ||
| } | ||
| seen[key] = true | ||
|
|
||
| if d.validateEclipseInstall(path) { | ||
| validInstalls = append(validInstalls, path) | ||
| } | ||
| } | ||
|
|
||
| // Stage 6: Enumerate plugins from each validated install | ||
| pluginSeen := make(map[string]bool) | ||
| var results []model.Extension | ||
| for _, installDir := range validInstalls { | ||
| plugins := d.enumerateEclipsePlugins(installDir) | ||
| for _, p := range plugins { | ||
| dedupKey := p.ID + "@" + p.Version | ||
| if pluginSeen[dedupKey] { | ||
| continue | ||
| } | ||
| pluginSeen[dedupKey] = true | ||
| results = append(results, p) | ||
| } | ||
| } | ||
|
|
||
| return results | ||
| } |
There was a problem hiding this comment.
This PR introduces a fairly complex Windows Eclipse detection pipeline (candidate gathering, validation, p2 director parsing, bundles.info parsing, dropins scanning), but there are no new unit tests covering it. Since internal/detector already has extensive test coverage, please add focused tests for the key behaviors (candidate validation, p2 output parsing/classification, bundles.info parsing, and dropins handling) to prevent regressions.
| case arg == "--include-bundled-plugins": | ||
| cfg.IncludeBundledPlugins = true |
There was a problem hiding this comment.
The new --include-bundled-plugins flag is parsed, but it isn’t shown in printHelp() under Options. This makes the flag hard to discover and can lead to confusion when users run --help.
- Move filterUserInstalledExtensions to model package (was duplicated) - Thread context.Context through Eclipse detection pipeline (was using context.Background()) - Drive letter probes check if drive exists before probing (avoids network drive timeouts) - Fix eclipseBundledPrefixes comment to match actual Source values - Add --include-bundled-plugins to help text - Add 14 unit tests for Eclipse detection pipeline (validation, bundles.info parsing, p2 director output parsing, dropins, etc.)
What does this PR do?
Type of change
Testing
./stepsecurity-dev-machine-guard --verbose./stepsecurity-dev-machine-guard --json | python3 -m json.toolmake lintmake testRelated Issues