Skip to content

feat: verify CLI protocol version on LS start [IDE-1652]#391

Open
nick-y-snyk wants to merge 34 commits into
mainfrom
feat/IDE-1652-cli-protocol-version-gate
Open

feat: verify CLI protocol version on LS start [IDE-1652]#391
nick-y-snyk wants to merge 34 commits into
mainfrom
feat/IDE-1652-cli-protocol-version-gate

Conversation

@nick-y-snyk
Copy link
Copy Markdown
Contributor

Description

Adds verifyCliProtocolVersion() called from SnykLanguageServer.start() after the CLI path is validated. If the CLI reports a protocol version that doesn't match REQUIRED_LS_PROTOCOL_VERSION, the plugin logs and shows a user-visible error and aborts LS startup. Non-parseable output is treated as non-fatal (logged, startup continues).

Matches the equivalent gate added in VSCode extension PR #733. Part of IDE-1652.

Checklist

Screenshots / GIFs

N/A — no UI change.

nick-y-snyk and others added 18 commits March 27, 2026 13:35
…nfig management [IDE-1652]

Implement Story 1.1: Configuration reception, storage, and persistence.
Adds new message types (ConfigSetting, LspFolderConfig, LspConfigurationParam),
FolderConfigSettings singleton for thread-safe folder config storage, and
snykConfiguration handler on SnykExtendedLanguageClient that persists global
settings and manages folder configs from the Language Server.
…[IDE-1652]

- Add explicitChanges tracking to Preferences (mark/check/clear/persist with flush)
- Refactor LsConfigurationUpdater to produce LspConfigurationParam instead of old Settings type
- Add ConfigSetting.outbound() factory for value+changed only outbound settings
- Update SnykLanguageServer.getInitializationOptions() to use buildConfigurationParam()
- Add convertInboundValue() for scanning mode format conversion
- Synchronize FolderConfigSettings.setInstance(), add null guards on explicit-change methods
- Extract 21 LS setting key constants to LsSettingsKeys
- Add 21 new tests covering change tracking, payload construction, and round-trip semantics
… getCurrentSettings [IDE-1652]

Migrate HTMLSettingsPreferencePage, NativeProjectPropertyPage, ReferenceChooserDialog,
and SnykToolView from old FolderConfigs/FolderConfig to FolderConfigSettings/LspFolderConfig.
Add bridge in folderConfig() to populate both old and new config systems during transition.
Remove unused getCurrentSettings() from LsConfigurationUpdater and its test.
…v25 [IDE-1652]

Delete FolderConfig, FolderConfigsParam, FolderConfigs, and Settings classes.
Remove old $/snyk.folderConfigs handler and SNYK_FOLDER_CONFIG constant.
Bump REQUIRED_LS_PROTOCOL_VERSION from 23 to 25.
Resolve review findings: token changed flag uses isExplicitlyChanged,
atomic computeFolderConfig for consumer writes, additionalParameters
returns List<String>, folderPath set on empty configs.
…aths [IDE-1652]

Replace prefs.store() with prefs.storeAndTrackChange() in all user-facing
settings pages (HTMLSettingsPreferencePage, PreferencesPage, BaseHandler,
SnykWizard, SummaryBrowserHandler) so that user edits are tracked as
explicitly changed. Wire folder-scope consumers to use
withSettingIfChanged instead of withSetting. Update LsConfigurationUpdater
to propagate isExplicitlyChanged for all user-changeable settings in the
outbound payload. Remove unused PREF_TO_LS_KEY field.
…user overrides [IDE-1652]

storeAndTrackChange compared against hardcoded empty string default
instead of the registered store default, causing settings with non-empty
defaults to be falsely marked as user-overridden on first save.

HTML settings form now handles null values from LS as reset signals,
calling clearExplicitlyChanged to remove the user override before the
next didChangeConfiguration is sent to the LS.
Use Map interface instead of ConcurrentHashMap for field declaration.
Narrow catch clause from Exception to IllegalArgumentException.
…ettings [IDE-1652]

Replaces the hardcoded inline outbound block in LsConfigurationUpdater and the 8-key
LS_TO_PREF_KEY map in SnykExtendedLanguageClient with a shared LsSettingsRegistry.
Both outbound (IDE→LS) and inbound ($/snyk.configuration) now use the same binding.

Also adds snyk_secrets_enabled (outbound + inbound) which was previously missing.
Adds verifyCliProtocolVersion() called from start() after path validation.
Aborts with IOException + user-visible message when CLI version doesn't match
REQUIRED_LS_PROTOCOL_VERSION. Non-fatal on unparseable output.
…ing [IDE-1652]

addAll() previously called configs.clear() before re-adding, which wiped user overrides
(changed=true) in flight. Now merges per-folder: incoming wins for unchanged keys,
user-changed keys survive; folders absent from incoming payload are removed.
…urationParam [IDE-1652]

Drops snake_case @SerializedName annotations from ConfigSetting, LspFolderConfig,
and LspConfigurationParam — LS now uses camelCase JSON keys matching Java field names.
Adds metadata fields (requiredProtocolVersion, integrationName, trustedFolders, etc.)
to LspConfigurationParam for InitializationOptions serialization.
Four bugs found by cross-checking with snyk-ls internal/types/ldx_sync_config.go:

1. scan_automatic: was sending string "automatic"/"manual"; LS GetBool() only returns
   true for string "true" → auto-scan always false. Now sends Java Boolean true/false.

2. enabled_severities: LS has no such key. Uses individual severity_filter_critical/high/
   medium/low boolean keys. Replace composite map with 4 individual settings.

3. enableTelemetry: no such key in LS. Was sending camelCase "enableTelemetry" which LS
   silently ignored. Remove from outbound registry entirely.

4. additional_environment: LS registers this as folder-scope only, not global. Remove from
   global settings outbound; stays in folderConfigs[].settings path.
…[IDE-1652]

Pull latest from snyk-ls@270c5ad4 (feat(config): centralized configuration [IDE-1786]).
Adds custom CLI channel UI, updated CSS variables, and other config dialog changes.
UseVarargs, ArrayIsStoredDirectly, MethodReturnsInternalArray on trustedFolders.
Use varargs setter, clone on store and on return.
Moves verifyCliProtocolVersion() and its tests to feat/IDE-1652-cli-protocol-version-gate branch.
@nick-y-snyk nick-y-snyk requested a review from a team as a code owner May 15, 2026 11:20
@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented May 15, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@snyk-pr-review-bot

This comment has been minimized.

@nick-y-snyk nick-y-snyk changed the base branch from main to feat/IDE-1652 May 15, 2026 11:27
ProcessBuilder pb = new ProcessBuilder(cliPath, "language-server", "--protocolVersion");
pb.redirectErrorStream(true);
Process proc = pb.start();
String output = new String(proc.getInputStream().readAllBytes()).trim();
@snyk-pr-review-bot

This comment has been minimized.

@snyk-pr-review-bot

This comment has been minimized.

@snyk-pr-review-bot

This comment has been minimized.

…an triggers [IDE-1652]

Replace the flat LsSettingsKeys string constants with a typed LsKey enum so all
LS protocol key references are compiler-checked and co-located with their string
value. LsSettingsRegistry ENTRIES is now an EnumMap<LsKey, Entry> with insertion
order preserved; BY_LS_KEY retains the string-keyed lookup for inbound parsing.

Entry gains alwaysChanged (replaces the outbound-side of alwaysFixed) and
useInFallbackForm flags. TOKEN is now alwaysChanged=true so it is always sent
with changed=true without requiring explicit-change tracking. Inbound deserializers
for risk_score_threshold and trusted_folders are added to BY_LS_KEY so
persistGlobalSettings can handle them directly; the alwaysFixed guard is removed
from that method since prefKey==null already excludes hardcoded entries.

HTMLSettingsPreferencePage.parseAndSaveConfig is rewritten as a registry-driven
JsonNode loop, removing the IdeConfigData typed record and its fragile field-by-
field mapping. FolderConfigSettings.createEmptyConfig switches to direct setters
instead of round-tripping through Gson. LspFolderConfig gains setFolderPath and
setSettings mutators to support this.

snykConfiguration() restores the two behaviours dropped when folderConfig() was
removed: triggerScan(null) when SCANNING_MODE_AUTOMATIC is set, and
toolView.refreshDeltaReference() after folder configs are stored. These are
needed for LDX-sync post-startup pushes where the LS does not trigger a scan.

All LsSettingsKeys references in LsConfigurationUpdater, LsConfigurationUpdaterTest,
and SnykLanguageServerTest are updated to LsKey.FOO.key.
…pattern [IDE-1652]

Move RISK_SCORE_THRESHOLD, SEVERITY_FILTER_*, and TRUSTED_FOLDERS from inline
blocks in LsConfigurationUpdater into LsSettingsRegistry.ENTRIES so all three
data paths (IDE→LS, LS→IDE, form→IDE) are driven by pure registry loops.

- LsSettingsRegistry: add additionalChangedPrefKeys (severity shared-changed flag)
  and formDeserializer (form-specific JSON→pref conversion) to Entry; move all
  previously inline-only keys into ENTRIES; BY_LS_KEY now auto-populated from ENTRIES
- LsConfigurationUpdater: remove 3 inline blocks; single generic loop with
  additionalChangedPrefKeys OR-ing for changed flag
- HTMLSettingsPreferencePage: remove FALLBACK_FORM_KEYS set, SCANNING_MODE
  special-case, and post-loop severity/risk/trusted blocks; pure registry loop
  using entry.useInFallbackForm and entry.formDeserializer
- FolderConfigSettings.addAll: remove mergeConfigs — LS values are authoritative,
  incoming folder config replaces existing entirely (matches VSCode behaviour)
Replace per-key if-blocks with a generic folderNode.fields() loop.
scan_command_config stays special-cased for typed deserialization; all
other fields use withSetting(key, value, true) directly, matching
VSCode's setSetting pattern. Removes convertScanCommandConfig helper.
- HTMLSettingsPreferencePage: replace manual ScanCommandConfig construction
  loop with objectMapper.convertValue() — fixes AvoidInstantiatingObjectsInLoops
- LsSettingsRegistry: suppress AvoidFieldNameMatchingMethodName on alwaysChanged
  field; extract TRUE constant for severity filter outbound defaults —
  fixes AvoidDuplicateLiterals
Resolves PMD AvoidFieldNameMatchingMethodName — field clashed with the
Entry.alwaysChanged() factory method. Rename field; factory method and
constructor parameter keep the same name.
- HTMLSettingsPreferencePageTest: update JSON keys from camelCase to
  snake_case LS key strings (cli_path, automatic_download, snyk_code_enabled
  etc); flatten filterSeverity/issueViewOptions nested objects to individual
  severity_filter_* and issue_view_* keys — matching what the LS HTML sends
- FolderConfigSettingsTest: remove addAllPreservesUserOverriddenKeys — merge
  behavior was intentionally removed; LS values are authoritative
- LsConfigurationUpdaterTest: add getPref stubs for severity filters and
  scanning_mode — registry uses getPref not getBooleanPref
…[IDE-1652]

Replace hardcoded pref key array with a stream over ENTRIES — any new
registry key is automatically tracked without a separate update here.
Two fixes to ensure user-set values survive restart:

1. parseAndSaveConfig: unconditionally mark all non-null form values as
   explicitly changed. Old equality check could skip marking when
   persistGlobalSettings had already written the same value, leaving
   explicitChanges empty on disk.

2. buildConfigurationParam: treat stored value deviating from outbound
   default as changed=true, matching IntelliJ's
   machineScopedValueDeviatesFromPluginDefaults logic. Covers values set
   via login flow (saveLoginArgs) which bypass explicit change tracking.
processFolderConfig fell through to node.asText() for array nodes,
producing an empty string for additional_parameters (sent as JSON array
by LS HTML). Add array branch that collects elements into List<String>,
matching IntelliJ's StringOrListTypeAdapter behaviour.
parseAndSaveConfig treated absent keys (n == null) and explicit null
(n.isNull()) identically — both called clearExplicitlyChanged. Since
the LS HTML sends only diff (user-changed keys), every save wiped
tracking for all untouched keys, leaving explicitChanges empty.

Split the two cases: absent = leave tracking untouched; explicit null =
clear. Matches IntelliJ SaveConfigHandler behaviour.
Adds verifyCliProtocolVersion() called from start() after path validation.
Aborts with IOException + user-visible message when CLI version doesn't match
REQUIRED_LS_PROTOCOL_VERSION. Non-fatal on unparseable output.
@nick-y-snyk nick-y-snyk force-pushed the feat/IDE-1652-cli-protocol-version-gate branch from a650f99 to 0936ad9 Compare May 15, 2026 18:36
@snyk-pr-review-bot

This comment has been minimized.

@snyk-pr-review-bot
Copy link
Copy Markdown

PR Reviewer Guide 🔍

🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Race Condition 🟠 [major]

In verifyCliProtocolVersion, proc.getInputStream().readAllBytes() is called before proc.waitFor(). This creates a race condition where the input stream might be read before the CLI process has actually written the version number to stdout. If the read returns an empty string, the subsequent Integer.parseInt(output) will throw a NumberFormatException, which is caught and logged as non-fatal, effectively bypassing the protocol version enforcement for slow-starting processes.

String output = new String(proc.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
proc.waitFor(5, TimeUnit.SECONDS);
Resource Leak 🟡 [minor]

The Process object created in verifyCliProtocolVersion is not explicitly destroyed. While the process is expected to exit after printing the version, if it hangs or takes longer than the 5-second timeout in waitFor, the process will remain running as a zombie or orphan. The code should call proc.destroy() or proc.destroyForcibly() if waitFor returns false or an exception occurs.

Process proc = pb.start();
String output = new String(proc.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
proc.waitFor(5, TimeUnit.SECONDS);
📚 Repository Context Analyzed

This review considered 7 relevant code sections from 4 files (average relevance: 1.00)

@nick-y-snyk nick-y-snyk changed the base branch from feat/IDE-1652 to main May 21, 2026 14:54
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.

3 participants