CVE-2022-24441 (published 2022-11-30T13:15Z)

The package snyk before 1.1064.0 are vulnerable to Code Injection when analyzing a project. An attacker who can convince a user to scan a malicious project can include commands in a build file such as build.gradle or gradle-wrapper.jar, which will be executed with the privileges of the application. This vulnerability may be triggered when running the the CLI tool directly, or when running a scan with one of the IDE plugins that invoke the Snyk CLI. Successful exploitation of this issue would likely require some level of social engineering - to coerce an untrusted project to be downloaded and analyzed via the Snyk CLI or opened in an IDE where a Snyk IDE plugin is installed and enabled. Additionally, if the IDE has a Trust feature then the target folder must be marked as ‘trusted’ in order to be vulnerable. **NOTE:** This issue is independent of the one reported in [CVE-2022-40764](https://security.snyk.io/vuln/SNYK-JS-SNYK-3037342), and upgrading to a fixed version for this addresses that issue as well. The affected IDE plugins and versions are: - VS Code - Affected: <=1.8.0, Fixed: 1.9.0 - IntelliJ - Affected: <=2.4.47, Fixed: 2.4.48 - Visual Studio - Affected: <=1.1.30, Fixed: 1.1.31 - Eclipse - Affected: <=v20221115.132308, Fixed: All subsequent versions - Language Server - Affected: <=v20221109.114426, Fixed: All subsequent versions

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-78
cwe_name: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
extended_description: 
		   This weakness can lead to a vulnerability in environments in which the attacker does not have direct access to the operating system, such as in web applications. Alternately, if the weakness occurs in a privileged program, it could allow the attacker to specify commands that normally would not be accessible, or to call alternate commands with privileges that the attacker does not have. The problem is exacerbated if the compromised process does not follow the principle of least privilege, because the attacker-controlled commands may run with special system privileges that increases the amount of damage.
            There are at least two subtypes of OS command injection:
               
				 The application intends to execute a single, fixed program that is under its own control. It intends to use externally-supplied inputs as arguments to that program. For example, the program might use system("nslookup [HOSTNAME]") to run nslookup and allow the user to supply a HOSTNAME, which is used as an argument. Attackers cannot prevent nslookup from executing. However, if the program does not remove command separators from the HOSTNAME argument, attackers could place the separators into the arguments, which allows them to execute their own program after nslookup has finished executing.
				 The application accepts an input that it uses to fully select which program to run, as well as which commands to use. The application simply redirects this entire command to the operating system. For example, the program might use "exec([COMMAND])" to execute the [COMMAND] that was supplied by the user. If the COMMAND is under attacker control, then the attacker can execute arbitrary commands or programs. If the command is being executed using functions like exec() and CreateProcess(), the attacker might not be able to combine multiple commands together in the same line.
               
            From a weakness standpoint, these variants represent distinct programmer errors. In the first variant, the programmer clearly intends that input from untrusted parties will be part of the arguments in the command to be executed. In the second variant, the programmer does not intend for the command to be accessible to any untrusted party, but the programmer probably has not accounted for alternate ways in which malicious attackers can provide input.
         
         

Fix information:
repo_url: https://github.com/snyk/snyk-visual-studio-plugin
commit_details: repo_url: https://github.com/snyk/snyk-visual-studio-plugin
author: Michel Kaporin
author_date: 2022-11-29 13:22:28+01:00
msg: feat: add workspace trust (#217)
repository: repo_url: https://github.com/snyk/snyk-visual-studio-plugin
repo_name: snyk/snyk-visual-studio-plugin
description: Snyk Visual Studio Extension 
repo_language: C#
owner: snyk
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,10 @@
 # Snyk Changelog
 
+## [1.1.31]
+
+### Added
+- Adds workspace trust mechanism to ensure scans are run on the trusted projects.
+
 ## [1.1.30]
 
 ### Changed
@@ -48,7 +53,7 @@
 
 ### Added
 - Organization description information in settings.
- 
+
 ### Fixed
 - Changing custom endpoint settings leads to authentication errors.
 

programming_language: Markdown
filename: Snyk.VisualStudio.Extension.2022.csproj
change_type: ModificationType.MODIFY
diff: @@ -49,6 +49,9 @@
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TrustDialogWindow.xaml.cs">
+      <DependentUpon>TrustDialogWindow.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <None Include="source.extension.vsixmanifest">
@@ -120,6 +123,12 @@
       <Name>Snyk.Common</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Page Include="TrustDialogWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+  </ItemGroup>
   <Import Project="..\Snyk.VisualStudio.Extension.Shared\Snyk.VisualStudio.Extension.Shared.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />

programming_language: PowerShell
filename: TrustDialogWindow.xaml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,59 @@
+﻿<ui:DialogWindow x:Class="Snyk.VisualStudio.Extension.TrustDialogWindow"
+                 x:Name="TrustWindow"
+                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+                 xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
+                 xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0"
+                 mc:Ignorable="d"
+                 WindowStartupLocation="CenterScreen"
+                 IsCloseButtonEnabled="True"
+                 HasHelpButton="False"
+                 MinHeight="290" Height="290"
+                 MinWidth="500" Width="500"
+                 BorderBrush="{x:Static SystemColors.WindowFrameBrush}" BorderThickness="1"
+                 WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True"
+                 xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
+                 xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
+                 toolkit:Themes.UseVsTheme="True"
+                 Title="Snyk - This folder has not been trusted"
+                 MouseDown="TrustDialogWindow_OnMouseDown">
+    <DockPanel Margin="10">
+        <Button DockPanel.Dock="Top" HorizontalAlignment="Right" Click="DoNotTrustButton_OnClick" MinWidth="1" MinHeight="1" Width="35" Margin="0" Padding="0">
+            <imaging:CrispImage Moniker="{x:Static catalog:KnownMonikers.Close}"/>
+        </Button>
+        <StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal">
+            <Button x:Name="TrustButton" Margin="5, 5" Content="Trust folder and continue" Click="TrustButton_OnClick"/>
+            <Button x:Name="DoNotTrustButton" Margin="5, 5" Content="Don't scan" Click="DoNotTrustButton_OnClick"/>
+        </StackPanel>
+        <Grid>
+            <Grid.RowDefinitions>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+            </Grid.RowDefinitions>
+            <Grid Grid.Row="0">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="5*"/>
+                </Grid.ColumnDefinitions>
+                    <imaging:CrispImage Grid.Column="0" Width="50" Moniker="{x:Static catalog:KnownMonikers.StatusSecurityWarning}"/>
+                <StackPanel VerticalAlignment="Center" Grid.Column="1" Margin="0, 0, 5, 0">
+                    <TextBlock FontSize="14">This folder has not been trusted:</TextBlock>
+                    <TextBlock FontSize="14" FontWeight="Bold" TextWrapping="Wrap" Text="{Binding ElementName=TrustWindow, Path=FolderPath}"/>
+                </StackPanel>
+            </Grid>
+            <StackPanel Grid.Row="1" Margin="5">
+                <TextBlock TextWrapping="Wrap">
+                    When scanning folder files for vulnerabilities, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan folders you trust.
+                </TextBlock>
+                <TextBlock>
+                    <LineBreak/>
+                    <Hyperlink NavigateUri="https://docs.snyk.io/ide-tools/visual-studio-extension/workspace-trust" RequestNavigate="Hyperlink_OnRequestNavigate">
+                        More information
+                    </Hyperlink>
+                </TextBlock>
+            </StackPanel>
+        </Grid>
+    </DockPanel>
+</ui:DialogWindow>
\ No newline at end of file

programming_language: PowerShell
filename: TrustDialogWindow.xaml.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,49 @@
+﻿
+namespace Snyk.VisualStudio.Extension
+{
+    using System.Diagnostics;
+    using System.Windows;
+    using System.Windows.Input;
+    using System.Windows.Navigation;
+    using Microsoft.VisualStudio.PlatformUI;
+
+    /// <summary>
+    /// Trusted dialog window for Visual Studio 2022.
+    /// </summary>
+    public partial class TrustDialogWindow : DialogWindow
+    {
+        public TrustDialogWindow(string folderPath)
+        {
+            this.FolderPath = folderPath;
+            this.InitializeComponent();
+        }
+
+        public string FolderPath { get; }
+
+        private void DoNotTrustButton_OnClick(object sender, RoutedEventArgs e)
+        {
+            this.DialogResult = false;
+            this.Close();
+        }
+
+        private void TrustButton_OnClick(object sender, RoutedEventArgs e)
+        {
+            this.DialogResult = true;
+            this.Close();
+        }
+
+        private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+        {
+            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
+            e.Handled = true;
+        }
+
+        private void TrustDialogWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.ChangedButton == MouseButton.Left)
+            {
+                this.DragMove();
+            }
+        }
+    }
+}

programming_language: C#
filename: ISnykServiceProvider.cs
change_type: ModificationType.MODIFY
diff: @@ -38,6 +38,8 @@
         /// </summary>
         ISolutionService SolutionService { get; }
 
+        IWorkspaceTrustService WorkspaceTrustService { get; }
+
         /// <summary>
         /// Gets Tasks service instance.
         /// </summary>

programming_language: C#
filename: IWorkspaceTrustService.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,9 @@
+﻿namespace Snyk.VisualStudio.Extension.Shared.Service
+{
+    public interface IWorkspaceTrustService
+    {
+        bool IsFolderTrusted(string absoluteFolderPath);
+
+        void AddFolderToTrusted(string absoluteFolderPath);
+    }
+}

programming_language: C#
filename: SnykService.cs
change_type: ModificationType.MODIFY
diff: @@ -52,6 +52,8 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
 
         private ISentryService sentryService;
 
+        private IWorkspaceTrustService workspaceTrustService;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="SnykService"/> class.
         /// </summary>
@@ -68,6 +70,11 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
         /// </summary>
         public ISolutionService SolutionService => SnykSolutionService.Instance;
 
+        /// <summary>
+        /// Gets solution service.
+        /// </summary>
+        public IWorkspaceTrustService WorkspaceTrustService => this.workspaceTrustService;
+
         /// <summary>
         /// Gets Tasks service.
         /// </summary>
@@ -241,6 +248,7 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
                 this.dte = await this.serviceProvider.GetServiceAsync(typeof(DTE)) as DTE2;
                 await SnykSolutionService.Instance.InitializeAsync(this);
                 this.tasksService = SnykTasksService.Instance;
+                this.workspaceTrustService = new WorkspaceTrustService(this.UserStorageSettingsService);
 
                 NotificationService.Initialize(this);
                 VsStatusBar.Initialize(this);

programming_language: C#
filename: SnykTasksService.cs
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,9 @@
     using System.Collections.Generic;
     using System.Threading;
     using System.Threading.Tasks;
+    using Community.VisualStudio.Toolkit;
     using Microsoft.VisualStudio.Shell;
+    using Microsoft.VisualStudio.Shell.Interop;
     using Serilog;
     using Snyk.Analytics;
     using Snyk.Code.Library.Domain.Analysis;
@@ -12,6 +14,7 @@
     using Snyk.VisualStudio.Extension.Shared.CLI;
     using Snyk.VisualStudio.Extension.Shared.CLI.Download;
     using Snyk.VisualStudio.Extension.Shared.Service.Domain;
+    using Snyk.VisualStudio.Extension.Shared.UI;
     using static Snyk.VisualStudio.Extension.Shared.CLI.Download.SnykCliDownloader;
     using Task = System.Threading.Tasks.Task;
 
@@ -198,7 +201,6 @@
         public async Task ScanAsync()
         {
             Logger.Information("Enter Scan method");
-
             try
             {
                 var selectedFeatures = await this.GetFeaturesSettingsAsync();
@@ -212,6 +214,13 @@
                     return;
                 }
 
+                var isFolderTrusted = await this.IsFolderTrustedAsync();
+                if (!isFolderTrusted)
+                {
+                    Logger.Information("Workspace folder was not trusted for scanning.");
+                    return;
+                }
+
                 this.serviceProvider.AnalyticsService.LogAnalysisIsTriggeredEvent(this.GetSelectedFeatures(selectedFeatures));
 
                 var ossScanTask = this.ScanOssAsync(selectedFeatures);
@@ -225,6 +234,41 @@
             }
         }
 
+        /// <summary>
+        /// Checks if opened solution folder is trusted. If not, prompts a user with trust permission.
+        /// </summary>
+        /// <returns>Folder is trusted or not.</returns>
+        public async Task<bool> IsFolderTrustedAsync()
+        {
+            var solutionFolderPath = await this.serviceProvider.SolutionService.GetSolutionFolderAsync();
+            var isFolderTrusted = this.serviceProvider.WorkspaceTrustService.IsFolderTrusted(solutionFolderPath);
+
+            if (string.IsNullOrEmpty(solutionFolderPath) || isFolderTrusted)
+            {
+                return true;
+            }
+
+            var trustDialog = new TrustDialogWindow(solutionFolderPath);
+            var trusted = trustDialog.ShowModal();
+
+            if (trusted != true)
+            {
+                return false;
+            }
+
+            try
+            {
+                this.serviceProvider.WorkspaceTrustService.AddFolderToTrusted(solutionFolderPath);
+                Logger.Information("Workspace folder was trusted: {SolutionFolderPath}", solutionFolderPath);
+                return true;
+            }
+            catch (ArgumentException e)
+            {
+                Logger.Error(e, "Failed to add folder to trusted list.");
+                throw;
+            }
+        }
+
         /// <summary>
         /// Start a CLI download task in background thread.
         /// </summary>

programming_language: C#
filename: WorkspaceTrustService.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,89 @@
+﻿namespace Snyk.VisualStudio.Extension.Shared.Service
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using Serilog;
+    using Snyk.Common;
+    using Snyk.VisualStudio.Extension.Shared.Settings;
+
+    public class WorkspaceTrustService : IWorkspaceTrustService
+    {
+        private static readonly ILogger Logger = LogManager.ForContext<WorkspaceTrustService>();
+
+        private readonly IUserStorageSettingsService settingsService;
+
+        public WorkspaceTrustService(IUserStorageSettingsService settingsService)
+        {
+            this.settingsService = settingsService;
+        }
+
+        public void AddFolderToTrusted(string absoluteFolderPath)
+        {
+            if (!Path.IsPathRooted(absoluteFolderPath))
+            {
+                throw new ArgumentException("Trusted folder path provided is not absolute.");
+            }
+
+            if (!Directory.Exists(absoluteFolderPath))
+            {
+                throw new ArgumentException("Trusted folder doesn't exist.");
+            }
+
+            try
+            {
+                var trustedFolders = this.settingsService.TrustedFolders;
+                trustedFolders.Add(absoluteFolderPath);
+                this.settingsService.TrustedFolders = trustedFolders;
+            }
+            catch (Exception e)
+            {
+                Logger.Error(e, "Failed to add a folder to trusted.");
+            }
+        }
+
+        public bool IsFolderTrusted(string absoluteFolderPath)
+        {
+            var trustedFolders = this.settingsService.TrustedFolders;
+
+            foreach (var trustedFolder in trustedFolders)
+            {
+                if (this.IsSubFolderOrEqual(trustedFolder, absoluteFolderPath))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Verify if subfolder is rooted at parent path.
+        /// </summary>
+        /// <param name="parentPath">Parent path to check against.</param>
+        /// <param name="childPath">Subfolder path to verify.</param>
+        /// <returns>Returns true if childPath is subfolder of parentPath, or equal to it.</returns>
+        private bool IsSubFolderOrEqual(string parentPath, string childPath)
+        {
+            var parentUri = new Uri(parentPath);
+            if (new Uri(childPath).Equals(parentUri))
+            {
+                return true;
+            }
+
+            var childUri = new DirectoryInfo(childPath).Parent;
+            while (childUri != null)
+            {
+                if (new Uri(childUri.FullName).Equals(parentUri))
+                {
+                    return true;
+                }
+
+                childUri = childUri.Parent;
+            }
+
+            return false;
+        }
+    }
+}

programming_language: C#
filename: IUserStorageSettingsService.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,9 @@
+﻿namespace Snyk.VisualStudio.Extension.Shared.Settings
+{
+    using System.Collections.Generic;
+
+    public interface IUserStorageSettingsService
+    {
+        ISet<string> TrustedFolders { get; set; }
+    }
+}

programming_language: C#
filename: SnykSettings.cs
change_type: ModificationType.MODIFY
diff: @@ -61,8 +61,13 @@
         public bool BinariesAutoUpdateEnabled { get; set; } = true;
 
         /// <summary>
-        /// Gets or sets the value of the custom CLI path
+        /// Gets or sets the value of the custom CLI path.
         /// </summary>
         public string CustomCliPath { get; set; } = string.Empty;
+
+        /// <summary>
+        /// Gets or sets an array of workspace trusted folders.
+        /// </summary>
+        public ISet<string> TrustedFolders { get; set; } = new HashSet<string>();
     }
 }
\ No newline at end of file

programming_language: C#
filename: SnykUserStorageSettingsService.cs
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,7 @@
 ﻿namespace Snyk.VisualStudio.Extension.Shared.Settings
 {
     using System;
+    using System.Collections.Generic;
     using System.Threading.Tasks;
     using Serilog;
     using Snyk.Common;
@@ -9,7 +10,7 @@
     /// <summary>
     /// Service for solution settings.
     /// </summary>
-    public class SnykUserStorageSettingsService
+    public class SnykUserStorageSettingsService : IUserStorageSettingsService
     {
         private static readonly ILogger Logger = LogManager.ForContext<SnykUserStorageSettingsService>();
 
@@ -51,6 +52,20 @@
             }
         }
 
+        /// <summary>
+        /// Gets or sets trusted folders list.
+        /// </summary>
+        public ISet<string> TrustedFolders
+        {
+            get => this.LoadSettings().TrustedFolders;
+            set
+            {
+                var settings = this.LoadSettings();
+                settings.TrustedFolders = value;
+                this.settingsLoader.Save(settings);
+            }
+        }
+
         /// <summary>
         /// Get CLI additional options string.
         /// </summary>

programming_language: C#
filename: Snyk.VisualStudio.Extension.Shared.projitems
change_type: ModificationType.MODIFY
diff: @@ -31,13 +31,16 @@
     <Compile Include="$(MSBuildThisFileDirectory)Service\ApiEndpointResolver.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\ISentryService.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\ISnykApiService.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Service\IWorkspaceTrustService.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\LocalCodeEngine.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\SentryService.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\SolutionType.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\IOssService.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\OssScanException.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Service\OssService.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Service\WorkspaceTrustService.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Settings\ISnykOptions.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Settings\IUserStorageSettingsService.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Settings\SnykGeneralOptionsDialogPage.cs">
       <SubType>Component</SubType>
     </Compile>

programming_language: PowerShell
filename: MessagePanel.xaml
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,12 @@
 ﻿<UserControl x:Class="Snyk.VisualStudio.Extension.Shared.UI.Toolwindow.MessagePanel"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
-             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-             xmlns:c="clr-namespace:Snyk.VisualStudio.Extension.Shared.UI.Controls" 
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:c="clr-namespace:Snyk.VisualStudio.Extension.Shared.UI.Controls"
              xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
              toolkit:Themes.UseVsTheme="True"
-             mc:Ignorable="d" 
+             mc:Ignorable="d"
              d:DesignHeight="450" d:DesignWidth="800">
     <Grid VerticalAlignment="Center">
         <Grid.Resources>
@@ -32,11 +32,11 @@
                 <Button x:Name="runScanButton" Content="Run scan" IsDefault="True" Click="RunButton_Click" HorizontalAlignment="Center" MinWidth="80"/>
             </StackPanel>
         </StackPanel>
-        
+
         <StackPanel Name="messagePanel" Visibility="Collapsed" HorizontalAlignment="Center">
             <c:TextField x:Name="message" FontWeight="Bold"/>
         </StackPanel>
-        
+
         <StackPanel Name="overviewPanel" Visibility="Collapsed" HorizontalAlignment="Center" Orientation="Horizontal">
             <Image Source="{StaticResource SnykDogLogo}" Width="48" Margin="0,-30,0,30"/>
 
@@ -48,11 +48,17 @@
                     1. Authenticate to Snyk.io
                 </TextBlock>
                 <TextBlock>
-                    2. Analyze code for issues and vulnerabilities                
+                    2. Analyze code for issues and vulnerabilities
                 </TextBlock>
                 <TextBlock>
                     3. Improve your code and upgrade dependencies
                 </TextBlock>
+                <TextBlock Margin="0,10,0,0" TextWrapping="Wrap" Width="550">
+                    When scanning project files, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan projects you trust.
+                    <Hyperlink NavigateUri="https://docs.snyk.io/ide-tools/visual-studio-extension/workspace-trust" RequestNavigate="Hyperlink_RequestNavigate">
+                        More info
+                    </Hyperlink>
+                </TextBlock>
                 <StackPanel HorizontalAlignment="Left" Margin="0,25,0,0">
                     <Button Name="testCodeNowButton" Click="TestCodeNow_Click" Padding="20,6,20,8">
                         <Button.Resources>
@@ -60,14 +66,14 @@
                                 <Setter Property="CornerRadius" Value="3"/>
                             </Style>
                         </Button.Resources>
-                        Test code now
+                        Trust project and scan
                     </Button>
                 </StackPanel>
                 <ProgressBar Name="authenticateSnykProgressBar" Visibility="Collapsed" IsIndeterminate="True" Height="3" Padding="0,2" Margin="0, 5, 0, 0"/>
                 <TextBlock Margin="0, 20, 0, 0" FontSize="11">
                     By connecting your account with Snyk, you agree
                 </TextBlock>
-                <TextBlock FontSize="11">                    
+                <TextBlock FontSize="11">
                     to the Snyk
                     <Hyperlink NavigateUri="https://snyk.io/policies/privacy/" RequestNavigate="Hyperlink_RequestNavigate">
                         Privacy Policy

programming_language: Markdown
filename: MessagePanel.xaml.cs
change_type: ModificationType.MODIFY
diff: @@ -1,13 +1,19 @@
 ﻿namespace Snyk.VisualStudio.Extension.Shared.UI.Toolwindow
 {
+    using System;
     using System.Collections.Generic;
     using System.Diagnostics;
     using System.IO;
     using System.Threading.Tasks;
     using System.Windows;
     using System.Windows.Controls;
+    using Community.VisualStudio.Toolkit;
     using Microsoft.VisualStudio.Shell;
+    using Microsoft.VisualStudio.Shell.Interop;
     using Microsoft.VisualStudio.Threading;
+    using Serilog;
+    using Serilog.Core;
+    using Snyk.Common;
     using Snyk.VisualStudio.Extension.Shared.Service;
 
     /// <summary>
@@ -15,6 +21,7 @@
     /// </summary>
     public partial class MessagePanel : UserControl
     {
+        private static readonly ILogger Logger = LogManager.ForContext<MessagePanel>();
         private readonly IList<StackPanel> panels;
 
         /// <summary>
@@ -120,6 +127,36 @@
             this.authenticateSnykProgressBar.Visibility = Visibility.Collapsed;
             this.testCodeNowButton.IsEnabled = true;
 
+            // Add folder to trusted
+            var solutionFolderPath = await this.ServiceProvider.SolutionService.GetSolutionFolderAsync();
+            if (!string.IsNullOrEmpty(solutionFolderPath))
+            {
+                try
+                {
+                    this.ServiceProvider.WorkspaceTrustService.AddFolderToTrusted(solutionFolderPath);
+                    Logger.Information("Workspace folder was trusted: {SolutionFolderPath}", solutionFolderPath);
+                }
+                catch (ArgumentException ex)
+                {
+                    Logger.Error(ex, "Failed to add folder to trusted list.");
+                    throw ex;
+                }
+            }
+
+            // Issue scan
+            if (authenticationSucceeded)
+            {
+                var uiShell = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
+                if (uiShell != null)
+                {
+                    uiShell.PostExecCommand(
+                        SnykGuids.SnykVSPackageCommandSet,
+                        SnykGuids.RunScanCommandId,
+                        0,
+                        null);
+                }
+            }
+
             var nextPanel = authenticationSucceeded ? (ToolWindowState)RunScanState.Instance : OverviewState.Instance;
             this.Context.TransitionTo(nextPanel);
         }

programming_language: C#
filename: WorkspaceTrustServiceTest.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,149 @@
+﻿namespace Snyk.VisualStudio.Extension.Tests.Service
+{
+    using System;
+    using System.Collections.Generic;
+    using System.IO;
+    using Moq;
+    using Snyk.VisualStudio.Extension.Shared.Service;
+    using Snyk.VisualStudio.Extension.Shared.Settings;
+    using Xunit;
+
+    public class WorkspaceTrustServiceTest
+    {
+        [Fact]
+        public void WorkspaceTrustServiceTest_IsFolderTrusted_NotTrusted()
+        {
+            var trustedFolders = new HashSet<string>();
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(trustedFolders);
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = "C:\\Users\\Project";
+
+            Assert.False(service.IsFolderTrusted(folderPath));
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_IsFolderTrusted_Trusted()
+        {
+            var trustedFolders = new HashSet<string>();
+            trustedFolders.Add("C:\\Users\\Project");
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(trustedFolders);
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = "C:\\Users\\Project";
+
+            Assert.True(service.IsFolderTrusted(folderPath));
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_IsFolderTrusted_SubfolderTrusted()
+        {
+            var trustedFolders = new HashSet<string>();
+            trustedFolders.Add("C:\\Users\\Project");
+
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(trustedFolders);
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = "C:\\Users\\Project\\subfolder";
+
+            Assert.True(service.IsFolderTrusted(folderPath));
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_IsFolderTrusted_ParentFolderNotTrusted()
+        {
+            var trustedFolders = new HashSet<string>();
+            trustedFolders.Add("C:\\Users\\Project\\subfolder");
+
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(trustedFolders);
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = "C:\\Users\\Project";
+
+            Assert.False(service.IsFolderTrusted(folderPath));
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_AddFolderToTrusted_NonExistingFolder()
+        {
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = "C:\\Users\\Project";
+
+            Assert.Throws<ArgumentException>(() => service.AddFolderToTrusted(folderPath));
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_AddFolderToTrusted_RelativeFolder()
+        {
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = "\\Users\\Project";
+
+            Assert.Throws<ArgumentException>(() => service.AddFolderToTrusted(folderPath));
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_AddFolderToTrusted_ExistingFolder()
+        {
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(new HashSet<string>());
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+            var folderPath = Path.GetDirectoryName(Path.GetTempFileName());
+
+            service.AddFolderToTrusted(folderPath);
+
+            settingsServiceMock.VerifySet(s => s.TrustedFolders = new HashSet<string> { folderPath }, Times.Once);
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_AddFolderToTrusted_MultipleFolders()
+        {
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            var presentFolder = "C:\\Users\\Project";
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(new HashSet<string> { presentFolder });
+
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+
+            var newFolderPath = this.CreateTempDirectory();
+
+            service.AddFolderToTrusted(newFolderPath);
+
+            settingsServiceMock.VerifySet(s => s.TrustedFolders = new HashSet<string> { presentFolder, newFolderPath });
+        }
+
+        [Fact]
+        public void WorkspaceTrustServiceTest_AddFolderToTrusted_SameFolderTwice()
+        {
+            var settingsServiceMock = new Mock<IUserStorageSettingsService>();
+            settingsServiceMock.Setup(s => s.TrustedFolders).Returns(new HashSet<string>());
+            var service = new WorkspaceTrustService(settingsServiceMock.Object);
+
+            var folderPath1 = this.CreateTempDirectory();
+            var folderPath2 = folderPath1;
+
+            service.AddFolderToTrusted(folderPath1);
+            settingsServiceMock.VerifySet(s => s.TrustedFolders = new HashSet<string> { folderPath1 }, Times.Once);
+
+            service.AddFolderToTrusted(folderPath2);
+
+            // Must not append new entry to collection
+            settingsServiceMock.VerifySet(s => s.TrustedFolders = new HashSet<string> { folderPath1 }, Times.Exactly(2));
+        }
+
+        private string CreateTempDirectory()
+        {
+            var tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
+            Directory.CreateDirectory(tempDirectory);
+
+            return tempDirectory;
+        }
+    }
+}

programming_language: C#
filename: Snyk.VisualStudio.Extension.csproj
change_type: ModificationType.MODIFY
diff: @@ -26,7 +26,7 @@
     <StartAction>Program</StartAction>
     <StartProgram Condition="'$(DevEnvDir)' != ''">$(DevEnvDir)devenv.exe</StartProgram>
     <StartArguments>/rootsuffix Exp</StartArguments>
-	<DeployVsixExtensionFilesDependsOn>$(DeployVsixExtensionFilesDependsOn);SaveSettingsJsonFile</DeployVsixExtensionFilesDependsOn>
+    <DeployVsixExtensionFilesDependsOn>$(DeployVsixExtensionFilesDependsOn);SaveSettingsJsonFile</DeployVsixExtensionFilesDependsOn>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -121,9 +121,17 @@
       <Version>4.5.4</Version>
     </PackageReference>
   </ItemGroup>
-  <ItemGroup />
+  <ItemGroup>
+    <Page Include="TrustDialogWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+  </ItemGroup>
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TrustDialogWindow.xaml.cs">
+      <DependentUpon>TrustDialogWindow.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <Import Project="..\Snyk.VisualStudio.Extension.Shared\Snyk.VisualStudio.Extension.Shared.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
@@ -144,30 +152,17 @@
   </Target>  
   -->
   <Target Name="IncludePackageReferenceDependencies" AfterTargets="GetVsixSourceItems">
-      <ItemGroup>
-          <VSIXSourceItem Include="@(ReferencePath)" />
-      </ItemGroup>
+    <ItemGroup>
+      <VSIXSourceItem Include="@(ReferencePath)" />
+    </ItemGroup>
   </Target>
-  <Target Name="SaveSettingsJsonFile"
-          DependsOnTargets="GetVsixDeploymentPath">
-	<Message Condition="!Exists('$(VsixDeploymentPath)settings.json')"
-	  Importance="High" 
-      Text="settings.json does not exist, skipping step"/>
-    <Message Condition="Exists('$(VsixDeploymentPath)settings.json')"
-      Importance="High" 
-      Text="Saving settings.json file from $(VsixDeploymentPath)settings.json"/>
-    <Move Condition="Exists('$(VsixDeploymentPath)settings.json')"
-          SourceFiles="$(VsixDeploymentPath)settings.json"
-          DestinationFiles="$(IntermediateOutputPath)settings.json"/>
+  <Target Name="SaveSettingsJsonFile" DependsOnTargets="GetVsixDeploymentPath">
+    <Message Condition="!Exists('$(VsixDeploymentPath)settings.json')" Importance="High" Text="settings.json does not exist, skipping step" />
+    <Message Condition="Exists('$(VsixDeploymentPath)settings.json')" Importance="High" Text="Saving settings.json file from $(VsixDeploymentPath)settings.json" />
+    <Move Condition="Exists('$(VsixDeploymentPath)settings.json')" SourceFiles="$(VsixDeploymentPath)settings.json" DestinationFiles="$(IntermediateOutputPath)settings.json" />
   </Target>
-  <Target Name="AfterBuild" 
-          DependsOnTargets="GetVsixDeploymentPath">
-    <Message 
-      Condition="Exists('$(IntermediateOutputPath)settings.json')"
-      Text="Copying settings.json back to $(VsixDeploymentPath)" 
-      Importance="High"/>
-    <Move Condition="Exists('$(IntermediateOutputPath)settings.json')"
-          DestinationFiles="$(VsixDeploymentPath)settings.json"
-          SourceFiles="$(IntermediateOutputPath)settings.json"/>
+  <Target Name="AfterBuild" DependsOnTargets="GetVsixDeploymentPath">
+    <Message Condition="Exists('$(IntermediateOutputPath)settings.json')" Text="Copying settings.json back to $(VsixDeploymentPath)" Importance="High" />
+    <Move Condition="Exists('$(IntermediateOutputPath)settings.json')" DestinationFiles="$(VsixDeploymentPath)settings.json" SourceFiles="$(IntermediateOutputPath)settings.json" />
   </Target>
 </Project>
\ No newline at end of file

programming_language: PowerShell
filename: TrustDialogWindow.xaml
change_type: ModificationType.ADD
diff: @@ -0,0 +1,59 @@
+﻿<ui:DialogWindow x:Class="Snyk.VisualStudio.Extension.TrustDialogWindow"
+                 x:Name="trustDialogWindow"
+                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+                 xmlns:toolkit="clr-namespace:Community.VisualStudio.Toolkit;assembly=Community.VisualStudio.Toolkit"
+                 xmlns:ui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0"
+                 mc:Ignorable="d"
+                 WindowStartupLocation="CenterScreen"
+                 IsCloseButtonEnabled="True"
+                 HasHelpButton="False"
+                MinHeight="290" Height="290"
+                 MinWidth="500" Width="500"
+                 BorderBrush="{x:Static SystemColors.WindowFrameBrush}" BorderThickness="1"
+                 WindowStyle="None" ResizeMode="NoResize" AllowsTransparency="True"
+                 xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog"
+                 xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging"
+                 toolkit:Themes.UseVsTheme="True"
+                 Title="Snyk - This folder has not been trusted"
+                 MouseDown="TrustDialogWindow_OnMouseDown">
+    <DockPanel Margin="10">
+        <Button DockPanel.Dock="Top" HorizontalAlignment="Right" Click="DoNotTrustButton_OnClick" MinWidth="1" MinHeight="1" Width="35" Margin="0" Padding="0">
+            <imaging:CrispImage Moniker="{x:Static catalog:KnownMonikers.Close}"/>
+        </Button>
+        <StackPanel HorizontalAlignment="Right" DockPanel.Dock="Bottom" Orientation="Horizontal">
+            <Button x:Name="TrustButton" Margin="5, 5" Content="Trust folder and continue" Click="TrustButton_OnClick"/>
+            <Button x:Name="DoNotTrustButton" Margin="5, 5" Content="Don't scan" Click="DoNotTrustButton_OnClick"/>
+        </StackPanel>
+        <Grid>
+            <Grid.RowDefinitions>
+                <RowDefinition Height="auto"/>
+                <RowDefinition Height="auto"/>
+            </Grid.RowDefinitions>
+            <Grid Grid.Row="0">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="5*"/>
+                </Grid.ColumnDefinitions>
+                <imaging:CrispImage Grid.Column="0" Width="50" Moniker="{x:Static catalog:KnownMonikers.StatusSecurityWarning}"/>
+                <StackPanel VerticalAlignment="Center" Grid.Column="1" Margin="0, 0, 5, 0">
+                    <TextBlock FontSize="14">This folder has not been trusted:</TextBlock>
+                    <TextBlock FontSize="14" FontWeight="Bold" TextWrapping="Wrap" Text="{Binding ElementName=TrustWindow, Path=FolderPath}"/>
+                </StackPanel>
+            </Grid>
+            <StackPanel Grid.Row="1" Margin="5">
+                <TextBlock TextWrapping="Wrap">
+                    When scanning folder files for vulnerabilities, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan folders you trust.
+                </TextBlock>
+                <TextBlock>
+                    <LineBreak/>
+                    <Hyperlink NavigateUri="https://docs.snyk.io/ide-tools/visual-studio-extension/workspace-trust" RequestNavigate="Hyperlink_OnRequestNavigate">
+                        More information
+                    </Hyperlink>
+                </TextBlock>
+            </StackPanel>
+        </Grid>
+    </DockPanel>
+</ui:DialogWindow>
\ No newline at end of file

programming_language: PowerShell
filename: TrustDialogWindow.xaml.cs
change_type: ModificationType.ADD
diff: @@ -0,0 +1,48 @@
+﻿namespace Snyk.VisualStudio.Extension
+{
+    using System.Diagnostics;
+    using System.Windows;
+    using System.Windows.Input;
+    using System.Windows.Navigation;
+    using Microsoft.VisualStudio.PlatformUI;
+
+    /// <summary>
+    /// Trusted dialog window for Visual Studio versions less than 2022.
+    /// </summary>
+    public partial class TrustDialogWindow : DialogWindow
+    {
+        public TrustDialogWindow(string folderPath)
+        {
+            this.FolderPath = folderPath;
+            this.InitializeComponent();
+        }
+
+        public string FolderPath { get; }
+
+        private void DoNotTrustButton_OnClick(object sender, RoutedEventArgs e)
+        {
+            this.DialogResult = false;
+            this.Close();
+        }
+
+        private void TrustButton_OnClick(object sender, RoutedEventArgs e)
+        {
+            this.DialogResult = true;
+            this.Close();
+        }
+
+        private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+        {
+            Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri));
+            e.Handled = true;
+        }
+
+        private void TrustDialogWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
+        {
+            if (e.ChangedButton == MouseButton.Left)
+            {
+                this.DragMove();
+            }
+        }
+    }
+}

programming_language: C#
repo_url: https://github.com/snyk/snyk-ls
commit_details: repo_url: https://github.com/snyk/snyk-ls
author: Michel Kaporin
author_date: 2022-11-29 13:17:08+01:00
msg: feat: add trust management mechanism (#187)
repository: repo_url: https://github.com/snyk/snyk-ls
repo_name: snyk/snyk-ls
description: Language Server using LSP meant to be used by IDEs as Snyk Backend for Frontends
repo_language: Go
owner: snyk
file_changes: filename: .goreleaser.yaml
change_type: ModificationType.MODIFY
diff: @@ -60,4 +60,4 @@ dist: build
 env:
   - GO111MODULE=on
   - CGO_ENABLED=0
-  - LS_PROTOCOL_VERSION=3
+  - LS_PROTOCOL_VERSION=4

programming_language: Shell
filename: Makefile
change_type: ModificationType.MODIFY
diff: @@ -37,7 +37,7 @@ tools:
 	@echo "==> Installing go-licenses"
 	@go install github.com/google/go-licenses@latest
 ifeq (,$(wildcard ./.bin/golangci-lint*))
-	@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b .bin/ v1.48.0
+	@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b .bin/ v1.50.0
 else
 	@echo "==> golangci-lint is already installed"
 endif

programming_language: Batchfile
filename: README.md
change_type: ModificationType.MODIFY
diff: @@ -75,6 +75,15 @@ Right now the language server supports the following actions:
   }
   ```
 
+- Trust Notification
+  - method: `$/snyk.addTrustedFolders`
+  - payload:
+  ```json
+  {
+    "trustedFolders": ["/a/path/to/trust"]
+  }
+  ```
+
 ## Installation
 
 ### Download
@@ -132,12 +141,37 @@ within `initializationOptions?: LSPAny;` we support the following settings:
   "organization": "a string", // The name of your organization, e.g. the output of: curl -H "Authorization: token $(snyk config get api)"  https://snyk.io/api/cli-config/settings/sast | jq .org
   "enableTelemetry":  "true", // Whether or not user analytics can be tracked
   "manageBinariesAutomatically": "true", // Whether or not CLI/LS binaries will be downloaded & updated automatically
-  "cliPath":  "/a/patch/snyk-cli" // The path where the CLI can be found, or where it should be downloaded to
-  "token":  "secret-token" // The Snyk token, e.g.: snyk config get api
-  "automaticAuthentication": "true" // Whether or not LS will automatically authenticate on scan start (default: true)
+  "cliPath":  "/a/patch/snyk-cli", // The path where the CLI can be found, or where it should be downloaded to
+  "token":  "secret-token", // The Snyk token, e.g.: snyk config get api
+  "automaticAuthentication": "true", // Whether or not LS will automatically authenticate on scan start (default: true)
+  "enableTrustedFoldersFeature": "true", // Whether or not LS will prompt to trust a folder (default: true)
+  "trustedFolders": ["/a/trusted/path", "/another/trusted/path"], // An array of folder that should be trusted
 }
 ```
 
+#### Workspace Trust
+
+As part of examining the codebase for vulnerabilities, Snyk may automatically execute code on your computer to obtain
+additional data for analysis. For example, this includes invoking the package manager (e.g., pip, gradle, maven, yarn,
+npm, etc.)
+to get dependency information for Snyk Open Source. Invoking these programs on untrusted code that has malicious
+configurations may expose your system to malicious code execution and exploits.
+
+To safeguard from using the language server on untrusted folders, our language server will ask for folder trust
+before running scans against these folders. When in doubt, do not grant trust.
+
+The trust feature is enabled by default. When a folder is trusted, all sub-folders are also trusted. After a folder
+is trusted, Snyk Language Server notifies the Language Server Client with the custom `$/snyk.addTrustedFolders`
+notification,
+which contains a list of currently trusted folder paths. Based on this, a client can then implement logic to intercept
+this notification and persist the decision and trust in the IDE or Editor storage mechanism.
+
+Trust dialogs can be disabled by setting `enableTrustedFoldersFeature` to `false` in the initialization options. This
+will disable all trust prompts and checks.
+
+An initial set of trusted folders can be provided by setting `trustedFolders` to an array of paths in the
+`initializationOptions`. These folders will be trusted on startup and will not prompt the user to trust them.
+
 #### Environment variables
 
 Snyk LS and Snyk CLI support and need certain environment variables to function:

programming_language: Markdown
filename: config.go
change_type: ModificationType.MODIFY
diff: @@ -119,33 +119,35 @@ func (c *CliSettings) DefaultBinaryInstallPath() string {
 }
 
 type Config struct {
-	configLoaded                concurrency.AtomicBool
-	cliSettings                 *CliSettings
-	configFile                  string
-	format                      string
-	isErrorReportingEnabled     concurrency.AtomicBool
-	isSnykCodeEnabled           concurrency.AtomicBool
-	isSnykOssEnabled            concurrency.AtomicBool
-	isSnykIacEnabled            concurrency.AtomicBool
-	isSnykContainerEnabled      concurrency.AtomicBool
-	isSnykAdvisorEnabled        concurrency.AtomicBool
-	isTelemetryEnabled          concurrency.AtomicBool
-	manageBinariesAutomatically concurrency.AtomicBool
-	logPath                     string
-	organization                string
-	snykCodeAnalysisTimeout     time.Duration
-	snykApiUrl                  string
-	snykCodeApiUrl              string
-	token                       string
-	deviceId                    string
-	clientCapabilities          lsp.ClientCapabilities
-	m                           sync.Mutex
-	path                        string
-	defaultDirs                 []string
-	integrationName             string
-	integrationVersion          string
-	automaticAuthentication     bool
-	tokenChangeChannels         []chan string
+	configLoaded                 concurrency.AtomicBool
+	cliSettings                  *CliSettings
+	configFile                   string
+	format                       string
+	isErrorReportingEnabled      concurrency.AtomicBool
+	isSnykCodeEnabled            concurrency.AtomicBool
+	isSnykOssEnabled             concurrency.AtomicBool
+	isSnykIacEnabled             concurrency.AtomicBool
+	isSnykContainerEnabled       concurrency.AtomicBool
+	isSnykAdvisorEnabled         concurrency.AtomicBool
+	isTelemetryEnabled           concurrency.AtomicBool
+	manageBinariesAutomatically  concurrency.AtomicBool
+	logPath                      string
+	organization                 string
+	snykCodeAnalysisTimeout      time.Duration
+	snykApiUrl                   string
+	snykCodeApiUrl               string
+	token                        string
+	deviceId                     string
+	clientCapabilities           lsp.ClientCapabilities
+	m                            sync.Mutex
+	path                         string
+	defaultDirs                  []string
+	integrationName              string
+	integrationVersion           string
+	automaticAuthentication      bool
+	tokenChangeChannels          []chan string
+	trustedFolders               []string
+	trustedFoldersFeatureEnabled bool
 }
 
 func CurrentConfig() *Config {
@@ -185,6 +187,7 @@ func New() *Config {
 	c.snykCodeApiUrl = defaultDeeproxyApiUrl
 	c.snykCodeAnalysisTimeout = snykCodeAnalysisTimeoutFromEnv()
 	c.token = ""
+	c.trustedFoldersFeatureEnabled = true
 	c.clientSettingsFromEnv()
 	c.deviceId = c.determineDeviceId()
 	c.addDefaults()
@@ -211,6 +214,16 @@ func (c *Config) determineDeviceId() string {
 	}
 }
 
+func (c *Config) IsTrustedFolderFeatureEnabled() bool {
+	return c.trustedFoldersFeatureEnabled
+}
+
+func (c *Config) SetTrustedFolderFeatureEnabled(enabled bool) {
+	c.m.Lock()
+	defer c.m.Unlock()
+	c.trustedFoldersFeatureEnabled = enabled
+}
+
 func (c *Config) Load() {
 	files := c.configFiles()
 	for _, fileName := range files {
@@ -538,3 +551,15 @@ func (c *Config) SetIntegrationName(integrationName string) {
 func (c *Config) SetIntegrationVersion(integrationVersion string) {
 	c.integrationVersion = integrationVersion
 }
+
+func (c *Config) TrustedFolders() []string {
+	c.m.Lock()
+	defer c.m.Unlock()
+	return c.trustedFolders
+}
+
+func (c *Config) SetTrustedFolders(folderPaths []string) {
+	c.m.Lock()
+	defer c.m.Unlock()
+	c.trustedFolders = folderPaths
+}

programming_language: Go
filename: config_test.go
change_type: ModificationType.MODIFY
diff: @@ -45,6 +45,7 @@ func TestConfigDefaults(t *testing.T) {
 	assert.True(t, c.IsSnykIacEnabled(), "Snyk IaC should be enabled by default")
 	assert.Equal(t, "", c.LogPath(), "Logpath should be empty by default")
 	assert.Equal(t, "md", c.Format(), "Output format should be md by default")
+	assert.Empty(t, c.trustedFolders)
 }
 
 func Test_TokenChanged_ChannelsInformed(t *testing.T) {

programming_language: Go
filename: configuration.go
change_type: ModificationType.MODIFY
diff: @@ -19,6 +19,7 @@ package server
 import (
 	"context"
 	"os"
+	"reflect"
 	"strconv"
 	"strings"
 
@@ -37,7 +38,7 @@ func WorkspaceDidChangeConfiguration(srv *jrpc2.Server) jrpc2.Handler {
 		defer log.Info().Str("method", "WorkspaceDidChangeConfiguration").Interface("params", params).Msg("DONE")
 
 		emptySettings := lsp.Settings{}
-		if params.Settings != emptySettings {
+		if !reflect.DeepEqual(params.Settings, emptySettings) {
 			// client used settings push
 			UpdateSettings(ctx, params.Settings)
 			return true, nil
@@ -65,7 +66,7 @@ func WorkspaceDidChangeConfiguration(srv *jrpc2.Server) jrpc2.Handler {
 			return false, err
 		}
 
-		if fetchedSettings[0] != emptySettings {
+		if !reflect.DeepEqual(fetchedSettings[0], emptySettings) {
 			UpdateSettings(ctx, fetchedSettings[0])
 			return true, nil
 		}
@@ -86,7 +87,7 @@ func UpdateSettings(ctx context.Context, settings lsp.Settings) {
 
 func writeSettings(ctx context.Context, settings lsp.Settings, initialize bool) {
 	emptySettings := lsp.Settings{}
-	if settings == emptySettings {
+	if reflect.DeepEqual(settings, emptySettings) {
 		return
 	}
 	updateToken(settings.Token)
@@ -98,6 +99,20 @@ func writeSettings(ctx context.Context, settings lsp.Settings, initialize bool)
 	updateTelemetry(settings)
 	updateOrganization(settings)
 	manageBinariesAutomatically(settings)
+	updateTrustedFolders(settings)
+}
+
+func updateTrustedFolders(settings lsp.Settings) {
+	trustedFoldersFeatureEnabled, err := strconv.ParseBool(settings.EnableTrustedFoldersFeature)
+	if err == nil {
+		config.CurrentConfig().SetTrustedFolderFeatureEnabled(trustedFoldersFeatureEnabled)
+	} else {
+		config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	}
+
+	if settings.TrustedFolders != nil {
+		config.CurrentConfig().SetTrustedFolders(settings.TrustedFolders)
+	}
 }
 
 func updateAutoAuthentication(settings lsp.Settings) {

programming_language: Go
filename: configuration_test.go
change_type: ModificationType.MODIFY
diff: @@ -71,7 +71,7 @@ func Test_WorkspaceDidChangeConfiguration_Push(t *testing.T) {
 	assert.Equal(t, "token", config.CurrentConfig().Token())
 }
 
-func callBackMock(ctx context.Context, request *jrpc2.Request) (interface{}, error) {
+func callBackMock(_ context.Context, request *jrpc2.Request) (interface{}, error) {
 	jsonRPCRecorder.Record(*request)
 	if request.Method() == "workspace/configuration" {
 		return []lsp.Settings{sampleSettings}, nil
@@ -151,6 +151,7 @@ func Test_UpdateSettings(t *testing.T) {
 			ManageBinariesAutomatically: "false",
 			CliPath:                     "C:\\Users\\CliPath\\snyk-ls.exe",
 			Token:                       "a fancy token",
+			TrustedFolders:              []string{"trustedPath1", "trustedPath2"},
 		}
 
 		UpdateSettings(context.Background(), settings)
@@ -171,6 +172,8 @@ func Test_UpdateSettings(t *testing.T) {
 		assert.False(t, c.ManageBinariesAutomatically())
 		assert.Equal(t, "C:\\Users\\CliPath\\snyk-ls.exe", c.CliSettings().Path())
 		assert.Equal(t, "a fancy token", c.Token())
+		assert.Contains(t, c.TrustedFolders(), "trustedPath1")
+		assert.Contains(t, c.TrustedFolders(), "trustedPath2")
 	})
 
 	t.Run("blank organisation is ignored", func(t *testing.T) {
@@ -209,6 +212,15 @@ func Test_UpdateSettings(t *testing.T) {
 		assert.Empty(t, os.Getenv("b"))
 		assert.Empty(t, os.Getenv(";"))
 	})
+	t.Run("trusted folders", func(t *testing.T) {
+		config.SetCurrentConfig(config.New())
+
+		UpdateSettings(context.Background(), lsp.Settings{TrustedFolders: []string{"/a/b", "/b/c"}})
+
+		c := config.CurrentConfig()
+		assert.Contains(t, c.TrustedFolders(), "/a/b")
+		assert.Contains(t, c.TrustedFolders(), "/b/c")
+	})
 
 	t.Run("manage binaries automatically", func(t *testing.T) {
 		t.Run("true", func(t *testing.T) {

programming_language: Go
filename: execute_command.go
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,9 @@ import (
 	"github.com/rs/zerolog/log"
 	sglsp "github.com/sourcegraph/go-lsp"
 
+	"github.com/snyk/snyk-ls/application/config"
 	"github.com/snyk/snyk-ls/application/di"
+	"github.com/snyk/snyk-ls/application/server/lsp"
 	"github.com/snyk/snyk-ls/domain/ide/command"
 	"github.com/snyk/snyk-ls/domain/ide/workspace"
 	"github.com/snyk/snyk-ls/domain/snyk"
@@ -49,10 +51,18 @@ func ExecuteCommandHandler(srv *jrpc2.Server) jrpc2.Handler {
 			}
 			navigateToLocation(srv, args)
 		case snyk.WorkspaceScanCommand:
-			workspace.Get().ClearIssues(bgCtx)
-			workspace.Get().ScanWorkspace(bgCtx)
+			w := workspace.Get()
+			w.ClearIssues(bgCtx)
+			w.ScanWorkspace(bgCtx)
+			handleUntrustedFolders(bgCtx, srv)
 		case snyk.OpenBrowserCommand:
 			command.OpenBrowser(params.Arguments[0].(string))
+		case snyk.TrustWorkspaceFoldersCommand:
+			err := TrustWorkspaceFolders()
+			if err != nil {
+				log.Err(err).Msgf("Error on %s command", snyk.TrustWorkspaceFoldersCommand)
+				notification.SendError(err)
+			}
 		case snyk.LoginCommand:
 			authenticator := di.Authenticator()
 			_, err := authenticator.Authenticate(context.Background())
@@ -75,3 +85,19 @@ func ExecuteCommandHandler(srv *jrpc2.Server) jrpc2.Handler {
 		return nil, nil
 	})
 }
+
+func TrustWorkspaceFolders() error {
+	if !config.CurrentConfig().IsTrustedFolderFeatureEnabled() {
+		return nil
+	}
+
+	trustedFolderPaths := config.CurrentConfig().TrustedFolders()
+	_, untrusted := workspace.Get().GetFolderTrust()
+	for _, folder := range untrusted {
+		trustedFolderPaths = append(trustedFolderPaths, folder.Path())
+	}
+
+	config.CurrentConfig().SetTrustedFolders(trustedFolderPaths)
+	notification.Send(lsp.SnykTrustedFoldersParams{TrustedFolders: trustedFolderPaths})
+	return nil
+}

programming_language: Go
filename: execute_command_test.go
change_type: ModificationType.MODIFY
diff: @@ -25,6 +25,7 @@ import (
 
 	"github.com/atotto/clipboard"
 
+	"github.com/snyk/snyk-ls/application/config"
 	"github.com/snyk/snyk-ls/application/di"
 	"github.com/snyk/snyk-ls/domain/ide/workspace"
 	"github.com/snyk/snyk-ls/domain/snyk"
@@ -47,6 +48,24 @@ func Test_executeWorkspaceScanCommand_shouldStartWorkspaceScanOnCommandReceipt(t
 	}, 2*time.Second, time.Millisecond)
 }
 
+func Test_executeWorkspaceScanCommand_shouldAskForTrust(t *testing.T) {
+	loc := setupServer(t)
+
+	scanner := &snyk.TestScanner{}
+	workspace.Get().AddFolder(workspace.NewFolder("dummy", "dummy", scanner, di.HoverService()))
+	// explicitly enable folder trust which is disabled by default in tests
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+
+	params := lsp.ExecuteCommandParams{Command: snyk.WorkspaceScanCommand}
+	_, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Eventually(t, func() bool {
+		return scanner.Calls() == 0 && checkTrustMessageRequest()
+	}, 2*time.Second, time.Millisecond)
+}
+
 func Test_loginCommand_StartsAuthentication(t *testing.T) {
 	// Arrange
 	loc := setupServer(t)
@@ -84,3 +103,50 @@ func Test_executeCommand_shouldCopyAuthURLToClipboard(t *testing.T) {
 
 	assert.Equal(t, authenticationMock.ExpectedAuthURL, actualURL)
 }
+
+func Test_TrustWorkspaceFolders(t *testing.T) {
+	t.Run("Doesn't mutate trusted folders, if trusted folders disabled", func(t *testing.T) {
+		loc := setupServer(t)
+		workspace.Get().AddFolder(workspace.NewFolder("/path/to/folder1", "dummy", nil, di.HoverService()))
+
+		params := lsp.ExecuteCommandParams{Command: snyk.TrustWorkspaceFoldersCommand}
+		_, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assert.Len(t, config.CurrentConfig().TrustedFolders(), 0)
+	})
+
+	t.Run("Updates trusted workspace folders", func(t *testing.T) {
+		loc := setupServer(t)
+		workspace.Get().AddFolder(workspace.NewFolder("/path/to/folder1", "dummy", nil, di.HoverService()))
+		workspace.Get().AddFolder(workspace.NewFolder("/path/to/folder2", "dummy", nil, di.HoverService()))
+		config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+
+		params := lsp.ExecuteCommandParams{Command: snyk.TrustWorkspaceFoldersCommand}
+		_, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assert.Len(t, config.CurrentConfig().TrustedFolders(), 2)
+		assert.Contains(t, config.CurrentConfig().TrustedFolders(), "/path/to/folder1", "/path/to/folder2")
+	})
+
+	t.Run("Existing trusted workspace folders are not removed", func(t *testing.T) {
+		loc := setupServer(t)
+		workspace.Get().AddFolder(workspace.NewFolder("/path/to/folder1", "dummy", nil, di.HoverService()))
+		config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+		config.CurrentConfig().SetTrustedFolders([]string{"/path/to/folder2"})
+
+		params := lsp.ExecuteCommandParams{Command: snyk.TrustWorkspaceFoldersCommand}
+		_, err := loc.Client.Call(ctx, "workspace/executeCommand", params)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assert.Len(t, config.CurrentConfig().TrustedFolders(), 2)
+		assert.Contains(t, config.CurrentConfig().TrustedFolders(), "/path/to/folder1", "/path/to/folder2")
+	})
+}

programming_language: Go
filename: message_types.go
change_type: ModificationType.MODIFY
diff: @@ -233,24 +233,26 @@ type WorkspaceFoldersChangeEvent struct {
 
 // Settings is the struct that is parsed from the InitializationParams.InitializationOptions field
 type Settings struct {
-	ActivateSnykOpenSource      string `json:"activateSnykOpenSource,omitempty"`
-	ActivateSnykCode            string `json:"activateSnykCode,omitempty"`
-	ActivateSnykIac             string `json:"activateSnykIac,omitempty"`
-	Insecure                    string `json:"insecure,omitempty"`
-	Endpoint                    string `json:"endpoint,omitempty"`
-	AdditionalParams            string `json:"additionalParams,omitempty"`
-	AdditionalEnv               string `json:"additionalEnv,omitempty"`
-	Path                        string `json:"path,omitempty"`
-	SendErrorReports            string `json:"sendErrorReports,omitempty"`
-	Organization                string `json:"organization,omitempty"`
-	EnableTelemetry             string `json:"enableTelemetry,omitempty"`
-	ManageBinariesAutomatically string `json:"manageBinariesAutomatically,omitempty"`
-	CliPath                     string `json:"cliPath,omitempty"`
-	Token                       string `json:"token,omitempty"`
-	IntegrationName             string `json:"integrationName,omitempty"`
-	IntegrationVersion          string `json:"integrationVersion,omitempty"`
-	AutomaticAuthentication     string `json:"automaticAuthentication,omitempty"`
-	DeviceId                    string `json:"deviceId,omitempty"`
+	ActivateSnykOpenSource      string   `json:"activateSnykOpenSource,omitempty"`
+	ActivateSnykCode            string   `json:"activateSnykCode,omitempty"`
+	ActivateSnykIac             string   `json:"activateSnykIac,omitempty"`
+	Insecure                    string   `json:"insecure,omitempty"`
+	Endpoint                    string   `json:"endpoint,omitempty"`
+	AdditionalParams            string   `json:"additionalParams,omitempty"`
+	AdditionalEnv               string   `json:"additionalEnv,omitempty"`
+	Path                        string   `json:"path,omitempty"`
+	SendErrorReports            string   `json:"sendErrorReports,omitempty"`
+	Organization                string   `json:"organization,omitempty"`
+	EnableTelemetry             string   `json:"enableTelemetry,omitempty"`
+	ManageBinariesAutomatically string   `json:"manageBinariesAutomatically,omitempty"`
+	CliPath                     string   `json:"cliPath,omitempty"`
+	Token                       string   `json:"token,omitempty"`
+	IntegrationName             string   `json:"integrationName,omitempty"`
+	IntegrationVersion          string   `json:"integrationVersion,omitempty"`
+	AutomaticAuthentication     string   `json:"automaticAuthentication,omitempty"`
+	DeviceId                    string   `json:"deviceId,omitempty"`
+	EnableTrustedFoldersFeature string   `json:"enableTrustedFoldersFeature,omitempty"`
+	TrustedFolders              []string `json:"trustedFolders,omitempty"`
 }
 
 type DidChangeConfigurationParams struct {
@@ -607,3 +609,24 @@ type ShowDocumentParams struct {
 	 */
 	Selection sglsp.Range `json:"selection"`
 }
+
+type MessageActionItem struct {
+	Title string `json:"title"`
+}
+
+type ShowMessageRequestParams struct {
+	Type    MessageType         `json:"type"`
+	Message string              `json:"message"`
+	Actions []MessageActionItem `json:"actions"`
+}
+
+type MessageType int
+
+const Error MessageType = 1
+const Warning MessageType = 2
+const Info MessageType = 3
+const Log MessageType = 4
+
+type SnykTrustedFoldersParams struct {
+	TrustedFolders []string `json:"trustedFolders"`
+}

programming_language: Go
filename: server.go
change_type: ModificationType.MODIFY
diff: @@ -90,7 +90,7 @@ func initHandlers(srv *jrpc2.Server, handlers *handler.Map) {
 	(*handlers)["textDocument/willSaveWaitUntil"] = NoOpHandler()
 	(*handlers)["shutdown"] = Shutdown()
 	(*handlers)["exit"] = Exit(srv)
-	(*handlers)["workspace/didChangeWorkspaceFolders"] = WorkspaceDidChangeWorkspaceFoldersHandler()
+	(*handlers)["workspace/didChangeWorkspaceFolders"] = WorkspaceDidChangeWorkspaceFoldersHandler(srv)
 	(*handlers)["workspace/didChangeConfiguration"] = WorkspaceDidChangeConfiguration(srv)
 	(*handlers)["window/workDoneProgress/cancel"] = WindowWorkDoneProgressCancelHandler()
 	(*handlers)["workspace/executeCommand"] = ExecuteCommandHandler(srv)
@@ -145,7 +145,7 @@ func CodeActionHandler() jrpc2.Handler {
 	})
 }
 
-func WorkspaceDidChangeWorkspaceFoldersHandler() jrpc2.Handler {
+func WorkspaceDidChangeWorkspaceFoldersHandler(srv *jrpc2.Server) jrpc2.Handler {
 	return handler.New(func(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) (interface{}, error) {
 		// The context provided by the JSON-RPC server is cancelled once a new message is being processed,
 		// so we don't want to propagate it to functions that start background operations
@@ -153,7 +153,8 @@ func WorkspaceDidChangeWorkspaceFoldersHandler() jrpc2.Handler {
 
 		log.Info().Str("method", "WorkspaceDidChangeWorkspaceFoldersHandler").Msg("RECEIVING")
 		defer log.Info().Str("method", "WorkspaceDidChangeWorkspaceFoldersHandler").Msg("SENDING")
-		workspace.Get().ProcessFolderChange(bgCtx, params)
+		workspace.Get().AddAndRemoveFoldersAndTriggerScan(bgCtx, params)
+		handleUntrustedFolders(bgCtx, srv)
 		return nil, nil
 	})
 }
@@ -183,24 +184,7 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
 			os.Exit(0)
 		}()
 
-		if len(params.WorkspaceFolders) > 0 {
-			for _, workspaceFolder := range params.WorkspaceFolders {
-				log.Info().Str("method", method).Msgf("Adding workspaceFolder %v", workspaceFolder)
-				f := workspace.NewFolder(
-					uri.PathFromUri(workspaceFolder.Uri),
-					workspaceFolder.Name,
-					di.Scanner(),
-					di.HoverService(),
-				)
-				w.AddFolder(f)
-			}
-		} else {
-			if params.RootURI != "" {
-				w.AddFolder(workspace.NewFolder(uri.PathFromUri(params.RootURI), params.ClientInfo.Name, di.Scanner(), di.HoverService()))
-			} else if params.RootPath != "" {
-				w.AddFolder(workspace.NewFolder(params.RootPath, params.ClientInfo.Name, di.Scanner(), di.HoverService()))
-			}
-		}
+		addWorkspaceFolders(params, w)
 
 		return lsp.InitializeResult{
 			ServerInfo: lsp.ServerInfo{
@@ -232,6 +216,7 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
 						snyk.LoginCommand,
 						snyk.CopyAuthLinkCommand,
 						snyk.LogoutCommand,
+						snyk.TrustWorkspaceFoldersCommand,
 					},
 				},
 			},
@@ -241,10 +226,37 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
 func InitializedHandler(srv *jrpc2.Server) handler.Func {
 	return handler.New(func(ctx context.Context, params lsp.InitializedParams) (interface{}, error) {
 		workspace.Get().ScanWorkspace(context.Background())
+		if config.CurrentConfig().AutomaticAuthentication() || config.CurrentConfig().NonEmptyToken() {
+			go handleUntrustedFolders(context.Background(), srv)
+		}
 		return nil, nil
 	})
 }
 
+func addWorkspaceFolders(params lsp.InitializeParams, w *workspace.Workspace) {
+	const method = "addWorkspaceFolders"
+	if len(params.WorkspaceFolders) > 0 {
+		for _, workspaceFolder := range params.WorkspaceFolders {
+			log.Info().Str("method", method).Msgf("Adding workspaceFolder %v", workspaceFolder)
+			f := workspace.NewFolder(
+				uri.PathFromUri(workspaceFolder.Uri),
+				workspaceFolder.Name,
+				di.Scanner(),
+				di.HoverService(),
+			)
+			w.AddFolder(f)
+		}
+	} else {
+		if params.RootURI != "" {
+			f := workspace.NewFolder(uri.PathFromUri(params.RootURI), params.ClientInfo.Name, di.Scanner(), di.HoverService())
+			w.AddFolder(f)
+		} else if params.RootPath != "" {
+			f := workspace.NewFolder(params.RootPath, params.ClientInfo.Name, di.Scanner(), di.HoverService())
+			w.AddFolder(f)
+		}
+	}
+}
+
 func setClientInformation(initParams lsp.InitializeParams) {
 	var integrationName, integrationVersion string
 	if initParams.InitializationOptions.IntegrationName != "" {
@@ -400,6 +412,12 @@ func registerNotifier(srv *jrpc2.Server) {
 				Interface("source", source).
 				Interface("diagnosticCount", len(params.Diagnostics)).
 				Msg("publishing diagnostics")
+		case lsp.SnykTrustedFoldersParams:
+			notifier(srv, "$/snyk.addTrustedFolders", params)
+			log.Info().
+				Str("method", "registerNotifier").
+				Interface("trustedPaths", params.TrustedFolders).
+				Msg("sending trusted Folders to client")
 		default:
 			log.Warn().
 				Str("method", "registerNotifier").

programming_language: Go
filename: server_test.go
change_type: ModificationType.MODIFY
diff: @@ -230,7 +230,6 @@ func Test_initialize_shouldSupportCodeLenses(t *testing.T) {
 
 func Test_TextDocumentCodeLenses_shouldReturnCodeLenses(t *testing.T) {
 	loc := setupServer(t)
-
 	didOpenParams, dir := didOpenTextParams(t)
 
 	clientParams := lsp.InitializeParams{
@@ -243,6 +242,7 @@ func Test_TextDocumentCodeLenses_shouldReturnCodeLenses(t *testing.T) {
 			Token:                       "xxx",
 			ManageBinariesAutomatically: "true",
 			CliPath:                     "",
+			EnableTrustedFoldersFeature: "false",
 		},
 	}
 	_, err := loc.Client.Call(ctx, "initialize", clientParams)
@@ -468,6 +468,72 @@ func Test_initialize_autoAuthenticateSetCorrectly(t *testing.T) {
 	})
 }
 
+func Test_initialize_handlesUntrustedFoldersWhenAutomaticAuthentication(t *testing.T) {
+	loc := setupServer(t)
+	initializationOptions := lsp.Settings{
+		EnableTrustedFoldersFeature: "true",
+	}
+	params := lsp.InitializeParams{
+		InitializationOptions: initializationOptions,
+		WorkspaceFolders:      []lsp.WorkspaceFolder{{Uri: uri.PathToUri("/untrusted/dummy"), Name: "dummy"}}}
+	_, err := loc.Client.Call(ctx, "initialize", params)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	_, err = loc.Client.Call(ctx, "initialized", nil)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	assert.Nil(t, err)
+	assert.Eventually(t, func() bool { return checkTrustMessageRequest() }, time.Second, time.Millisecond)
+}
+
+func Test_initialize_handlesUntrustedFoldersWhenAuthenticated(t *testing.T) {
+	loc := setupServer(t)
+	initializationOptions := lsp.Settings{
+		EnableTrustedFoldersFeature: "true",
+		Token:                       "token",
+	}
+	params := lsp.InitializeParams{
+		InitializationOptions: initializationOptions,
+		WorkspaceFolders:      []lsp.WorkspaceFolder{{Uri: uri.PathToUri("/untrusted/dummy"), Name: "dummy"}}}
+	_, err := loc.Client.Call(ctx, "initialize", params)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	_, err = loc.Client.Call(ctx, "initialized", nil)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	assert.Nil(t, err)
+	assert.Eventually(t, func() bool { return checkTrustMessageRequest() }, time.Second, time.Millisecond)
+}
+
+func Test_initialize_doesnotHandleUntrustedFolders(t *testing.T) {
+	loc := setupServer(t)
+	initializationOptions := lsp.Settings{
+		EnableTrustedFoldersFeature: "true",
+	}
+	params := lsp.InitializeParams{
+		InitializationOptions: initializationOptions,
+		WorkspaceFolders:      []lsp.WorkspaceFolder{{Uri: uri.PathToUri("/untrusted/dummy"), Name: "dummy"}}}
+	_, err := loc.Client.Call(ctx, "initialize", params)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+	_, err = loc.Client.Call(ctx, "initialized", nil)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	assert.Nil(t, err)
+	assert.Eventually(t, func() bool { return checkTrustMessageRequest() }, time.Second, time.Millisecond)
+}
+
 func Test_textDocumentDidOpenHandler_shouldAcceptDocumentItemAndPublishDiagnostics(t *testing.T) {
 	loc := setupServer(t)
 	didOpenParams, dir := didOpenTextParams(t)
@@ -480,8 +546,9 @@ func Test_textDocumentDidOpenHandler_shouldAcceptDocumentItemAndPublishDiagnosti
 			ActivateSnykIac:             "false",
 			Organization:                "fancy org",
 			Token:                       "xxx",
-			ManageBinariesAutomatically: "true",
+			ManageBinariesAutomatically: "false",
 			CliPath:                     "",
+			EnableTrustedFoldersFeature: "false",
 		},
 	}
 	_, err := loc.Client.Call(ctx, "initialize", clientParams)
@@ -489,6 +556,11 @@ func Test_textDocumentDidOpenHandler_shouldAcceptDocumentItemAndPublishDiagnosti
 		t.Fatal(err, "couldn't initialize")
 	}
 
+	_, err = loc.Client.Call(ctx, "initialized", nil)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
 	_, err = loc.Client.Call(ctx, "textDocument/didOpen", didOpenParams)
 	if err != nil {
 		t.Fatal(err)
@@ -688,8 +760,9 @@ func runSmokeTest(repo string, commit string, file1 string, file2 string, t *tes
 	clientParams := lsp.InitializeParams{
 		WorkspaceFolders: []lsp.WorkspaceFolder{folder},
 		InitializationOptions: lsp.Settings{
-			Endpoint: os.Getenv("SNYK_API"),
-			Token:    os.Getenv("SNYK_TOKEN"),
+			Endpoint:                    os.Getenv("SNYK_API"),
+			Token:                       os.Getenv("SNYK_TOKEN"),
+			EnableTrustedFoldersFeature: "false",
 		},
 	}
 

programming_language: Go
filename: trust.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 Snyk Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package server
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/creachadair/jrpc2"
+	"github.com/pkg/errors"
+	"github.com/rs/zerolog/log"
+
+	"github.com/snyk/snyk-ls/application/server/lsp"
+	"github.com/snyk/snyk-ls/domain/ide/workspace"
+)
+
+const doTrust = "Trust folders and continue"
+const dontTrust = "Don't trust folders"
+
+func handleUntrustedFolders(ctx context.Context, srv *jrpc2.Server) {
+	w := workspace.Get()
+	// debounce requests from overzealous clients (Eclipse, I'm looking at you)
+	if w.IsTrustRequestOngoing() {
+		return
+	}
+	w.StartRequestTrustCommunication()
+	defer w.EndRequestTrustCommunication()
+
+	_, untrusted := w.GetFolderTrust()
+	if len(untrusted) > 0 {
+
+		decision, err := showTrustDialog(srv, untrusted, doTrust, dontTrust)
+		if err != nil {
+			return
+		}
+
+		if decision.Title == doTrust {
+			w.TrustFoldersAndScan(ctx, untrusted)
+		}
+	}
+}
+
+func showTrustDialog(srv *jrpc2.Server, untrusted []*workspace.Folder, dontTrust string, doTrust string) (lsp.MessageActionItem, error) {
+	method := "showTrustDialog"
+	result, err := srv.Callback(context.Background(), "window/showMessageRequest", lsp.ShowMessageRequestParams{
+		Type:    lsp.Warning,
+		Message: getTrustMessage(untrusted),
+		Actions: []lsp.MessageActionItem{{Title: dontTrust}, {Title: doTrust}},
+	})
+	if err != nil {
+		log.Err(errors.Wrap(err, "couldn't show trust message")).Str("method", method).Send()
+		return lsp.MessageActionItem{Title: dontTrust}, err
+	}
+
+	var trust lsp.MessageActionItem
+	if result != nil {
+		err = result.UnmarshalResult(&trust)
+		if err != nil {
+			log.Err(errors.Wrap(err, "couldn't unmarshal trust message")).Str("method", method).Send()
+			return lsp.MessageActionItem{Title: dontTrust}, err
+		}
+	}
+	return trust, err
+}
+
+func getTrustMessage(untrusted []*workspace.Folder) string {
+	var untrustedFolderString string
+	for _, folder := range untrusted {
+		untrustedFolderString += folder.Path() + "\n"
+	}
+	return fmt.Sprintf("When scanning for vulnerabilities, Snyk may automatically execute code such as invoking "+
+		"the package manager to get dependency information. You should only scan folders you trust."+
+		"\n\nUntrusted Folders: \n%s\n\n", untrustedFolderString)
+}

programming_language: Go
filename: trust_test.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,145 @@
+/*
+ * Copyright 2022 Snyk Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package server
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/creachadair/jrpc2"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/snyk/snyk-ls/application/config"
+	"github.com/snyk/snyk-ls/application/di"
+	"github.com/snyk/snyk-ls/application/server/lsp"
+	"github.com/snyk/snyk-ls/domain/ide/workspace"
+	"github.com/snyk/snyk-ls/domain/snyk"
+	"github.com/snyk/snyk-ls/internal/uri"
+)
+
+func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndNotScan(t *testing.T) {
+	loc := setupServer(t)
+	w := workspace.Get()
+	scanner := &snyk.TestScanner{}
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	w.AddFolder(workspace.NewFolder("dummy", "dummy", scanner, di.HoverService()))
+	handleUntrustedFolders(context.Background(), loc.Server)
+
+	assert.True(t, checkTrustMessageRequest())
+	assert.Equal(t, scanner.Calls(), 0)
+}
+
+func Test_handleUntrustedFolders_shouldNotTriggerTrustRequestWhenAlreadyRequesting(t *testing.T) {
+	loc := setupServer(t)
+	w := workspace.Get()
+	scanner := &snyk.TestScanner{}
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	w.AddFolder(workspace.NewFolder("dummy", "dummy", scanner, di.HoverService()))
+	w.StartRequestTrustCommunication()
+
+	handleUntrustedFolders(context.Background(), loc.Server)
+
+	assert.Len(t, jsonRPCRecorder.FindCallbacksByMethod("window/showMessageRequest"), 0)
+	assert.Equal(t, scanner.Calls(), 0)
+}
+
+func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndScanAfterConfirmation(t *testing.T) {
+	loc := setupCustomServer(t, func(_ context.Context, _ *jrpc2.Request) (interface{}, error) {
+		return lsp.MessageActionItem{
+			Title: doTrust,
+		}, nil
+	})
+	registerNotifier(loc.Server)
+
+	w := workspace.Get()
+	scanner := &snyk.TestScanner{}
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	w.AddFolder(workspace.NewFolder("/trusted/dummy", "dummy", scanner, di.HoverService()))
+
+	handleUntrustedFolders(context.Background(), loc.Server)
+
+	assert.Eventually(t, func() bool {
+		addTrustedSent := len(jsonRPCRecorder.FindNotificationsByMethod("$/snyk.addTrustedFolders")) == 1
+		return scanner.Calls() == 1 && addTrustedSent
+	}, time.Second, time.Millisecond)
+}
+
+func Test_handleUntrustedFolders_shouldTriggerTrustRequestAndNotScanAfterNegativeConfirmation(t *testing.T) {
+	loc := setupCustomServer(t, func(_ context.Context, _ *jrpc2.Request) (interface{}, error) {
+		return lsp.MessageActionItem{
+			Title: dontTrust,
+		}, nil
+	})
+	registerNotifier(loc.Server)
+	w := workspace.Get()
+	scanner := &snyk.TestScanner{}
+	w.AddFolder(workspace.NewFolder("/trusted/dummy", "dummy", scanner, di.HoverService()))
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+
+	handleUntrustedFolders(context.Background(), loc.Server)
+
+	assert.Equal(t, scanner.Calls(), 0)
+}
+
+func Test_initializeHandler_shouldCallHandleUntrustedFolders(t *testing.T) {
+	loc := setupServer(t)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+
+	_, err := loc.Client.Call(context.Background(), "initialize", lsp.InitializeParams{
+		RootURI: uri.PathToUri("/untrusted/dummy"),
+	})
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	_, err = loc.Client.Call(ctx, "initialized", nil)
+	if err != nil {
+		t.Fatal(err, "couldn't send initialized")
+	}
+
+	assert.NoError(t, err)
+	assert.Eventually(t, func() bool { return checkTrustMessageRequest() }, time.Second, time.Millisecond)
+}
+
+func Test_DidWorkspaceFolderChange_shouldCallHandleUntrustedFolders(t *testing.T) {
+	loc := setupServer(t)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+
+	_, err := loc.Client.Call(context.Background(), "workspace/didChangeWorkspaceFolders", lsp.DidChangeWorkspaceFoldersParams{
+		Event: lsp.WorkspaceFoldersChangeEvent{
+			Added: []lsp.WorkspaceFolder{
+				{Uri: uri.PathToUri("/untrusted/dummy"), Name: "dummy"},
+			},
+			Removed: []lsp.WorkspaceFolder{},
+		},
+	})
+
+	assert.NoError(t, err)
+	assert.Eventually(t, func() bool { return checkTrustMessageRequest() }, time.Second, time.Millisecond)
+}
+
+func checkTrustMessageRequest() bool {
+	callbacks := jsonRPCRecorder.FindCallbacksByMethod("window/showMessageRequest")
+	if len(callbacks) == 0 {
+		return false
+	}
+	var params lsp.ShowMessageRequestParams
+	_ = callbacks[0].UnmarshalParams(&params)
+	_, untrusted := workspace.Get().GetFolderTrust()
+	return params.Type == lsp.Warning && params.Message == getTrustMessage(untrusted)
+}

programming_language: Go
filename: folder.go
change_type: ModificationType.MODIFY
diff: @@ -18,10 +18,12 @@ package workspace
 
 import (
 	"context"
+	"strings"
 	"sync"
 
 	"github.com/rs/zerolog/log"
 
+	"github.com/snyk/snyk-ls/application/config"
 	"github.com/snyk/snyk-ls/application/server/lsp"
 	"github.com/snyk/snyk-ls/domain/ide/converter"
 	"github.com/snyk/snyk-ls/domain/ide/hover"
@@ -64,6 +66,7 @@ func NewFolder(path string, name string, scanner snyk.Scanner, hoverService hove
 	folder.productAttributes[snyk.ProductInfrastructureAsCode] = snyk.ProductAttributes{}
 	folder.productAttributes[snyk.ProductOpenSource] = snyk.ProductAttributes{}
 	folder.documentDiagnosticCache = concurrency.AtomicMap{}
+
 	return &folder
 }
 
@@ -115,9 +118,14 @@ func (f *Folder) ClearDiagnosticsCache(filePath string) {
 }
 
 func (f *Folder) scan(ctx context.Context, path string) {
+	const method = "domain.ide.workspace.folder.scan"
+	if !f.IsTrusted() {
+		log.Warn().Str("path", path).Str("method", method).Msg("skipping scan of untrusted path")
+		return
+	}
 	issuesSlice := f.DocumentDiagnosticsFromCache(path)
 	if issuesSlice != nil {
-		log.Info().Str("method", "domain.ide.workspace.folder.scan").Msgf("Cached results found: Skipping scan for %s", path)
+		log.Info().Str("method", method).Msgf("Cached results found: Skipping scan for %s", path)
 		f.processResults(issuesSlice)
 		return
 	}
@@ -228,3 +236,16 @@ func (f *Folder) ClearDiagnostics() {
 
 	f.documentDiagnosticCache.ClearAll()
 }
+
+func (f *Folder) IsTrusted() bool {
+	if !config.CurrentConfig().IsTrustedFolderFeatureEnabled() {
+		return true
+	}
+
+	for _, path := range config.CurrentConfig().TrustedFolders() {
+		if strings.HasPrefix(f.path, path) {
+			return true
+		}
+	}
+	return false
+}

programming_language: Go
filename: folder_test.go
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@ import (
 
 	"github.com/stretchr/testify/assert"
 
+	"github.com/snyk/snyk-ls/application/config"
 	"github.com/snyk/snyk-ls/application/server/lsp"
 	"github.com/snyk/snyk-ls/domain/ide/hover"
 	"github.com/snyk/snyk-ls/domain/snyk"
@@ -71,7 +72,7 @@ func Test_Scan_WhenCachedResultsButNoIssues_shouldNotReScan(t *testing.T) {
 	assert.Equal(t, 1, scannerRecorder.Calls())
 }
 
-func TestProcessResults_SendsDiagnosticsAndHovers(t *testing.T) {
+func Test_ProcessResults_SendsDiagnosticsAndHovers(t *testing.T) {
 	t.Skipf("test this once we have uniform abstractions for hover & diagnostics")
 	testutil.UnitTest(t)
 	hoverService := hover.NewFakeHoverService()
@@ -86,7 +87,7 @@ func TestProcessResults_SendsDiagnosticsAndHovers(t *testing.T) {
 	// assert.hoverService.GetAll()
 }
 
-func TestProcessResults_whenDifferentPaths_AddsToCache(t *testing.T) {
+func Test_ProcessResults_whenDifferentPaths_AddsToCache(t *testing.T) {
 	testutil.UnitTest(t)
 	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
 
@@ -102,7 +103,7 @@ func TestProcessResults_whenDifferentPaths_AddsToCache(t *testing.T) {
 	assert.Len(t, f.documentDiagnosticCache.Get("path2"), 1)
 }
 
-func TestProcessResults_whenSamePaths_AddsToCache(t *testing.T) {
+func Test_ProcessResults_whenSamePaths_AddsToCache(t *testing.T) {
 	testutil.UnitTest(t)
 	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
 
@@ -116,7 +117,7 @@ func TestProcessResults_whenSamePaths_AddsToCache(t *testing.T) {
 	assert.Len(t, f.documentDiagnosticCache.Get("path1"), 2)
 }
 
-func TestProcessResults_whenDifferentPaths_AccumulatesIssues(t *testing.T) {
+func Test_ProcessResults_whenDifferentPaths_AccumulatesIssues(t *testing.T) {
 	testutil.UnitTest(t)
 	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
 
@@ -132,7 +133,7 @@ func TestProcessResults_whenDifferentPaths_AccumulatesIssues(t *testing.T) {
 	assert.NotNil(t, f.documentDiagnosticCache.Get("path3"))
 }
 
-func TestProcessResults_whenSamePaths_AccumulatesIssues(t *testing.T) {
+func Test_ProcessResults_whenSamePaths_AccumulatesIssues(t *testing.T) {
 	testutil.UnitTest(t)
 	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
 
@@ -147,7 +148,7 @@ func TestProcessResults_whenSamePaths_AccumulatesIssues(t *testing.T) {
 	assert.Len(t, f.documentDiagnosticCache.Get("path1"), 3)
 }
 
-func TestProcessResults_whenSamePathsAndDuplicateIssues_DeDuplicates(t *testing.T) {
+func Test_ProcessResults_whenSamePathsAndDuplicateIssues_DeDuplicates(t *testing.T) {
 	testutil.UnitTest(t)
 	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
 
@@ -200,3 +201,50 @@ func Test_ClearDiagnostics(t *testing.T) {
 		10*time.Millisecond,
 	)
 }
+
+func Test_IsTrusted_shouldReturnFalseByDefault(t *testing.T) {
+	testutil.UnitTest(t)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
+	assert.False(t, f.IsTrusted())
+}
+
+func Test_IsTrusted_shouldReturnTrueForPathContainedInTrustedFolders(t *testing.T) {
+	testutil.UnitTest(t)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	config.CurrentConfig().SetTrustedFolders([]string{"dummy"})
+	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
+	assert.True(t, f.IsTrusted())
+}
+
+func Test_IsTrusted_shouldReturnTrueForSubfolderOfTrustedFolders_Linux(t *testing.T) {
+	testutil.IntegTest(t)
+	testutil.NotOnWindows(t, "Unix/macOS file paths are incompatible with Windows")
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	config.CurrentConfig().SetTrustedFolders([]string{"/dummy"})
+	f := NewFolder("/dummy/dummyF", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
+	assert.True(t, f.IsTrusted())
+}
+
+func Test_IsTrusted_shouldReturnFalseForDifferentFolder(t *testing.T) {
+	testutil.UnitTest(t)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	config.CurrentConfig().SetTrustedFolders([]string{"/dummy"})
+	f := NewFolder("/UntrustedPath", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
+	assert.False(t, f.IsTrusted())
+}
+
+func Test_IsTrusted_shouldReturnTrueForSubfolderOfTrustedFolders(t *testing.T) {
+	testutil.IntegTest(t)
+	testutil.OnlyOnWindows(t, "Windows specific test")
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	config.CurrentConfig().SetTrustedFolders([]string{"c:\\dummy"})
+	f := NewFolder("c:\\dummy\\dummyF", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
+	assert.True(t, f.IsTrusted())
+}
+
+func Test_IsTrusted_shouldReturnTrueIfTrustFeatureDisabled(t *testing.T) {
+	testutil.UnitTest(t) // disables trust feature
+	f := NewFolder("c:\\dummy\\dummyF", "dummy", snyk.NewTestScanner(), hover.NewFakeHoverService())
+	assert.True(t, f.IsTrusted())
+}

programming_language: Go
filename: workspace.go
change_type: ModificationType.MODIFY
diff: @@ -20,10 +20,14 @@ import (
 	"context"
 	"sync"
 
+	"github.com/rs/zerolog/log"
+
+	"github.com/snyk/snyk-ls/application/config"
 	"github.com/snyk/snyk-ls/application/server/lsp"
 	"github.com/snyk/snyk-ls/domain/ide/hover"
 	"github.com/snyk/snyk-ls/domain/observability/performance"
 	"github.com/snyk/snyk-ls/domain/snyk"
+	"github.com/snyk/snyk-ls/internal/notification"
 	"github.com/snyk/snyk-ls/internal/uri"
 )
 
@@ -33,11 +37,13 @@ var mutex = &sync.Mutex{}
 
 // Workspace represents the highest entity in an IDE that contains code. A workspace may contain multiple folders
 type Workspace struct {
-	mutex        sync.Mutex
-	folders      map[string]*Folder
-	instrumentor performance.Instrumentor
-	scanner      snyk.Scanner
-	hoverService hover.Service
+	mutex               sync.Mutex
+	folders             map[string]*Folder
+	instrumentor        performance.Instrumentor
+	scanner             snyk.Scanner
+	hoverService        hover.Service
+	trustMutex          sync.Mutex
+	trustRequestOngoing bool // for debouncing
 }
 
 func New(instrumentor performance.Instrumentor, scanner snyk.Scanner, hoverService hover.Service) *Workspace {
@@ -62,7 +68,7 @@ func Set(w *Workspace) {
 	instance = w
 }
 
-func (w *Workspace) DeleteFolder(folder string) {
+func (w *Workspace) RemoveFolder(folder string) {
 	w.mutex.Lock()
 	defer w.mutex.Unlock()
 	delete(w.folders, folder)
@@ -87,14 +93,16 @@ func (w *Workspace) GetFolderContaining(path string) (folder *Folder) {
 }
 
 func (w *Workspace) ScanWorkspace(ctx context.Context) {
-	for _, folder := range w.folders {
+	trusted, _ := w.GetFolderTrust()
+
+	for _, folder := range trusted {
 		go folder.ScanFolder(ctx)
 	}
 }
 
-func (w *Workspace) ProcessFolderChange(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) {
+func (w *Workspace) AddAndRemoveFoldersAndTriggerScan(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) {
 	for _, folder := range params.Event.Removed {
-		w.DeleteFolder(uri.PathFromUri(folder.Uri))
+		w.RemoveFolder(uri.PathFromUri(folder.Uri))
 		// TODO: check if we need to clean up the reported diagnostics, if folder was removed?
 	}
 	for _, folder := range params.Event.Added {
@@ -104,7 +112,7 @@ func (w *Workspace) ProcessFolderChange(ctx context.Context, params lsp.DidChang
 	w.ScanWorkspace(ctx)
 }
 
-func (w *Workspace) ClearIssues(ctx context.Context) {
+func (w *Workspace) ClearIssues(_ context.Context) {
 	for _, folder := range w.folders {
 		folder.ClearScannedStatus()
 		folder.ClearDiagnostics()
@@ -112,3 +120,28 @@ func (w *Workspace) ClearIssues(ctx context.Context) {
 
 	w.hoverService.ClearAllHovers()
 }
+
+func (w *Workspace) TrustFoldersAndScan(ctx context.Context, foldersToBeTrusted []*Folder) {
+	currentConfig := config.CurrentConfig()
+	trustedFolderPaths := currentConfig.TrustedFolders()
+	for _, f := range foldersToBeTrusted {
+		// we need to append and set the trusted path to the config before the scan, as the scan is checking for trust
+		trustedFolderPaths = append(trustedFolderPaths, f.Path())
+		currentConfig.SetTrustedFolders(trustedFolderPaths)
+		go f.ScanFolder(ctx)
+	}
+	notification.Send(lsp.SnykTrustedFoldersParams{TrustedFolders: trustedFolderPaths})
+}
+
+func (w *Workspace) GetFolderTrust() (trusted []*Folder, untrusted []*Folder) {
+	for _, folder := range w.folders {
+		if folder.IsTrusted() {
+			trusted = append(trusted, folder)
+			log.Info().Str("folder", folder.Path()).Msg("Trusted folder")
+		} else {
+			untrusted = append(untrusted, folder)
+			log.Info().Str("folder", folder.Path()).Msg("Untrusted folder")
+		}
+	}
+	return trusted, untrusted
+}

programming_language: Go
filename: workspace_test.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,117 @@
+/*
+ * Copyright 2022 Snyk Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package workspace
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/snyk/snyk-ls/application/config"
+	"github.com/snyk/snyk-ls/application/server/lsp"
+	"github.com/snyk/snyk-ls/domain/observability/performance"
+	"github.com/snyk/snyk-ls/domain/snyk"
+	"github.com/snyk/snyk-ls/internal/testutil"
+	"github.com/snyk/snyk-ls/internal/uri"
+)
+
+func Test_GetFolderTrust_shouldReturnTrustedAndUntrustedFolders(t *testing.T) {
+	testutil.UnitTest(t)
+	const trustedDummy = "trustedDummy"
+	const untrustedDummy = "untrustedDummy"
+	scanner := &snyk.TestScanner{}
+	w := New(performance.NewTestInstrumentor(), scanner, nil)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	config.CurrentConfig().SetTrustedFolders([]string{trustedDummy})
+	w.AddFolder(NewFolder(trustedDummy, trustedDummy, scanner, nil))
+	w.AddFolder(NewFolder(untrustedDummy, untrustedDummy, scanner, nil))
+
+	trusted, untrusted := w.GetFolderTrust()
+
+	assert.Equal(t, trustedDummy, trusted[0].path)
+	assert.Equal(t, untrustedDummy, untrusted[0].path)
+}
+
+func Test_TrustFoldersAndScan_shouldAddFoldersToTrustedFoldersAndTriggerScan(t *testing.T) {
+	testutil.UnitTest(t)
+	const trustedDummy = "trustedDummy"
+	const untrustedDummy = "untrustedDummy"
+	scanner := &snyk.TestScanner{}
+	w := New(performance.NewTestInstrumentor(), scanner, nil)
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	trustedFolder := NewFolder(trustedDummy, trustedDummy, scanner, nil)
+	w.AddFolder(trustedFolder)
+	untrustedFolder := NewFolder(untrustedDummy, untrustedDummy, scanner, nil)
+	w.AddFolder(untrustedFolder)
+
+	w.TrustFoldersAndScan(context.Background(), []*Folder{trustedFolder})
+
+	assert.Contains(t, config.CurrentConfig().TrustedFolders(), trustedFolder.path)
+	assert.NotContains(t, config.CurrentConfig().TrustedFolders(), untrustedFolder.path)
+	assert.Eventually(t, func() bool {
+		return scanner.Calls() == 1
+	}, time.Second, time.Millisecond, "scanner should be called after trust is granted")
+}
+
+func Test_AddAndRemoveFoldersAndTriggerScan(t *testing.T) {
+	testutil.UnitTest(t)
+	const trustedDummy = "trustedDummy"
+	const untrustedDummy = "untrustedDummy"
+	const toBeRemoved = "toBeRemoved"
+	trustedPathAfterConversions := uri.PathFromUri(uri.PathToUri(trustedDummy))
+	toBeRemovedAbsolutePathAfterConversions := uri.PathFromUri(uri.PathToUri(toBeRemoved))
+
+	scanner := &snyk.TestScanner{}
+	w := New(performance.NewTestInstrumentor(), scanner, nil)
+	toBeRemovedFolder := NewFolder(toBeRemovedAbsolutePathAfterConversions, toBeRemoved, scanner, nil)
+	w.AddFolder(toBeRemovedFolder)
+
+	config.CurrentConfig().SetTrustedFolderFeatureEnabled(true)
+	config.CurrentConfig().SetTrustedFolders([]string{trustedPathAfterConversions})
+
+	params := lsp.DidChangeWorkspaceFoldersParams{Event: lsp.WorkspaceFoldersChangeEvent{
+		Added: []lsp.WorkspaceFolder{
+			{Name: trustedDummy, Uri: uri.PathToUri(trustedDummy)},
+			{Name: untrustedDummy, Uri: uri.PathToUri(untrustedDummy)},
+		},
+		Removed: []lsp.WorkspaceFolder{
+			{Name: toBeRemoved, Uri: uri.PathToUri(toBeRemoved)},
+		},
+	}}
+
+	w.AddAndRemoveFoldersAndTriggerScan(context.Background(), params)
+
+	assert.Nil(t, w.GetFolderContaining(toBeRemoved))
+
+	// one call for one trusted folder
+	assert.Eventually(t, func() bool {
+		return scanner.Calls() == 1
+	}, time.Second, time.Millisecond, "scanner should be called after trust is granted")
+}
+
+func Test_Get(t *testing.T) {
+	New(nil, nil, nil)
+	assert.Equal(t, instance, Get())
+}
+
+func Test_Set(t *testing.T) {
+	w := New(nil, nil, nil)
+	Set(w)
+	assert.Equal(t, w, instance)
+}

programming_language: Go
filename: workspace_trust.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 Snyk Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package workspace
+
+func (w *Workspace) StartRequestTrustCommunication() {
+	w.trustMutex.Lock()
+	w.trustRequestOngoing = true
+	w.trustMutex.Unlock()
+}
+
+func (w *Workspace) EndRequestTrustCommunication() {
+	w.trustMutex.Lock()
+	w.trustRequestOngoing = false
+	w.trustMutex.Unlock()
+}
+
+func (w *Workspace) IsTrustRequestOngoing() bool {
+	w.trustMutex.Lock()
+	defer w.trustMutex.Unlock()
+	return w.trustRequestOngoing
+}

programming_language: Go
filename: workspace_trust_test.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 Snyk Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package workspace
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/snyk/snyk-ls/internal/testutil"
+)
+
+func TestWorkspace_TrustRequests(t *testing.T) {
+	testutil.UnitTest(t)
+	w := New(nil, nil, nil)
+	w.StartRequestTrustCommunication()
+	w.IsTrustRequestOngoing()
+	assert.True(t, w.IsTrustRequestOngoing())
+	w.EndRequestTrustCommunication()
+	assert.False(t, w.IsTrustRequestOngoing())
+}

programming_language: Go
filename: command.go
change_type: ModificationType.MODIFY
diff: @@ -17,12 +17,13 @@
 package snyk
 
 const (
-	NavigateToRangeCommand = "snyk.navigateToRange"
-	WorkspaceScanCommand   = "snyk.workspace.scan"
-	OpenBrowserCommand     = "snyk.openBrowser"
-	LoginCommand           = "snyk.login"
-	CopyAuthLinkCommand    = "snyk.copyAuthLink"
-	LogoutCommand          = "snyk.logout"
+	NavigateToRangeCommand       = "snyk.navigateToRange"
+	WorkspaceScanCommand         = "snyk.workspace.scan"
+	OpenBrowserCommand           = "snyk.openBrowser"
+	LoginCommand                 = "snyk.login"
+	CopyAuthLinkCommand          = "snyk.copyAuthLink"
+	LogoutCommand                = "snyk.logout"
+	TrustWorkspaceFoldersCommand = "snyk.trustWorkspaceFolders"
 )
 
 type Command struct {

programming_language: Go
filename: test_setup.go
change_type: ModificationType.MODIFY
diff: @@ -45,6 +45,7 @@ func UnitTest(t *testing.T) {
 	c := config.New()
 	c.SetManageBinariesAutomatically(false)
 	c.SetToken("00000000-0000-0000-0000-000000000001")
+	c.SetTrustedFolderFeatureEnabled(false)
 	config.SetCurrentConfig(c)
 	CLIDownloadLockFileCleanUp(t)
 }
@@ -123,6 +124,7 @@ func prepareTestHelper(t *testing.T, envVar string) {
 	c.SetToken(GetEnvironmentToken())
 	c.SetErrorReportingEnabled(false)
 	c.SetTelemetryEnabled(false)
+	c.SetTrustedFolderFeatureEnabled(false)
 	config.SetCurrentConfig(c)
 
 	CLIDownloadLockFileCleanUp(t)

programming_language: Go
repo_url: https://github.com/snyk/snyk-intellij-plugin
commit_details: repo_url: https://github.com/snyk/snyk-intellij-plugin
author: Michel Kaporin
author_date: 2022-11-29 13:23:22+01:00
msg: Merge pull request #417 from snyk/feat/trust

feat: add workspace trust feature
repository: repo_url: https://github.com/snyk/snyk-intellij-plugin
repo_name: snyk/snyk-intellij-plugin
description: Snyk Vulnerability Scanner for IDEs based on the IntelliJ platform
repo_language: Kotlin
owner: snyk
repo_url: https://github.com/snyk/snyk-eclipse-plugin
commit_details: repo_url: https://github.com/snyk/snyk-eclipse-plugin
author: Michel Kaporin
author_date: 2022-11-29 13:21:02+01:00
msg: Merge pull request #106 from snyk/feat/trust-feature

feat: add folder trust
repository: repo_url: https://github.com/snyk/snyk-eclipse-plugin
repo_name: snyk/snyk-eclipse-plugin
description: Eclipse plugin
repo_language: Java
owner: snyk
repo_url: https://github.com/snyk/vscode-extension
commit_details: repo_url: https://github.com/snyk/vscode-extension
author: Michel Kaporin
author_date: 2022-11-29 13:24:41+01:00
msg: feat: workspace trust (#306)
repository: repo_url: https://github.com/snyk/vscode-extension
repo_name: snyk/vscode-extension
description: Snyk extension for Visual Studio Code
repo_language: TypeScript
owner: snyk
file_changes: filename: CHANGELOG.md
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,12 @@
 # Snyk Security - Code and Open Source Dependencies Changelog
 
-## [1.7.8]
+## [1.9.0]
+
+### Added
+
+- Added workspace trust feature.
+
+## [1.8.0]
 
 ### Added
 

programming_language: Markdown
filename: package.json
change_type: ModificationType.MODIFY
diff: @@ -175,6 +175,11 @@
             "description": "Severity issues to display.",
             "scope": "window"
           },
+          "snyk.trustedFolders": {
+            "type": "array",
+            "default": [],
+            "description": "Folders to trust for Snyk scans."
+          },
           "snyk.features.preview": {
             "type": "object",
             "default": {},
@@ -271,7 +276,7 @@
       },
       {
         "view": "snyk.views.welcome",
-        "contents": "Welcome to Snyk for Visual Studio Code. 👋\nLet's start by connecting VS Code with Snyk:\n[Connect VS Code with Snyk](command:snyk.initiateLogin 'Connect with Snyk')\n👉 Snyk's mission is to finds bugs, fast. Connect with Snyk to start your first analysis!\nBy connecting your account with Snyk, you agree to the Snyk [Privacy Policy](https://snyk.io/policies/privacy), and the Snyk [Terms of Service](https://snyk.io/policies/terms-of-service).",
+        "contents": "Welcome to Snyk for Visual Studio Code. 👋\n👉 Connect with Snyk to start your first analysis!\nWhen scanning folder files, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan projects you trust. [More info](https://docs.snyk.io/ide-tools/visual-studio-code-extension/workspace-trust)\n[Trust workspace and connect](command:snyk.initiateLogin 'Connect with Snyk')\nBy connecting your account with Snyk, you agree to the Snyk [Privacy Policy](https://snyk.io/policies/privacy), and the Snyk [Terms of Service](https://snyk.io/policies/terms-of-service).",
         "when": "!snyk:error && !snyk:loggedIn"
       },
       {

programming_language: TypeScript
filename: snykLib.ts
change_type: ModificationType.MODIFY
diff: @@ -126,14 +126,14 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib {
     if (!configuration.getFeaturesConfiguration()?.ossEnabled) return;
     if (!this.ossService) throw new Error('OSS service is not initialized.');
 
-    // wait until Snyk CLI is downloaded
+    // wait until Snyk Language Server is downloaded
     await firstValueFrom(this.downloadService.downloadReady$);
 
     try {
       const oldResult = this.ossService.getResult();
       const result = await this.ossService.test(manual, reportTriggeredEvent);
 
-      if (result instanceof CliError) {
+      if (result instanceof CliError || !result) {
         return;
       }
 

programming_language: TypeScript
filename: cliService.ts
change_type: ModificationType.MODIFY
diff: @@ -2,9 +2,11 @@ import { firstValueFrom } from 'rxjs';
 import parseArgsStringToArgv from 'string-argv';
 import { AnalysisStatusProvider } from '../../common/analysis/statusProvider';
 import { IConfiguration } from '../../common/configuration/configuration';
+import { getTrustedFolders } from '../../common/configuration/trustedFolders';
 import { ErrorHandler } from '../../common/error/errorHandler';
 import { ILanguageServer } from '../../common/languageServer/languageServer';
 import { ILog } from '../../common/logger/interfaces';
+import { messages as analysisMessages } from '../../common/messages/analysisMessages';
 import { DownloadService } from '../../common/services/downloadService';
 import { ExtensionContext } from '../../common/vscode/extensionContext';
 import { IVSCodeWorkspace } from '../../common/vscode/workspace';
@@ -23,6 +25,7 @@ export abstract class CliService<CliResult> extends AnalysisStatusProvider {
   private cliProcess?: CliProcess;
   private _isLsDownloadSuccessful = true;
   private _isCliReady: boolean;
+  private _isAnyWorkspaceFolderTrusted = true;
 
   constructor(
     protected readonly extensionContext: ExtensionContext,
@@ -43,7 +46,11 @@ export abstract class CliService<CliResult> extends AnalysisStatusProvider {
     return this._isCliReady;
   }
 
-  async test(manualTrigger: boolean, reportTriggeredEvent: boolean): Promise<CliResult | CliError> {
+  get isAnyWorkspaceFolderTrusted(): boolean {
+    return this._isAnyWorkspaceFolderTrusted;
+  }
+
+  async test(manualTrigger: boolean, reportTriggeredEvent: boolean): Promise<CliResult | CliError | void> {
     this.ensureDependencies();
 
     const currentCliPath = CliExecutable.getPath(this.extensionContext.extensionPath, this.config.getCliPath());
@@ -66,6 +73,19 @@ export abstract class CliService<CliResult> extends AnalysisStatusProvider {
     const cliPath = await firstValueFrom(this.languageServer.cliReady$);
     this._isCliReady = true;
 
+    let foldersToTest = this.workspace.getWorkspaceFolders();
+    if (foldersToTest.length == 0) {
+      throw new Error('No workspace was opened.');
+    }
+
+    foldersToTest = getTrustedFolders(this.config, foldersToTest);
+    if (foldersToTest.length == 0) {
+      this.handleNoTrustedFolders();
+      this.logger.info(`Skipping Open Source scan. ${analysisMessages.noWorkspaceTrustDescription}`);
+      return;
+    }
+    this._isAnyWorkspaceFolderTrusted = true;
+
     // Start test
     this.analysisStarted();
     this.beforeTest(manualTrigger, reportTriggeredEvent);
@@ -76,11 +96,6 @@ export abstract class CliService<CliResult> extends AnalysisStatusProvider {
       if (!killed) this.logger.error('Failed to kill an already running CLI instance.');
     }
 
-    const foldersToTest = this.workspace.getWorkspaceFolders();
-    if (foldersToTest.length == 0) {
-      throw new Error('No workspace was opened.');
-    }
-
     this.cliProcess = new CliProcess(this.logger, this.config, this.workspace);
     const args = this.buildArguments(foldersToTest);
 
@@ -130,6 +145,10 @@ export abstract class CliService<CliResult> extends AnalysisStatusProvider {
     this._isLsDownloadSuccessful = false;
   }
 
+  handleNoTrustedFolders() {
+    this._isAnyWorkspaceFolderTrusted = false;
+  }
+
   private buildArguments(foldersToTest: string[]): string[] {
     const args = [];
 

programming_language: TypeScript
filename: commandController.ts
change_type: ModificationType.MODIFY
diff: @@ -20,7 +20,7 @@ import {
   VSCODE_GO_TO_SETTINGS_COMMAND,
 } from '../constants/commands';
 import { COMMAND_DEBOUNCE_INTERVAL, IDE_NAME, SNYK_NAME_EXTENSION, SNYK_PUBLISHER } from '../constants/general';
-import { SNYK_LOGIN_COMMAND } from '../constants/languageServer';
+import { SNYK_LOGIN_COMMAND, SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND } from '../constants/languageServer';
 import { ErrorHandler } from '../error/errorHandler';
 import { ILog } from '../logger/interfaces';
 import { IOpenerService } from '../services/openerService';
@@ -55,6 +55,7 @@ export class CommandController {
     this.logger.info('Initiating login');
     await this.executeCommand(SNYK_INITIATE_LOGIN_COMMAND, this.authService.initiateLogin.bind(this.authService));
     await this.commands.executeCommand(SNYK_LOGIN_COMMAND);
+    await this.commands.executeCommand(SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND);
   }
 
   async setToken(): Promise<void> {

programming_language: TypeScript
filename: configuration.ts
change_type: ModificationType.MODIFY
diff: @@ -17,6 +17,7 @@ import {
   FEATURES_PREVIEW_SETTING,
   OSS_ENABLED_SETTING,
   SEVERITY_FILTER_SETTING,
+  TRUSTED_FOLDERS,
   YES_BACKGROUND_OSS_NOTIFICATION_SETTING,
   YES_CRASH_REPORT_SETTING,
   YES_TELEMETRY_SETTING,
@@ -98,6 +99,10 @@ export interface IConfiguration {
   getSnykLanguageServerPath(): string | undefined;
 
   setShouldReportEvents(b: boolean): Promise<void>;
+
+  getTrustedFolders(): string[];
+
+  setTrustedFolders(trustedFolders: string[]): Promise<void>;
 }
 
 export class Configuration implements IConfiguration {
@@ -424,6 +429,20 @@ export class Configuration implements IConfiguration {
     return this.workspace.getConfiguration<string>(CONFIGURATION_IDENTIFIER, this.getConfigName(ADVANCED_CLI_PATH));
   }
 
+  getTrustedFolders(): string[] {
+    return (
+      this.workspace.getConfiguration<string[]>(CONFIGURATION_IDENTIFIER, this.getConfigName(TRUSTED_FOLDERS)) || []
+    );
+  }
+
+  async setTrustedFolders(trustedFolders: string[]): Promise<void> {
+    await this.workspace.updateConfiguration(
+      CONFIGURATION_IDENTIFIER,
+      this.getConfigName(TRUSTED_FOLDERS),
+      trustedFolders,
+      true,
+    );
+  }
   private getConfigName = (setting: string) => setting.replace(`${CONFIGURATION_IDENTIFIER}.`, '');
 
   private static isSingleTenant(url: URL): boolean {

programming_language: TypeScript
filename: trustedFolders.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,7 @@
+import { IConfiguration } from './configuration';
+
+export function getTrustedFolders(config: IConfiguration, workspaceFolders: string[]): string[] {
+  const trustedFolders = config.getTrustedFolders();
+
+  return workspaceFolders.filter(folder => trustedFolders.includes(folder));
+}

programming_language: TypeScript
filename: languageServer.ts
change_type: ModificationType.MODIFY
diff: @@ -10,7 +10,9 @@ export const DID_CHANGE_CONFIGURATION_METHOD = 'workspace/didChangeConfiguration
 // custom methods
 export const SNYK_HAS_AUTHENTICATED = '$/snyk.hasAuthenticated';
 export const SNYK_CLI_PATH = '$/snyk.isAvailableCli';
+export const SNYK_ADD_TRUSTED_FOLDERS = '$/snyk.addTrustedFolders';
 
 // commands
 export const SNYK_LOGIN_COMMAND = 'snyk.login';
 export const SNYK_WORKSPACE_SCAN_COMMAND = 'snyk.workspace.scan';
+export const SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND = 'snyk.trustWorkspaceFolders';

programming_language: TypeScript
filename: settings.ts
change_type: ModificationType.MODIFY
diff: @@ -22,3 +22,4 @@ export const ADVANCED_CLI_PATH = `${CONFIGURATION_IDENTIFIER}.advanced.cliPath`;
 export const ADVANCED_CUSTOM_LS_PATH = `${CONFIGURATION_IDENTIFIER}.advanced.languageServerPath`;
 
 export const SEVERITY_FILTER_SETTING = `${CONFIGURATION_IDENTIFIER}.severity`;
+export const TRUSTED_FOLDERS = `${CONFIGURATION_IDENTIFIER}.trustedFolders`;

programming_language: TypeScript
filename: languageServer.ts
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,12 @@ import { firstValueFrom, ReplaySubject } from 'rxjs';
 import { IAuthenticationService } from '../../base/services/authenticationService';
 import { CLI_INTEGRATION_NAME } from '../../cli/contants/integration';
 import { Configuration, IConfiguration } from '../configuration/configuration';
-import { SNYK_CLI_PATH, SNYK_HAS_AUTHENTICATED, SNYK_LANGUAGE_SERVER_NAME } from '../constants/languageServer';
+import {
+  SNYK_ADD_TRUSTED_FOLDERS,
+  SNYK_CLI_PATH,
+  SNYK_HAS_AUTHENTICATED,
+  SNYK_LANGUAGE_SERVER_NAME,
+} from '../constants/languageServer';
 import { CONFIGURATION_IDENTIFIER } from '../constants/settings';
 import { ErrorHandler } from '../error/errorHandler';
 import { ILog } from '../logger/interfaces';
@@ -91,39 +96,10 @@ export class LanguageServer implements ILanguageServer {
 
     // Create the language client and start the client.
     this.client = this.languageClientAdapter.create('Snyk LS', SNYK_LANGUAGE_SERVER_NAME, serverOptions, clientOptions);
-
     this.client
       .onReady()
       .then(() => {
-        this.client.onNotification(SNYK_HAS_AUTHENTICATED, ({ token }: { token: string }) => {
-          this.authenticationService.updateToken(token).catch((error: Error) => {
-            ErrorHandler.handle(error, this.logger, error.message);
-          });
-        });
-
-        this.client.onNotification(SNYK_CLI_PATH, ({ cliPath }: { cliPath: string }) => {
-          if (!cliPath) {
-            ErrorHandler.handle(
-              new Error("CLI path wasn't provided by language server on $/snyk.isAvailableCli notification " + cliPath),
-              this.logger,
-              "CLI path wasn't provided by language server on notification",
-            );
-            return;
-          }
-
-          const currentCliPath = this.configuration.getCliPath();
-          if (currentCliPath != cliPath) {
-            this.logger.info('Setting Snyk CLI path to: ' + cliPath);
-            void this.configuration
-              .setCliPath(cliPath)
-              .then(() => {
-                this.cliReady$.next(cliPath);
-              })
-              .catch((error: Error) => {
-                ErrorHandler.handle(error, this.logger, error.message);
-              });
-          }
-        });
+        this.registerListeners(this.client);
       })
       .catch((error: Error) => ErrorHandler.handle(error, this.logger, error.message));
 
@@ -132,6 +108,44 @@ export class LanguageServer implements ILanguageServer {
     this.logger.info('Snyk Language Server started');
   }
 
+  private registerListeners(client: LanguageClient): void {
+    client.onNotification(SNYK_HAS_AUTHENTICATED, ({ token }: { token: string }) => {
+      this.authenticationService.updateToken(token).catch((error: Error) => {
+        ErrorHandler.handle(error, this.logger, error.message);
+      });
+    });
+
+    client.onNotification(SNYK_CLI_PATH, ({ cliPath }: { cliPath: string }) => {
+      if (!cliPath) {
+        ErrorHandler.handle(
+          new Error("CLI path wasn't provided by language server on $/snyk.isAvailableCli notification " + cliPath),
+          this.logger,
+          "CLI path wasn't provided by language server on notification",
+        );
+        return;
+      }
+
+      const currentCliPath = this.configuration.getCliPath();
+      if (currentCliPath != cliPath) {
+        this.logger.info('Setting Snyk CLI path to: ' + cliPath);
+        void this.configuration
+          .setCliPath(cliPath)
+          .then(() => {
+            this.cliReady$.next(cliPath);
+          })
+          .catch((error: Error) => {
+            ErrorHandler.handle(error, this.logger, error.message);
+          });
+      }
+    });
+
+    client.onNotification(SNYK_ADD_TRUSTED_FOLDERS, ({ trustedFolders }: { trustedFolders: string[] }) => {
+      this.configuration.setTrustedFolders(trustedFolders).catch((error: Error) => {
+        ErrorHandler.handle(error, this.logger, error.message);
+      });
+    });
+  }
+
   // Initialization options are not semantically equal to server settings, thus separated here
   // https://github.com/microsoft/language-server-protocol/issues/567
   async getInitializationOptions(): Promise<InitializationOptions> {

programming_language: TypeScript
filename: settings.ts
change_type: ModificationType.MODIFY
diff: @@ -20,6 +20,8 @@ export type ServerSettings = {
   manageBinariesAutomatically?: string;
   cliPath?: string;
   token?: string;
+  enableTrustedFoldersFeature?: string;
+  trustedFolders?: string[];
 };
 
 export class LanguageServerSettings {
@@ -36,6 +38,8 @@ export class LanguageServerSettings {
       organization: configuration.organization,
       token: await configuration.getToken(),
       manageBinariesAutomatically: `${configuration.isAutomaticDependencyManagementEnabled()}`,
+      enableTrustedFoldersFeature: 'true',
+      trustedFolders: configuration.getTrustedFolders(),
     };
   }
 }

programming_language: TypeScript
filename: analysisMessages.ts
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,9 @@
 export const messages = {
   scanFailed: 'Scan failed',
+  noWorkspaceTrust: 'No workspace folder was granted trust',
   clickToProblem: 'Click here to see the problem.',
   allSeverityFiltersDisabled: 'Please enable severity filters to see the results.',
   duration: (time: string, day: string): string => `Analysis finished at ${time}, ${day}`,
+  noWorkspaceTrustDescription:
+    'None of workspace folders were trusted. If you trust the workspace, you can add it to the list of trusted folders in the extension settings, or when prompted by the extension next time.',
 };

programming_language: TypeScript
filename: analysisTreeNodeProvider.ts
change_type: ModificationType.MODIFY
diff: @@ -72,5 +72,15 @@ export abstract class AnalysisTreeNodeProvder extends TreeNodeProvider {
     });
   }
 
+  protected getNoWorkspaceTrustTreeNode(): TreeNode {
+    return new TreeNode({
+      text: messages.noWorkspaceTrust,
+      command: {
+        command: SNYK_SHOW_OUTPUT_COMMAND,
+        title: '',
+      },
+    });
+  }
+
   protected abstract getFilteredIssues(issues: readonly unknown[]): readonly unknown[];
 }

programming_language: TypeScript
filename: configurationWatcher.ts
change_type: ModificationType.MODIFY
diff: @@ -13,6 +13,7 @@ import {
   CODE_SECURITY_ENABLED_SETTING,
   OSS_ENABLED_SETTING,
   SEVERITY_FILTER_SETTING,
+  TRUSTED_FOLDERS,
   YES_TELEMETRY_SETTING,
 } from '../constants/settings';
 import { ErrorHandler } from '../error/errorHandler';
@@ -68,6 +69,7 @@ class ConfigurationWatcher implements IWatcher {
         SEVERITY_FILTER_SETTING,
         ADVANCED_CUSTOM_ENDPOINT,
         ADVANCED_CUSTOM_LS_PATH,
+        TRUSTED_FOLDERS,
       ].find(config => event.affectsConfiguration(config));
 
       if (change) {

programming_language: TypeScript
filename: codeService.ts
change_type: ModificationType.MODIFY
diff: @@ -4,10 +4,12 @@ import { v4 as uuidv4 } from 'uuid';
 import { AnalysisStatusProvider } from '../common/analysis/statusProvider';
 import { IAnalytics, SupportedAnalysisProperties } from '../common/analytics/itly';
 import { FeaturesConfiguration, IConfiguration } from '../common/configuration/configuration';
+import { getTrustedFolders } from '../common/configuration/trustedFolders';
 import { IDE_NAME } from '../common/constants/general';
 import { ErrorHandler } from '../common/error/errorHandler';
 import { ILog } from '../common/logger/interfaces';
 import { Logger } from '../common/logger/logger';
+import { messages as generalAnalysisMessages } from '../common/messages/analysisMessages';
 import { LearnService } from '../common/services/learnService';
 import { IViewManagerService } from '../common/services/viewManagerService';
 import { User } from '../common/user';
@@ -47,6 +49,7 @@ export interface ISnykCodeService extends AnalysisStatusProvider, Disposable {
   readonly falsePositiveProvider: IWebViewProvider<FalsePositiveWebviewModel>;
   hasError: boolean;
   hasTransientError: boolean;
+  isAnyWorkspaceFolderTrusted: boolean;
 
   startAnalysis(paths: string[], manual: boolean, reportTriggeredEvent: boolean): Promise<void>;
   clearBundle(): void;
@@ -74,6 +77,7 @@ export class SnykCodeService extends AnalysisStatusProvider implements ISnykCode
   private _analysisProgress = '';
   private temporaryFailed = false;
   private failed = false;
+  private _isAnyWorkspaceFolderTrusted = true;
 
   constructor(
     readonly extensionContext: ExtensionContext,
@@ -134,6 +138,9 @@ export class SnykCodeService extends AnalysisStatusProvider implements ISnykCode
   get hasTransientError(): boolean {
     return this.temporaryFailed;
   }
+  get isAnyWorkspaceFolderTrusted(): boolean {
+    return this._isAnyWorkspaceFolderTrusted;
+  }
 
   get analysisStatus(): string {
     return this._analysisStatus;
@@ -150,6 +157,15 @@ export class SnykCodeService extends AnalysisStatusProvider implements ISnykCode
     const enabledFeatures = this.config.getFeaturesConfiguration();
     const requestId = uuidv4();
 
+    paths = getTrustedFolders(this.config, paths);
+    if (!paths.length) {
+      this._isAnyWorkspaceFolderTrusted = false;
+      this.viewManagerService.refreshCodeAnalysisViews(enabledFeatures);
+      this.logger.info(`Skipping Code scan. ${generalAnalysisMessages.noWorkspaceTrustDescription}`);
+      return;
+    }
+    this._isAnyWorkspaceFolderTrusted = true;
+
     try {
       Logger.info(analysisMessages.started);
 

programming_language: TypeScript
filename: issueTreeProvider.ts
change_type: ModificationType.MODIFY
diff: @@ -51,6 +51,8 @@ export class IssueTreeProvider extends AnalysisTreeNodeProvder {
       return this.getTransientErrorTreeNodes();
     } else if (this.snykCode.hasError) {
       return [this.getErrorEncounteredTreeNode()];
+    } else if (!this.snykCode.isAnyWorkspaceFolderTrusted) {
+      return [this.getNoWorkspaceTrustTreeNode()];
     }
 
     if (this.diagnosticCollection) {

programming_language: TypeScript
filename: ossService.ts
change_type: ModificationType.MODIFY
diff: @@ -106,6 +106,11 @@ export class OssService extends CliService<OssResult> {
     super.handleLsDownloadFailure(error);
   }
 
+  override handleNoTrustedFolders(): void {
+    super.handleNoTrustedFolders();
+    this.viewManagerService.refreshOssView();
+  }
+
   activateSuggestionProvider(): void {
     this.suggestionProvider.activate();
   }

programming_language: TypeScript
filename: ossVulnerabilityTreeProvider.ts
change_type: ModificationType.MODIFY
diff: @@ -50,6 +50,8 @@ export class OssVulnerabilityTreeProvider extends AnalysisTreeNodeProvder {
           text: messages.cookingDependencies,
         }),
       ];
+    } else if (!this.ossService.isAnyWorkspaceFolderTrusted) {
+      return [this.getNoWorkspaceTrustTreeNode()];
     }
 
     if (this.ossService.isAnalysisRunning) {

programming_language: TypeScript
filename: cliService.test.ts
change_type: ModificationType.MODIFY
diff: @@ -50,10 +50,12 @@ suite('CliService', () => {
       getGlobalStateValue: () => undefined,
     } as unknown as ExtensionContext;
 
+    const testFolderPath = 'test-folder';
     configuration = {
       getAdditionalCliParameters: () => '',
       isAutomaticDependencyManagementEnabled: () => true,
       getCliPath: () => undefined,
+      getTrustedFolders: () => [testFolderPath],
     } as unknown as IConfiguration;
 
     downloadService = {
@@ -71,7 +73,7 @@ suite('CliService', () => {
       logger,
       configuration,
       {
-        getWorkspaceFolders: () => ['test-folder'],
+        getWorkspaceFolders: () => [testFolderPath],
       } as IVSCodeWorkspace,
       downloadService,
       ls,

programming_language: TypeScript
filename: trustedFolders.test.ts
change_type: ModificationType.ADD
diff: @@ -0,0 +1,38 @@
+import { deepStrictEqual } from 'assert';
+import { IConfiguration } from '../../../../snyk/common/configuration/configuration';
+import { getTrustedFolders } from '../../../../snyk/common/configuration/trustedFolders';
+
+suite('Trusted Folders', () => {
+  test('Single folder trusted', () => {
+    const config = {
+      getTrustedFolders: () => ['/test/workspace', '/test/workspace2'],
+    } as IConfiguration;
+    const workspaceFolders = ['/test/workspace'];
+
+    const trustedFolders = getTrustedFolders(config, workspaceFolders);
+
+    deepStrictEqual(trustedFolders, ['/test/workspace']);
+  });
+
+  test('Multiple folders trusted', () => {
+    const config = {
+      getTrustedFolders: () => ['/test/workspace', '/test/workspace2'],
+    } as IConfiguration;
+    const workspaceFolders = ['/test/workspace', '/test/workspace2'];
+
+    const trustedFolders = getTrustedFolders(config, workspaceFolders);
+
+    deepStrictEqual(trustedFolders, ['/test/workspace', '/test/workspace2']);
+  });
+
+  test('No folders trusted', () => {
+    const config = {
+      getTrustedFolders: (): string[] => [],
+    } as IConfiguration;
+    const workspaceFolders = ['/test/workspace', '/test/workspace2'];
+
+    const trustedFolders = getTrustedFolders(config, workspaceFolders);
+
+    deepStrictEqual(trustedFolders, []);
+  });
+});

programming_language: TypeScript
filename: languageServer.test.ts
change_type: ModificationType.MODIFY
diff: @@ -49,6 +49,9 @@ suite('Language Server', () => {
           reportFalsePositives: false,
         };
       },
+      getTrustedFolders(): string[] {
+        return ['/trusted/test/folder'];
+      },
     } as IConfiguration;
 
     downloadService = {
@@ -87,6 +90,8 @@ suite('Language Server', () => {
       additionalParams: '--all-projects',
       manageBinariesAutomatically: 'true',
       deviceId: user.anonymousId,
+      enableTrustedFoldersFeature: 'true',
+      trustedFolders: ['/trusted/test/folder'],
     };
 
     deepStrictEqual(await languageServer.getInitializationOptions(), expectedInitializationOptions);

programming_language: TypeScript
filename: middleware.test.ts
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,7 @@ suite('Language Server: Middleware', () => {
           reportFalsePositives: false,
         };
       },
+      getTrustedFolders: () => ['/trusted/test/folder'],
     } as IConfiguration;
   });
 
@@ -78,6 +79,8 @@ suite('Language Server: Middleware', () => {
       serverResult.cliPath,
       CliExecutable.getPath(extensionContextMock.extensionPath, configuration.getCliPath()),
     );
+    assert.strictEqual(serverResult.enableTrustedFoldersFeature, 'true');
+    assert.deepStrictEqual(serverResult.trustedFolders, configuration.getTrustedFolders());
   });
 
   test('Configuration request should return an error', async () => {

programming_language: TypeScript
filename: ossService.test.ts
change_type: ModificationType.MODIFY
diff: @@ -33,6 +33,7 @@ suite('OssService', () => {
     } as unknown as ILanguageServer;
     ls.cliReady$.next('');
 
+    const testFolderPath = '';
     ossService = new OssService(
       {
         extensionPath,
@@ -42,10 +43,11 @@ suite('OssService', () => {
         getAdditionalCliParameters: () => '',
         getCliPath: () => undefined,
         isAutomaticDependencyManagementEnabled: () => true,
-      } as IConfiguration,
+        getTrustedFolders: () => [testFolderPath],
+      } as unknown as IConfiguration,
       {} as IWebViewProvider<OssIssueCommandArg>,
       {
-        getWorkspaceFolders: () => [''],
+        getWorkspaceFolders: () => [testFolderPath],
       } as IVSCodeWorkspace,
       {
         refreshOssView: () => undefined,

programming_language: TypeScript