Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
b6ed57a
feat: initial work
nick-y-snyk Dec 15, 2025
3865155
chore: use shared settings-fallback.html and add pipeline job to keep…
andrewrobinsonhodges-snyk Dec 15, 2025
3e659ed
feat: migrate to modal window
nick-y-snyk Dec 16, 2025
2a342ac
feat: sync HTML settings with WinForms controls via BaseSnykUserControl
nick-y-snyk Dec 18, 2025
90a8c80
feat: improve HTML settings theming and CSS variable handling
nick-y-snyk Dec 18, 2025
1b3d3e5
rollback: InternalAutoScan
nick-y-snyk Dec 19, 2025
4196819
fix: camelCasing json dict keys + migrate to data class for
nick-y-snyk Dec 19, 2025
702daf9
feat: set token on auth
nick-y-snyk Jan 5, 2026
cedeeb8
fix: VS Designer
nick-y-snyk Jan 5, 2026
f9709ca
feat: add fallback form support with CLI-only mode and auto-save
nick-y-snyk Jan 5, 2026
f4bccef
fix: use ToString() to access AuthenticationToken value
nick-y-snyk Jan 5, 2026
06a388e
refactor: rename CliDownloadUrl to CliBaseDownloadURL
nick-y-snyk Jan 5, 2026
9232e66
progress
nick-y-snyk Jan 8, 2026
68288be
fix: back to real settings page
rrama Jan 8, 2026
08d9ccd
Merge branch 'main' into feat/IDE-1458
nick-y-snyk Jan 9, 2026
37bee04
feat: add debug HTML settings page for rapid dev
rrama Jan 9, 2026
f178ac2
fix: disable false DPI awareness
rrama Jan 9, 2026
9aac05c
refactor: address PR review feedback
rrama Jan 14, 2026
a63b385
chore: missed PR review comment
rrama Jan 15, 2026
ef5956d
fix: use correct cancellation token
rrama Jan 16, 2026
5c7eb65
chore: remove unused tooltips from scan config
rrama Jan 16, 2026
ca7bcac
chore: Changelog
rrama Jan 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/resource-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
# Add each resource as a key, value pair, mapping the local resource to the reference file (which should be stored in the language server repository). For example:
# resources["<path_to_local_file>"]="<url_of_reference_file>"
resources["Snyk.VisualStudio.Extension.2022/Resources/ScanSummaryInit.html"]="https://raw.githubusercontent.com/snyk/snyk-ls/refs/heads/main/shared_ide_resources/ui/html/ScanSummaryInit.html"
resources["Snyk.VisualStudio.Extension.2022/Resources/settings-fallback.html"]="https://raw.githubusercontent.com/snyk/snyk-ls/refs/heads/main/shared_ide_resources/ui/html/settings-fallback.html"
for key in ${!resources[@]}; do
candidate=$(sha512sum $key | awk {'print $1'})
candidate=${candidate:="null"}
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Snyk Security Changelog

## [2.7.0]
### Changed
- Added support for improved Settings UI for simpler configuration of Snyk settings (experimental).
- Automatic organization configuration is now enabled by default.
- Bump LS protocol version to 22.

## [2.6.0]
### Changed
- Organization setting can now also be set in solution-specific settings (experimental), allowing different organizations per solution/project.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ public LatestReleaseInfo GetLatestReleaseInfo()
{
Logger.Information("Get latest CLI release info");

var latestReleaseVersionUrl = string.Format(LatestReleaseVersionUrlScheme, SnykOptions.CliDownloadUrl, SnykOptions.CliReleaseChannel);
var latestReleaseVersionUrl = string.Format(LatestReleaseVersionUrlScheme, SnykOptions.CliBaseDownloadURL, SnykOptions.CliReleaseChannel);
var latestVersion = webClient.DownloadString(latestReleaseVersionUrl).Replace("\n", string.Empty);

var latestReleaseDownloadUrl = string.Format(LatestReleaseDownloadUrlScheme, SnykOptions.CliDownloadUrl, "v"+latestVersion);
var latestReleaseDownloadUrl = string.Format(LatestReleaseDownloadUrlScheme, SnykOptions.CliBaseDownloadURL, "v"+latestVersion);

return new LatestReleaseInfo
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
using System.Collections.Generic;
// ABOUTME: This file defines initialization options and configuration structures for the Snyk Language Server protocol
// ABOUTME: It contains data models for folder configs, scan commands, and initialization parameters sent to the Language Server
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Snyk.VisualStudio.Extension.Language
{
/// <summary>
/// CamelCase naming strategy that preserves dictionary keys as-is.
/// </summary>
public class CamelCasePreserveDictionaryKeysNamingStrategy : CamelCaseNamingStrategy
Comment thread
nick-y-snyk marked this conversation as resolved.
{
public CamelCasePreserveDictionaryKeysNamingStrategy() : base(processDictionaryKeys: false, overrideSpecifiedNames: false)
{
}
}

[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
public class SnykLsInitializationOptions
{
Expand Down Expand Up @@ -38,14 +50,18 @@ public class SnykLsInitializationOptions
public string OutputFormat { get; set; }
public string EnableDeltaFindings { get; set; }
public List<FolderConfig> FolderConfigs { get; set; }
public string CliBaseDownloadUrl { get; set; }
public int? RiskScoreThreshold { get; set; }
Comment thread
nick-y-snyk marked this conversation as resolved.
}

[JsonObject(NamingStrategyType = typeof(CamelCasePreserveDictionaryKeysNamingStrategy))]
Comment thread
nick-y-snyk marked this conversation as resolved.
public class FolderConfig
{
public string FolderPath { get; set; }
public string BaseBranch { get; set; }
public List<string> LocalBranches { get; set; }
public List<string> AdditionalParameters { get; set; }
public string AdditionalEnv { get; set; }
public string ReferenceFolderPath { get; set; }
public Dictionary<string, ScanCommandConfig> ScanCommandConfig { get; set; }
public string PreferredOrg { get; set; }
Expand All @@ -59,10 +75,13 @@ public void SetScanCommandConfig(Dictionary<string, ScanCommandConfig> scanComma
}
}

[JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))]
Comment thread
nick-y-snyk marked this conversation as resolved.
public class ScanCommandConfig
{
// Add properties as needed based on the Java ScanCommandConfig class
// This is a placeholder for now - can be extended with specific properties
public string PreScanCommand { get; set; }
public bool PreScanOnlyReferenceFolder { get; set; }
public string PostScanCommand { get; set; }
public bool PostScanOnlyReferenceFolder { get; set; }
}

public class FolderConfigsParam
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ public interface ILanguageClientManager
void FireOnLanguageClientNotInitializedAsync();
Task InvokeReportAnalyticsAsync(IAbstractAnalyticsEvent analyticsEvent, CancellationToken cancellationToken);
Task<FeatureFlagResponse> InvokeGetFeatureFlagStatusAsync(string featureFlagName, CancellationToken cancellationToken);
Task<string> GetConfigHtmlAsync(CancellationToken cancellationToken);
}
}
69 changes: 69 additions & 0 deletions Snyk.VisualStudio.Extension.2022/Language/LsAnalysisResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class AdditionalData
public IList<int> Rows { get; set; }
public bool IsSecurityType { get; set; }
public int PriorityScore { get; set; }
public int RiskScore { get; set; }
public bool HasAIFix { get; set; }
public IList<DataFlow> DataFlow { get; set; }

Expand Down Expand Up @@ -139,6 +140,74 @@ public class Issue
public IgnoreDetails IgnoreDetails { get; set; }
public AdditionalData AdditionalData { get; set; }

/// <summary>
/// Product type for this issue (code, oss, iac).
/// Used to determine which score field to use for priority calculation.
/// </summary>
[JsonIgnore]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why ignored ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It is not in the JSON sent from the LS, it is calculated using the JSON, but not from it directly.

public string Product { get; set; }

/// <summary>
/// Gets the priority for sorting issues.
/// Uses severity as primary key (Critical > High > Medium > Low),
/// with score as tiebreaker within same severity level.
/// </summary>
[JsonIgnore]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same here

Copy link
Copy Markdown
Contributor

@rrama rrama Jan 15, 2026

Choose a reason for hiding this comment

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

It is not in the JSON sent from the LS, it is calculated using the JSON, but not from it directly.

public int Priority
{
get
{
int severityPriority = GetSeverityPriority(Severity);
int scoreComponent = GetScoreComponent();
return severityPriority + scoreComponent;
}
}

/// <summary>
/// Maps severity to millions (4M for critical, 3M for high, 2M for medium, 1M for low).
/// Using millions ensures severity always takes precedence over score-based tiebreakers,
/// since scores are typically in the hundreds/thousands range.
/// </summary>
private int GetSeverityPriority(string severity)
{
if (string.IsNullOrEmpty(severity))
return 0;

// Case-insensitive comparison
switch (severity.ToLowerInvariant())
Comment thread
nick-y-snyk marked this conversation as resolved.
{
case "critical":
return 4_000_000;
Comment thread
nick-y-snyk marked this conversation as resolved.
case "high":
return 3_000_000;
case "medium":
return 2_000_000;
case "low":
return 1_000_000;
default:
return 0;
}
}

private int GetScoreComponent()
{
if (AdditionalData == null)
return 0;

// OSS and IaC use riskScore, Code uses priorityScore
if (Product == Snyk.VisualStudio.Extension.Product.Oss ||
Product == Snyk.VisualStudio.Extension.Product.Iac)
{
return AdditionalData.RiskScore;
}
else if (Product == Snyk.VisualStudio.Extension.Product.Code)
{
return AdditionalData.PriorityScore;
}

throw new InvalidOperationException($"Unknown product type: {Product}");
}

public string GetDisplayTitle() => string.IsNullOrEmpty(this.Title) ? this.AdditionalData?.Message : this.Title;

public bool HasFix()
Expand Down
3 changes: 2 additions & 1 deletion Snyk.VisualStudio.Extension.2022/Language/LsConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public static class LsConstants
{
public const string ProtocolVersion = "21";
public const string ProtocolVersion = "22";

// Notifications
public const string SnykHasAuthenticated = "$/snyk.hasAuthenticated";
Expand All @@ -21,6 +21,7 @@ public static class LsConstants

public const string SnykWorkspaceScan = "snyk.workspace.scan";
public const string SnykWorkspaceFolderScan = "snyk.workspaceFolder.scan";
public const string SnykWorkspaceConfiguration = "snyk.workspace.configuration";
public const string SnykSastEnabled = "snyk.getSettingsSastEnabled";
public const string SnykLogin = "snyk.login";
public const string SnykLogout = "snyk.logout";
Expand Down
17 changes: 15 additions & 2 deletions Snyk.VisualStudio.Extension.2022/Language/LsSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Linq;
// ABOUTME: This file converts Visual Studio IDE settings into Language Server initialization options
// ABOUTME: It bridges the gap between IDE configuration and the Snyk Language Server protocol format
using System.Linq;
using Microsoft.VisualStudio.Shell;
using Snyk.VisualStudio.Extension.CLI;
using Snyk.VisualStudio.Extension.Service;
Expand Down Expand Up @@ -37,6 +39,14 @@ public SnykLsInitializationOptions GetInitializationOptions()
OpenIssues = options.OpenIssuesEnabled,
IgnoredIssues = options.IgnoredIssuesEnabled,
},
FilterSeverity = new FilterSeverityOptions
{
Critical = options.FilterCritical,
Comment thread
nick-y-snyk marked this conversation as resolved.
High = options.FilterHigh,
Medium = options.FilterMedium,
Low = options.FilterLow,
},
// Use InternalAutoScan, as it starts off as false and will delay us telling LS about our actual AutoMode until we are actually ready to scan.
ScanningMode = options.InternalAutoScan ? "auto" : "manual",
Comment thread
nick-y-snyk marked this conversation as resolved.
#pragma warning disable VSTHRD104
AdditionalParams = ThreadHelper.JoinableTaskFactory.Run(() => this.serviceProvider.SnykOptionsManager.GetAdditionalOptionsAsync()),
Expand All @@ -53,7 +63,10 @@ public SnykLsInitializationOptions GetInitializationOptions()
OutputFormat = "plain",
DeviceId = options.DeviceId,
EnableDeltaFindings = options.EnableDeltaFindings.ToString().ToLower(),
FolderConfigs = options.FolderConfigs
FolderConfigs = options.FolderConfigs,
CliBaseDownloadUrl = options.CliBaseDownloadURL,
AdditionalEnv = options.AdditionalEnv,
RiskScoreThreshold = options.RiskScoreThreshold
};
return initializationOptions;
}
Expand Down
13 changes: 13 additions & 0 deletions Snyk.VisualStudio.Extension.2022/Language/SnykLanguageClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,19 @@ public async Task InvokeReportAnalyticsAsync(IAbstractAnalyticsEvent analyticsEv
await InvokeWithParametersAsync<object>(LsConstants.WorkspaceExecuteCommand, param, cancellationToken);
}

/// <summary>
/// Retrieves HTML configuration UI from the Language Server.
/// Returns null if LS is not available or command fails.
/// </summary>
public async Task<string> GetConfigHtmlAsync(CancellationToken cancellationToken)
{
var param = new LSP.ExecuteCommandParams
{
Command = LsConstants.SnykWorkspaceConfiguration
};
return await InvokeWithParametersAsync<string>(LsConstants.WorkspaceExecuteCommand, param, cancellationToken);
}

public async Task<object> DidChangeConfigurationAsync(CancellationToken cancellationToken)
{
if (!IsReady) return default;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
// ABOUTME: This file implements custom JSON-RPC message handlers for the Snyk Language Client
// ABOUTME: It processes diagnostics, authentication, and scan results from the Language Server
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -10,6 +12,7 @@
using Snyk.VisualStudio.Extension.Authentication;
using Snyk.VisualStudio.Extension.Extension;
using Snyk.VisualStudio.Extension.Service;
using Snyk.VisualStudio.Extension.Settings;

namespace Snyk.VisualStudio.Extension.Language
{
Expand Down Expand Up @@ -186,6 +189,15 @@ public async Task OnFolderConfig(JToken arg)

serviceProvider.SnykOptionsManager.Save(serviceProvider.Options, false);

// Trigger first scan after folder config is received.
//
// AutoScan vs InternalAutoScan vs ScanningMode:
// - AutoScan: Persisted user preference ("I want auto-scanning")
// - InternalAutoScan: Runtime flag, starts false each session to prevent scanning until we are actually ready.
// This controls ScanningMode, the string sent to LS ("auto"/"manual").
//
// We always start with InternalAutoScan=false and therefore ScanningMode="manual" during LS initialization to prevent the LS from auto-scanning before we are fully ready.
// Now folder configs have arrived, we can set InternalAutoScan=AutoScan and trigger the first scan if necessary.
if (serviceProvider.Options.AutoScan)
{
var isFolderTrusted = await this.serviceProvider.TasksService.IsFolderTrustedAsync();
Expand All @@ -195,7 +207,10 @@ public async Task OnFolderConfig(JToken arg)

if (!serviceProvider.Options.InternalAutoScan)
{
// AutoScan is enabled but we haven't triggered the first scan yet (InternalAutoScan is still false).
// So set InternalAutoScan=true, update LS with the true ScanningMode ("auto") and trigger the first scan.
serviceProvider.Options.InternalAutoScan = true;
await serviceProvider.LanguageClientManager.DidChangeConfigurationAsync(SnykVSPackage.Instance.DisposalToken);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is this needed here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I added a bunch of comments above explaining the logic, please verify you are happy with them.

serviceProvider.TasksService.ScanAsync().FireAndForget();
Comment thread
nick-y-snyk marked this conversation as resolved.
}
}
Expand Down Expand Up @@ -265,6 +280,9 @@ public async Task OnHasAuthenticated(JToken arg)

await serviceProvider.GeneralOptionsDialogPage.HandleAuthenticationSuccess(token, apiUrl);

// Notify HTML settings window of auth token change
HtmlSettingsWindow.Instance?.UpdateAuthToken(token);

if (!serviceProvider.Options.ApiToken.IsValid())
return;

Expand Down
Loading
Loading