CVE-2022-24441: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: 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
CWE 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.
CVSS v3 base score: 8.8
Published: 2022-11-30T13:15Z
Repository: https://github.com/snyk/snyk-visual-studio-plugin
Language: C#
Files changed: CHANGELOG.md, Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj, Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml, Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml.cs, Snyk.VisualStudio.Extension.Shared/Service/ISnykServiceProvider.cs, Snyk.VisualStudio.Extension.Shared/Service/IWorkspaceTrustService.cs, Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs, Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs, Snyk.VisualStudio.Extension.Shared/Service/WorkspaceTrustService.cs, Snyk.VisualStudio.Extension.Shared/Settings/IUserStorageSettingsService.cs, Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs, Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs, Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems, Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml, Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs, Snyk.VisualStudio.Extension.Tests/Service/WorkspaceTrustServiceTest.cs, Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj, Snyk.VisualStudio.Extension/TrustDialogWindow.xaml, Snyk.VisualStudio.Extension/TrustDialogWindow.xaml.cs
Commit date: 2022-11-29 13:22:28 +0100
Commit message: feat: add workspace trust (#217)


Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08e1c21..1254574 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,12 @@
 # Snyk Changelog
 
+## [1.1.31]
+
+### Added
+- Adds workspace trust mechanism to ensure scans are run on the trusted projects.
+
 ## [1.1.30]
 
 ### Changed
 - Link to GH issues in the error notification bar is replaced with a link to Snyk support.
 
@@ -46,11 +51,11 @@
 
 ## [1.1.23]
 
 ### Added
 - Organization description information in settings.
- 
+
 ### Fixed
 - Changing custom endpoint settings leads to authentication errors.
 
 ## [1.1.22]
 
diff --git a/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj b/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj
index 30dfb99..6fd88d3 100644
--- a/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj
+++ b/Snyk.VisualStudio.Extension.2022/Snyk.VisualStudio.Extension.2022.csproj
@@ -47,10 +47,13 @@
     <ErrorReport>prompt</ErrorReport>
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="TrustDialogWindow.xaml.cs">
+      <DependentUpon>TrustDialogWindow.xaml</DependentUpon>
+    </Compile>
   </ItemGroup>
   <ItemGroup>
     <None Include="source.extension.vsixmanifest">
       <SubType>Designer</SubType>
     </None>
@@ -118,10 +121,16 @@
     <ProjectReference Include="..\Snyk.Common\Snyk.Common.csproj">
       <Project>{7912ad6d-c154-4504-9ab3-8767d3c46e89}</Project>
       <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)' != ''" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
diff --git a/Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml b/Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml
new file mode 100644
index 0000000..d50ef33
--- /dev/null
+++ b/Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml
@@ -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
diff --git a/Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml.cs b/Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml.cs
new file mode 100644
index 0000000..14deefa
--- /dev/null
+++ b/Snyk.VisualStudio.Extension.2022/TrustDialogWindow.xaml.cs
@@ -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();
+            }
+        }
+    }
+}
diff --git a/Snyk.VisualStudio.Extension.Shared/Service/ISnykServiceProvider.cs b/Snyk.VisualStudio.Extension.Shared/Service/ISnykServiceProvider.cs
index bb06b24..84a61be 100644
--- a/Snyk.VisualStudio.Extension.Shared/Service/ISnykServiceProvider.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Service/ISnykServiceProvider.cs
@@ -36,10 +36,12 @@
         /// <summary>
         /// Gets Solution service instance.
         /// </summary>
         ISolutionService SolutionService { get; }
 
+        IWorkspaceTrustService WorkspaceTrustService { get; }
+
         /// <summary>
         /// Gets Tasks service instance.
         /// </summary>
         SnykTasksService TasksService { get; }
 
diff --git a/Snyk.VisualStudio.Extension.Shared/Service/IWorkspaceTrustService.cs b/Snyk.VisualStudio.Extension.Shared/Service/IWorkspaceTrustService.cs
new file mode 100644
index 0000000..f147408
--- /dev/null
+++ b/Snyk.VisualStudio.Extension.Shared/Service/IWorkspaceTrustService.cs
@@ -0,0 +1,9 @@
+﻿namespace Snyk.VisualStudio.Extension.Shared.Service
+{
+    public interface IWorkspaceTrustService
+    {
+        bool IsFolderTrusted(string absoluteFolderPath);
+
+        void AddFolderToTrusted(string absoluteFolderPath);
+    }
+}
diff --git a/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs b/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
index 8ac6cf5..9d1f254 100644
--- a/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Service/SnykService.cs
@@ -50,10 +50,12 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
 
         private ISnykApiService apiService;
 
         private ISentryService sentryService;
 
+        private IWorkspaceTrustService workspaceTrustService;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="SnykService"/> class.
         /// </summary>
         /// <param name="serviceProvider">Snyk service provider implementation.</param>
         public SnykService(IAsyncServiceProvider serviceProvider) => this.serviceProvider = serviceProvider;
@@ -66,10 +68,15 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
         /// <summary>
         /// Gets solution service.
         /// </summary>
         public ISolutionService SolutionService => SnykSolutionService.Instance;
 
+        /// <summary>
+        /// Gets solution service.
+        /// </summary>
+        public IWorkspaceTrustService WorkspaceTrustService => this.workspaceTrustService;
+
         /// <summary>
         /// Gets Tasks service.
         /// </summary>
         public SnykTasksService TasksService => this.tasksService;
 
@@ -239,10 +246,11 @@ namespace Snyk.VisualStudio.Extension.Shared.Service
                 await SnykTasksService.InitializeAsync(this);
 
                 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);
                 VsCodeService.Initialize();
 
diff --git a/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs b/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs
index 33baac9..d77c2a4 100644
--- a/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Service/SnykTasksService.cs
@@ -2,18 +2,21 @@
 {
     using System;
     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;
     using Snyk.Common;
     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;
 
     /// <summary>
     /// Incapsulate logic with background tasks work.
@@ -196,11 +199,10 @@
         /// </summary>
         /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
         public async Task ScanAsync()
         {
             Logger.Information("Enter Scan method");
-
             try
             {
                 var selectedFeatures = await this.GetFeaturesSettingsAsync();
 
                 if (!this.serviceProvider.SolutionService.IsSolutionOpen())
@@ -210,10 +212,17 @@
                     Logger.Information("Solution not opened");
 
                     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);
                 var snykCodeScanTask = this.ScanSnykCodeAsync(selectedFeatures);
 
@@ -223,10 +232,45 @@
             {
                 Logger.Error(ex, "Error on scan");
             }
         }
 
+        /// <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>
         /// <param name="downloadFinishedCallback"><see cref="CliDownloadFinishedCallback"/> callback object.</param>
         public void Download(CliDownloadFinishedCallback downloadFinishedCallback = null)
diff --git a/Snyk.VisualStudio.Extension.Shared/Service/WorkspaceTrustService.cs b/Snyk.VisualStudio.Extension.Shared/Service/WorkspaceTrustService.cs
new file mode 100644
index 0000000..8a4da12
--- /dev/null
+++ b/Snyk.VisualStudio.Extension.Shared/Service/WorkspaceTrustService.cs
@@ -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;
+        }
+    }
+}
diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/IUserStorageSettingsService.cs b/Snyk.VisualStudio.Extension.Shared/Settings/IUserStorageSettingsService.cs
new file mode 100644
index 0000000..c0521ab
--- /dev/null
+++ b/Snyk.VisualStudio.Extension.Shared/Settings/IUserStorageSettingsService.cs
@@ -0,0 +1,9 @@
+﻿namespace Snyk.VisualStudio.Extension.Shared.Settings
+{
+    using System.Collections.Generic;
+
+    public interface IUserStorageSettingsService
+    {
+        ISet<string> TrustedFolders { get; set; }
+    }
+}
diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs
index 2518a24..daf2806 100644
--- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykSettings.cs
@@ -59,10 +59,15 @@
         /// Gets or sets a value indicating whether binaries auto update is enabled.
         /// </summary>
         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
diff --git a/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs b/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs
index 5885dac..dd3b590 100644
--- a/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs
+++ b/Snyk.VisualStudio.Extension.Shared/Settings/SnykUserStorageSettingsService.cs
@@ -1,17 +1,18 @@
 ﻿namespace Snyk.VisualStudio.Extension.Shared.Settings
 {
     using System;
+    using System.Collections.Generic;
     using System.Threading.Tasks;
     using Serilog;
     using Snyk.Common;
     using Snyk.VisualStudio.Extension.Shared.Service;
 
     /// <summary>
     /// Service for solution settings.
     /// </summary>
-    public class SnykUserStorageSettingsService
+    public class SnykUserStorageSettingsService : IUserStorageSettingsService
     {
         private static readonly ILogger Logger = LogManager.ForContext<SnykUserStorageSettingsService>();
 
         private readonly ISolutionService solutionService;
 
@@ -49,10 +50,24 @@
                 settings.CustomCliPath = value;
                 this.settingsLoader.Save(settings);
             }
         }
 
+        /// <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>
         /// <returns>string.</returns>
         public async Task<string> GetAdditionalOptionsAsync()
diff --git a/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems b/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems
index 6119770..8f76247 100644
--- a/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems
+++ b/Snyk.VisualStudio.Extension.Shared/Snyk.VisualStudio.Extension.Shared.projitems
@@ -29,17 +29,20 @@
     <Compile Include="$(MSBuildThisFileDirectory)Microsoft\HtmlParser\HtmlToXamlConverter.cs" />
     <Compile Include="$(MSBuildThisFileDirectory)Microsoft\UI\HtmlRichTextBoxBehavior.cs" />
     <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>
     <Compile Include="$(MSBuildThisFileDirectory)Settings\SnykGeneralSettingsUserControl.cs">
       <SubType>UserControl</SubType>
diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml
index 0c5fd76..27eff58 100644
--- a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml
+++ b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml
@@ -1,14 +1,14 @@
 ﻿<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>
             <ImageSource x:Key="SnykDogLogo">pack://application:,,,/Snyk.VisualStudio.Extension;component/SnykDogLogoFullSize.png</ImageSource>
         </Grid.Resources>
@@ -30,15 +30,15 @@
             </c:TextField>
             <StackPanel Margin="0, 8, 0, 0">
                 <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"/>
 
             <StackPanel HorizontalAlignment="Center" Orientation="Vertical" Margin="40,20,20,20">
                 <TextBlock FontWeight="Bold">
@@ -46,30 +46,36 @@
                 </TextBlock>
                 <TextBlock Margin="0,20,0,0">
                     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>
                             <Style TargetType="Border">
                                 <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
                     </Hyperlink>,
                     and the Snyk
diff --git a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs
index 97e8273..4e332ae 100644
--- a/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs
+++ b/Snyk.VisualStudio.Extension.Shared/UI/Toolwindow/MessagePanel.xaml.cs
@@ -1,22 +1,29 @@
 ﻿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>
     /// Interaction logic for MessagePanel.xaml.
     /// </summary>
     public partial class MessagePanel : UserControl
     {
+        private static readonly ILogger Logger = LogManager.ForContext<MessagePanel>();
         private readonly IList<StackPanel> panels;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MessagePanel"/> class.
         /// </summary>
@@ -118,10 +125,40 @@
 
             await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
             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);
         }
 
         private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs args)
diff --git a/Snyk.VisualStudio.Extension.Tests/Service/WorkspaceTrustServiceTest.cs b/Snyk.VisualStudio.Extension.Tests/Service/WorkspaceTrustServiceTest.cs
new file mode 100644
index 0000000..2b13e5c
--- /dev/null
+++ b/Snyk.VisualStudio.Extension.Tests/Service/WorkspaceTrustServiceTest.cs
@@ -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;
+        }
+    }
+}
diff --git a/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj b/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj
index 0147173..90acf93 100644
--- a/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj
+++ b/Snyk.VisualStudio.Extension/Snyk.VisualStudio.Extension.csproj
@@ -24,11 +24,11 @@
     <CopyBuildOutputToOutputDirectory>true</CopyBuildOutputToOutputDirectory>
     <CopyOutputSymbolsToOutputDirectory>true</CopyOutputSymbolsToOutputDirectory>
     <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>
     <DebugType>full</DebugType>
     <Optimize>false</Optimize>
@@ -119,13 +119,21 @@
     </PackageReference>
     <PackageReference Include="System.Threading.Tasks.Extensions">
       <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" />
   <Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
@@ -142,32 +150,19 @@
       <Output TaskParameter="ExitCode" PropertyName="SuccessOrNot" />
     </Exec>
   </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
diff --git a/Snyk.VisualStudio.Extension/TrustDialogWindow.xaml b/Snyk.VisualStudio.Extension/TrustDialogWindow.xaml
new file mode 100644
index 0000000..cbc8718
--- /dev/null
+++ b/Snyk.VisualStudio.Extension/TrustDialogWindow.xaml
@@ -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
diff --git a/Snyk.VisualStudio.Extension/TrustDialogWindow.xaml.cs b/Snyk.VisualStudio.Extension/TrustDialogWindow.xaml.cs
new file mode 100644
index 0000000..56c8171
--- /dev/null
+++ b/Snyk.VisualStudio.Extension/TrustDialogWindow.xaml.cs
@@ -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();
+            }
+        }
+    }
+}

Vulnerable code:  

        /// Gets or sets the value of the custom CLI path
    public class SnykUserStorageSettingsService
             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" 
             mc:Ignorable="d" 
        
        
                    2. Analyze code for issues and vulnerabilities                
                        Test code now
                <TextBlock FontSize="11">                    
	<DeployVsixExtensionFilesDependsOn>$(DeployVsixExtensionFilesDependsOn);SaveSettingsJsonFile</DeployVsixExtensionFilesDependsOn>
  <ItemGroup />
      <ItemGroup>
          <VSIXSourceItem Include="@(ReferencePath)" />
      </ItemGroup>
  <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="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"/>
Fixed code: ## [1.1.31]

### Added
- Adds workspace trust mechanism to ensure scans are run on the trusted projects.


    <Compile Include="TrustDialogWindow.xaml.cs">
      <DependentUpon>TrustDialogWindow.xaml</DependentUpon>
    </Compile>
  <ItemGroup>
    <Page Include="TrustDialogWindow.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
  </ItemGroup>
﻿<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>
﻿
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();
            }
        }
    }
}
        IWorkspaceTrustService WorkspaceTrustService { get; }

﻿namespace Snyk.VisualStudio.Extension.Shared.Service
{
    public interface IWorkspaceTrustService
    {
        bool IsFolderTrusted(string absoluteFolderPath);

        void AddFolderToTrusted(string absoluteFolderPath);
    }
}
        private IWorkspaceTrustService workspaceTrustService;

        /// <summary>
        /// Gets solution service.
        /// </summary>
        public IWorkspaceTrustService WorkspaceTrustService => this.workspaceTrustService;

                this.workspaceTrustService = new WorkspaceTrustService(this.UserStorageSettingsService);
    using Community.VisualStudio.Toolkit;
    using Microsoft.VisualStudio.Shell.Interop;
    using Snyk.VisualStudio.Extension.Shared.UI;
                var isFolderTrusted = await this.IsFolderTrustedAsync();
                if (!isFolderTrusted)
                {
                    Logger.Information("Workspace folder was not trusted for scanning.");
                    return;
                }

        /// <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;
            }
        }

﻿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;
        }
    }
}
﻿namespace Snyk.VisualStudio.Extension.Shared.Settings
{
    using System.Collections.Generic;

    public interface IUserStorageSettingsService
    {
        ISet<string> TrustedFolders { get; set; }
    }
}
        /// Gets or sets the value of the custom CLI path.

        /// <summary>
        /// Gets or sets an array of workspace trusted folders.
        /// </summary>
        public ISet<string> TrustedFolders { get; set; } = new HashSet<string>();
    using System.Collections.Generic;
    public class SnykUserStorageSettingsService : IUserStorageSettingsService
        /// <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);
            }
        }

    <Compile Include="$(MSBuildThisFileDirectory)Service\IWorkspaceTrustService.cs" />
    <Compile Include="$(MSBuildThisFileDirectory)Service\WorkspaceTrustService.cs" />
    <Compile Include="$(MSBuildThisFileDirectory)Settings\IUserStorageSettingsService.cs" />
             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"
             mc:Ignorable="d"


                    2. Analyze code for issues and vulnerabilities
                <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>
                        Trust project and scan
                <TextBlock FontSize="11">
    using System;
    using Community.VisualStudio.Toolkit;
    using Microsoft.VisualStudio.Shell.Interop;
    using Serilog;
    using Serilog.Core;
    using Snyk.Common;
        private static readonly ILogger Logger = LogManager.ForContext<MessagePanel>();
            // 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);
                }
            }

﻿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;
        }
    }
}
    <DeployVsixExtensionFilesDependsOn>$(DeployVsixExtensionFilesDependsOn);SaveSettingsJsonFile</DeployVsixExtensionFilesDependsOn>
  <ItemGroup>
    <Page Include="TrustDialogWindow.xaml">
      <Generator>MSBuild:Compile</Generator>
      <SubType>Designer</SubType>
    </Page>
  </ItemGroup>
    <Compile Include="TrustDialogWindow.xaml.cs">
      <DependentUpon>TrustDialogWindow.xaml</DependentUpon>
    </Compile>
    <ItemGroup>
      <VSIXSourceItem Include="@(ReferencePath)" />
    </ItemGroup>
  <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="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" />
﻿<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>
﻿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();
            }
        }
    }
}