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-ls
Language: Go
Files changed: .goreleaser.yaml, Makefile, README.md, application/config/config.go, application/config/config_test.go, application/server/configuration.go, application/server/configuration_test.go, application/server/execute_command.go, application/server/execute_command_test.go, application/server/lsp/message_types.go, application/server/server.go, application/server/server_test.go, application/server/trust.go, application/server/trust_test.go, domain/ide/workspace/folder.go, domain/ide/workspace/folder_test.go, domain/ide/workspace/workspace.go, domain/ide/workspace/workspace_test.go, domain/ide/workspace/workspace_trust.go, domain/ide/workspace/workspace_trust_test.go, domain/snyk/command.go, internal/testutil/test_setup.go
Commit date: 2022-11-29 13:17:08 +0100
Commit message: feat: add trust management mechanism (#187)


Diff: diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 3b689df..3e1d7d3 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -58,6 +58,6 @@ checksum:
 dist: build
 
 env:
   - GO111MODULE=on
   - CGO_ENABLED=0
-  - LS_PROTOCOL_VERSION=3
+  - LS_PROTOCOL_VERSION=4
diff --git a/Makefile b/Makefile
index 5025431..c670d75 100644
--- a/Makefile
+++ b/Makefile
@@ -35,11 +35,11 @@ TIMEOUT := "-timeout=15m"
 .PHONY: tools
 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
 	@if [ ! -d ./.bin/pact ]; then\
 		echo "--- 🛠 Installing Pact CLI dependencies";\
diff --git a/README.md b/README.md
index 20a9e24..3134ed5 100644
--- a/README.md
+++ b/README.md
@@ -73,10 +73,19 @@ Right now the language server supports the following actions:
   {
     "cliPath": "/a/path/to/cli-executable"
   }
   ```
 
+- Trust Notification
+  - method: `$/snyk.addTrustedFolders`
+  - payload:
+  ```json
+  {
+    "trustedFolders": ["/a/path/to/trust"]
+  }
+  ```
+
 ## Installation
 
 ### Download
 
 The release workflow stores the generated executables, so that they can be
@@ -130,16 +139,41 @@ within `initializationOptions?: LSPAny;` we support the following settings:
   "path": "/usr/local/bin", // Adds to the system path used by the CLI
   "sendErrorReports":  "true", // Whether or not to report errors to Snyk - defaults to true
   "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:
 
 1. `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` to define the http proxy to be used
diff --git a/application/config/config.go b/application/config/config.go
index 38df763..c47f9ad 100644
--- a/application/config/config.go
+++ b/application/config/config.go
@@ -117,37 +117,39 @@ func (c *CliSettings) DefaultBinaryInstallPath() string {
 	}
 	return lsPath
 }
 
 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 {
 	mutex.Lock()
 	defer mutex.Unlock()
@@ -183,10 +185,11 @@ func New() *Config {
 	c.logPath = ""
 	c.snykApiUrl = defaultSnykApiUrl
 	c.snykCodeApiUrl = defaultDeeproxyApiUrl
 	c.snykCodeAnalysisTimeout = snykCodeAnalysisTimeoutFromEnv()
 	c.token = ""
+	c.trustedFoldersFeatureEnabled = true
 	c.clientSettingsFromEnv()
 	c.deviceId = c.determineDeviceId()
 	c.addDefaults()
 	return c
 }
@@ -209,10 +212,20 @@ func (c *Config) determineDeviceId() string {
 	} else {
 		return id
 	}
 }
 
+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 {
 		c.loadFile(fileName)
 	}
@@ -536,5 +549,17 @@ 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
+}
diff --git a/application/config/config_test.go b/application/config/config_test.go
index c6b2830..1be26f9 100644
--- a/application/config/config_test.go
+++ b/application/config/config_test.go
@@ -43,10 +43,11 @@ func TestConfigDefaults(t *testing.T) {
 	assert.False(t, c.IsSnykContainerEnabled(), "Snyk Container should be enabled by default")
 	assert.True(t, c.IsSnykOssEnabled(), "Snyk Open Source should be enabled by default")
 	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) {
 	// Arrange
 	c := New()
diff --git a/application/server/configuration.go b/application/server/configuration.go
index ae7b758..deade66 100644
--- a/application/server/configuration.go
+++ b/application/server/configuration.go
@@ -17,10 +17,11 @@
 package server
 
 import (
 	"context"
 	"os"
+	"reflect"
 	"strconv"
 	"strings"
 
 	"github.com/creachadair/jrpc2"
 	"github.com/creachadair/jrpc2/handler"
@@ -35,11 +36,11 @@ func WorkspaceDidChangeConfiguration(srv *jrpc2.Server) jrpc2.Handler {
 	return handler.New(func(ctx context.Context, params lsp.DidChangeConfigurationParams) (bool, error) {
 		log.Info().Str("method", "WorkspaceDidChangeConfiguration").Interface("params", params).Msg("RECEIVED")
 		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
 		}
 
@@ -63,11 +64,11 @@ func WorkspaceDidChangeConfiguration(srv *jrpc2.Server) jrpc2.Handler {
 		err = res.UnmarshalResult(&fetchedSettings)
 		if err != nil {
 			return false, err
 		}
 
-		if fetchedSettings[0] != emptySettings {
+		if !reflect.DeepEqual(fetchedSettings[0], emptySettings) {
 			UpdateSettings(ctx, fetchedSettings[0])
 			return true, nil
 		}
 
 		return false, nil
@@ -84,11 +85,11 @@ func UpdateSettings(ctx context.Context, settings lsp.Settings) {
 	writeSettings(ctx, settings, false)
 }
 
 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)
 	updateProductEnablement(settings)
 	updateCliConfig(settings)
@@ -96,10 +97,24 @@ func writeSettings(ctx context.Context, settings lsp.Settings, initialize bool)
 	updateEnvironment(settings)
 	updatePath(settings)
 	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) {
 	// Unless the field is included and set to false, auto-auth should be true by default.
 	autoAuth, err := strconv.ParseBool(settings.AutomaticAuthentication)
diff --git a/application/server/configuration_test.go b/application/server/configuration_test.go
index d31d3fe..6f3da85 100644
--- a/application/server/configuration_test.go
+++ b/application/server/configuration_test.go
@@ -69,11 +69,11 @@ func Test_WorkspaceDidChangeConfiguration_Push(t *testing.T) {
 	assert.True(t, strings.Contains(os.Getenv("PATH"), "addPath"))
 	assert.True(t, config.CurrentConfig().IsErrorReportingEnabled())
 	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
 	}
 	return nil, nil
@@ -149,10 +149,11 @@ func Test_UpdateSettings(t *testing.T) {
 			Organization:                "org",
 			EnableTelemetry:             "false",
 			ManageBinariesAutomatically: "false",
 			CliPath:                     "C:\\Users\\CliPath\\snyk-ls.exe",
 			Token:                       "a fancy token",
+			TrustedFolders:              []string{"trustedPath1", "trustedPath2"},
 		}
 
 		UpdateSettings(context.Background(), settings)
 
 		c := config.CurrentConfig()
@@ -169,10 +170,12 @@ func Test_UpdateSettings(t *testing.T) {
 		assert.Equal(t, "org", c.GetOrganization())
 		assert.False(t, c.IsTelemetryEnabled())
 		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) {
 		config.SetCurrentConfig(config.New())
 
@@ -207,10 +210,19 @@ func Test_UpdateSettings(t *testing.T) {
 		assert.Equal(t, "", c.GetOrganization())
 		assert.Empty(t, os.Getenv("a"))
 		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) {
 			UpdateSettings(context.Background(), lsp.Settings{
 				ManageBinariesAutomatically: "true",
diff --git a/application/server/execute_command.go b/application/server/execute_command.go
index a017a26..fa78baa 100644
--- a/application/server/execute_command.go
+++ b/application/server/execute_command.go
@@ -23,11 +23,13 @@ import (
 	"github.com/creachadair/jrpc2"
 	"github.com/creachadair/jrpc2/handler"
 	"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"
 	"github.com/snyk/snyk-ls/internal/notification"
 )
@@ -47,14 +49,22 @@ func ExecuteCommandHandler(srv *jrpc2.Server) jrpc2.Handler {
 			if len(args) < 2 {
 				log.Warn().Str("method", method).Msg("received NavigateToRangeCommand without range")
 			}
 			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())
 			if err != nil {
 				log.Err(err).Msg("Error on snyk.login command")
@@ -73,5 +83,21 @@ func ExecuteCommandHandler(srv *jrpc2.Server) jrpc2.Handler {
 			di.Authenticator().Logout(bgCtx)
 		}
 		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
+}
diff --git a/application/server/execute_command_test.go b/application/server/execute_command_test.go
index 28c4a4e..7afb5ec 100644
--- a/application/server/execute_command_test.go
+++ b/application/server/execute_command_test.go
@@ -23,10 +23,11 @@ import (
 	"github.com/sourcegraph/go-lsp"
 	"github.com/stretchr/testify/assert"
 
 	"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"
 	"github.com/snyk/snyk-ls/infrastructure/cli/auth"
 )
@@ -45,10 +46,28 @@ func Test_executeWorkspaceScanCommand_shouldStartWorkspaceScanOnCommandReceipt(t
 	assert.Eventually(t, func() bool {
 		return scanner.Calls() > 0
 	}, 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)
 	_, err := loc.Client.Call(ctx, "initialize", nil)
 	if err != nil {
@@ -82,5 +101,52 @@ func Test_executeCommand_shouldCopyAuthURLToClipboard(t *testing.T) {
 	}
 	actualURL, _ := clipboard.ReadAll()
 
 	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")
+	})
+}
diff --git a/application/server/lsp/message_types.go b/application/server/lsp/message_types.go
index ccd3308..0bffff3 100644
--- a/application/server/lsp/message_types.go
+++ b/application/server/lsp/message_types.go
@@ -231,28 +231,30 @@ type WorkspaceFoldersChangeEvent struct {
 	Removed []WorkspaceFolder `json:"Removed,omitempty"`
 }
 
 // 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 {
 	// The actual changed settings
 	Settings Settings `json:"settings"`
@@ -605,5 +607,26 @@ type ShowDocumentParams struct {
 	 * external program is started or the file is not a text
 	 * file.
 	 */
 	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"`
+}
diff --git a/application/server/server.go b/application/server/server.go
index c46f1f8..31012c6 100644
--- a/application/server/server.go
+++ b/application/server/server.go
@@ -88,11 +88,11 @@ func initHandlers(srv *jrpc2.Server, handlers *handler.Map) {
 	(*handlers)["textDocument/codeLens"] = CodeLensHandler()
 	(*handlers)["textDocument/willSave"] = NoOpHandler()
 	(*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)
 }
 
@@ -143,19 +143,20 @@ func CodeActionHandler() jrpc2.Handler {
 		actions := codeaction.GetFor(uri.PathFromUri(params.TextDocument.URI), params.Range)
 		return actions, nil
 	})
 }
 
-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
 		bgCtx := context.Background()
 
 		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
 	})
 }
 
 func InitializeHandler(srv *jrpc2.Server) handler.Func {
@@ -181,28 +182,11 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
 			monitorClientProcess(params.ProcessID)
 			log.Info().Msgf("Shutting down as client pid %d not running anymore.", params.ProcessID)
 			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{
 				Name:    "snyk-ls",
 				Version: config.LsProtocolVersion,
@@ -230,23 +214,51 @@ func InitializeHandler(srv *jrpc2.Server) handler.Func {
 						snyk.WorkspaceScanCommand,
 						snyk.OpenBrowserCommand,
 						snyk.LoginCommand,
 						snyk.CopyAuthLinkCommand,
 						snyk.LogoutCommand,
+						snyk.TrustWorkspaceFoldersCommand,
 					},
 				},
 			},
 		}, nil
 	})
 }
 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 != "" {
 		integrationName = initParams.InitializationOptions.IntegrationName
 		integrationVersion = initParams.InitializationOptions.IntegrationVersion
@@ -398,10 +410,16 @@ func registerNotifier(srv *jrpc2.Server) {
 				Str("method", "registerNotifier").
 				Interface("documentURI", params.URI).
 				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").
 				Interface("params", params).
 				Msg("received unconfigured notification object")
diff --git a/application/server/server_test.go b/application/server/server_test.go
index e762121..a454763 100644
--- a/application/server/server_test.go
+++ b/application/server/server_test.go
@@ -228,11 +228,10 @@ func Test_initialize_shouldSupportCodeLenses(t *testing.T) {
 	assert.Equal(t, result.Capabilities.CodeLensProvider.ResolveProvider, false)
 }
 
 func Test_TextDocumentCodeLenses_shouldReturnCodeLenses(t *testing.T) {
 	loc := setupServer(t)
-
 	didOpenParams, dir := didOpenTextParams(t)
 
 	clientParams := lsp.InitializeParams{
 		RootURI: uri.PathToUri(dir),
 		InitializationOptions: lsp.Settings{
@@ -241,10 +240,11 @@ func Test_TextDocumentCodeLenses_shouldReturnCodeLenses(t *testing.T) {
 			ActivateSnykIac:             "false",
 			Organization:                "fancy org",
 			Token:                       "xxx",
 			ManageBinariesAutomatically: "true",
 			CliPath:                     "",
+			EnableTrustedFoldersFeature: "false",
 		},
 	}
 	_, err := loc.Client.Call(ctx, "initialize", clientParams)
 	if err != nil {
 		t.Fatal(err, "couldn't initialize")
@@ -466,10 +466,76 @@ func Test_initialize_autoAuthenticateSetCorrectly(t *testing.T) {
 		assert.Nil(t, err)
 		assert.False(t, config.CurrentConfig().AutomaticAuthentication())
 	})
 }
 
+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)
 
 	clientParams := lsp.InitializeParams{
@@ -478,19 +544,25 @@ func Test_textDocumentDidOpenHandler_shouldAcceptDocumentItemAndPublishDiagnosti
 			ActivateSnykCode:            "true",
 			ActivateSnykOpenSource:      "false",
 			ActivateSnykIac:             "false",
 			Organization:                "fancy org",
 			Token:                       "xxx",
-			ManageBinariesAutomatically: "true",
+			ManageBinariesAutomatically: "false",
 			CliPath:                     "",
+			EnableTrustedFoldersFeature: "false",
 		},
 	}
 	_, err := loc.Client.Call(ctx, "initialize", clientParams)
 	if err != nil {
 		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)
 	}
 
@@ -686,12 +758,13 @@ 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",
 		},
 	}
 
 	_, err = loc.Client.Call(ctx, "initialize", clientParams)
 	if err != nil {
diff --git a/application/server/trust.go b/application/server/trust.go
new file mode 100644
index 0000000..f417227
--- /dev/null
+++ b/application/server/trust.go
@@ -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)
+}
diff --git a/application/server/trust_test.go b/application/server/trust_test.go
new file mode 100644
index 0000000..db3654f
--- /dev/null
+++ b/application/server/trust_test.go
@@ -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)
+}
diff --git a/domain/ide/workspace/folder.go b/domain/ide/workspace/folder.go
index 2fc862e..4593f85 100644
--- a/domain/ide/workspace/folder.go
+++ b/domain/ide/workspace/folder.go
@@ -16,14 +16,16 @@
 
 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"
 	"github.com/snyk/snyk-ls/domain/snyk"
 	"github.com/snyk/snyk-ls/internal/concurrency"
@@ -62,10 +64,11 @@ func NewFolder(path string, name string, scanner snyk.Scanner, hoverService hove
 	}
 	folder.productAttributes[snyk.ProductCode] = snyk.ProductAttributes{}
 	folder.productAttributes[snyk.ProductInfrastructureAsCode] = snyk.ProductAttributes{}
 	folder.productAttributes[snyk.ProductOpenSource] = snyk.ProductAttributes{}
 	folder.documentDiagnosticCache = concurrency.AtomicMap{}
+
 	return &folder
 }
 
 func (f *Folder) IsScanned() bool {
 	f.mutex.Lock()
@@ -113,13 +116,18 @@ func (f *Folder) ClearDiagnosticsCache(filePath string) {
 	f.documentDiagnosticCache.Delete(filePath)
 	f.ClearScannedStatus()
 }
 
 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
 	}
 
 	f.scanner.Scan(ctx, path, f.processResults, f.path)
@@ -226,5 +234,18 @@ func (f *Folder) ClearDiagnostics() {
 		return true
 	})
 
 	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
+}
diff --git a/domain/ide/workspace/folder_test.go b/domain/ide/workspace/folder_test.go
index 8bb2284..22e53c8 100644
--- a/domain/ide/workspace/folder_test.go
+++ b/domain/ide/workspace/folder_test.go
@@ -22,10 +22,11 @@ import (
 	"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/ide/hover"
 	"github.com/snyk/snyk-ls/domain/snyk"
 	"github.com/snyk/snyk-ls/infrastructure/code"
 	"github.com/snyk/snyk-ls/internal/notification"
@@ -69,11 +70,11 @@ func Test_Scan_WhenCachedResultsButNoIssues_shouldNotReScan(t *testing.T) {
 	f.ScanFile(ctx, filePath)
 
 	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()
 	f := NewFolder("dummy", "dummy", snyk.NewTestScanner(), hoverService)
 
@@ -84,11 +85,11 @@ func TestProcessResults_SendsDiagnosticsAndHovers(t *testing.T) {
 	f.processResults(issues)
 	// todo ideally there's a hover & diagnostic service that are symmetric and don't leak implementation details (e.g. channels)
 	// 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())
 
 	f.processResults([]snyk.Issue{
 		{ID: "id1", AffectedFilePath: "path1"},
@@ -100,11 +101,11 @@ func TestProcessResults_whenDifferentPaths_AddsToCache(t *testing.T) {
 	assert.NotNil(t, f.documentDiagnosticCache.Get("path2"))
 	assert.Len(t, f.documentDiagnosticCache.Get("path1"), 1)
 	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())
 
 	f.processResults([]snyk.Issue{
 		{ID: "id1", AffectedFilePath: "path1"},
@@ -114,11 +115,11 @@ func TestProcessResults_whenSamePaths_AddsToCache(t *testing.T) {
 	assert.Equal(t, 1, f.documentDiagnosticCache.Length())
 	assert.NotNil(t, f.documentDiagnosticCache.Get("path1"))
 	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())
 
 	f.processResults([]snyk.Issue{
 		{ID: "id1", AffectedFilePath: "path1"},
@@ -130,11 +131,11 @@ func TestProcessResults_whenDifferentPaths_AccumulatesIssues(t *testing.T) {
 	assert.NotNil(t, f.documentDiagnosticCache.Get("path1"))
 	assert.NotNil(t, f.documentDiagnosticCache.Get("path2"))
 	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())
 
 	f.processResults([]snyk.Issue{
 		{ID: "id1", AffectedFilePath: "path1"},
@@ -145,11 +146,11 @@ func TestProcessResults_whenSamePaths_AccumulatesIssues(t *testing.T) {
 	assert.Equal(t, 1, f.documentDiagnosticCache.Length())
 	assert.NotNil(t, f.documentDiagnosticCache.Get("path1"))
 	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())
 
 	f.processResults([]snyk.Issue{
 		{ID: "id1", AffectedFilePath: "path1"},
@@ -198,5 +199,52 @@ func Test_ClearDiagnostics(t *testing.T) {
 		},
 		1*time.Second,
 		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())
+}
diff --git a/domain/ide/workspace/workspace.go b/domain/ide/workspace/workspace.go
index 463d4e3..2221564 100644
--- a/domain/ide/workspace/workspace.go
+++ b/domain/ide/workspace/workspace.go
@@ -18,28 +18,34 @@ package workspace
 
 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"
 )
 
 // todo can we do without a singleton?
 var instance *Workspace
 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 {
 	return &Workspace{
 		folders:      make(map[string]*Folder, 0),
@@ -60,11 +66,11 @@ func Set(w *Workspace) {
 	mutex.Lock()
 	defer mutex.Unlock()
 	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)
 }
 
@@ -85,30 +91,57 @@ func (w *Workspace) GetFolderContaining(path string) (folder *Folder) {
 	}
 	return nil
 }
 
 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 {
 		f := NewFolder(uri.PathFromUri(folder.Uri), folder.Name, w.scanner, w.hoverService)
 		w.AddFolder(f)
 	}
 	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()
 	}
 
 	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
+}
diff --git a/domain/ide/workspace/workspace_test.go b/domain/ide/workspace/workspace_test.go
new file mode 100644
index 0000000..92843b8
--- /dev/null
+++ b/domain/ide/workspace/workspace_test.go
@@ -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)
+}
diff --git a/domain/ide/workspace/workspace_trust.go b/domain/ide/workspace/workspace_trust.go
new file mode 100644
index 0000000..af3f2bf
--- /dev/null
+++ b/domain/ide/workspace/workspace_trust.go
@@ -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
+}
diff --git a/domain/ide/workspace/workspace_trust_test.go b/domain/ide/workspace/workspace_trust_test.go
new file mode 100644
index 0000000..c21534f
--- /dev/null
+++ b/domain/ide/workspace/workspace_trust_test.go
@@ -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())
+}
diff --git a/domain/snyk/command.go b/domain/snyk/command.go
index 6f42245..145945a 100644
--- a/domain/snyk/command.go
+++ b/domain/snyk/command.go
@@ -15,16 +15,17 @@
  */
 
 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 {
 	/**
 	 * Title of the command, like `save`.
diff --git a/internal/testutil/test_setup.go b/internal/testutil/test_setup.go
index 88527f7..f0be289 100644
--- a/internal/testutil/test_setup.go
+++ b/internal/testutil/test_setup.go
@@ -43,10 +43,11 @@ func SmokeTest(t *testing.T) {
 func UnitTest(t *testing.T) {
 	t.Helper()
 	c := config.New()
 	c.SetManageBinariesAutomatically(false)
 	c.SetToken("00000000-0000-0000-0000-000000000001")
+	c.SetTrustedFolderFeatureEnabled(false)
 	config.SetCurrentConfig(c)
 	CLIDownloadLockFileCleanUp(t)
 }
 
 func CLIDownloadLockFileCleanUp(t *testing.T) {
@@ -121,10 +122,11 @@ func prepareTestHelper(t *testing.T, envVar string) {
 
 	c := config.New()
 	c.SetToken(GetEnvironmentToken())
 	c.SetErrorReportingEnabled(false)
 	c.SetTelemetryEnabled(false)
+	c.SetTrustedFolderFeatureEnabled(false)
 	config.SetCurrentConfig(c)
 
 	CLIDownloadLockFileCleanUp(t)
 }
 

Vulnerable code:   - LS_PROTOCOL_VERSION=3
	@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b .bin/ v1.48.0
  "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)
	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
		if params.Settings != emptySettings {
		if fetchedSettings[0] != emptySettings {
	if settings == emptySettings {
func callBackMock(ctx context.Context, request *jrpc2.Request) (interface{}, error) {
			workspace.Get().ClearIssues(bgCtx)
			workspace.Get().ScanWorkspace(bgCtx)
	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"`
	(*handlers)["workspace/didChangeWorkspaceFolders"] = WorkspaceDidChangeWorkspaceFoldersHandler()
func WorkspaceDidChangeWorkspaceFoldersHandler() jrpc2.Handler {
		workspace.Get().ProcessFolderChange(bgCtx, params)
		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()))
			}
		}

			ManageBinariesAutomatically: "true",
			Endpoint: os.Getenv("SNYK_API"),
			Token:    os.Getenv("SNYK_TOKEN"),
		log.Info().Str("method", "domain.ide.workspace.folder.scan").Msgf("Cached results found: Skipping scan for %s", path)
func TestProcessResults_SendsDiagnosticsAndHovers(t *testing.T) {
func TestProcessResults_whenDifferentPaths_AddsToCache(t *testing.T) {
func TestProcessResults_whenSamePaths_AddsToCache(t *testing.T) {
func TestProcessResults_whenDifferentPaths_AccumulatesIssues(t *testing.T) {
func TestProcessResults_whenSamePaths_AccumulatesIssues(t *testing.T) {
func TestProcessResults_whenSamePathsAndDuplicateIssues_DeDuplicates(t *testing.T) {
	mutex        sync.Mutex
	folders      map[string]*Folder
	instrumentor performance.Instrumentor
	scanner      snyk.Scanner
	hoverService hover.Service
func (w *Workspace) DeleteFolder(folder string) {
	for _, folder := range w.folders {
func (w *Workspace) ProcessFolderChange(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) {
		w.DeleteFolder(uri.PathFromUri(folder.Uri))
func (w *Workspace) ClearIssues(ctx context.Context) {
	NavigateToRangeCommand = "snyk.navigateToRange"
	WorkspaceScanCommand   = "snyk.workspace.scan"
	OpenBrowserCommand     = "snyk.openBrowser"
	LoginCommand           = "snyk.login"
	CopyAuthLinkCommand    = "snyk.copyAuthLink"
	LogoutCommand          = "snyk.logout"
Fixed code:   - LS_PROTOCOL_VERSION=4
	@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b .bin/ v1.50.0
- Trust Notification
  - method: `$/snyk.addTrustedFolders`
  - payload:
  ```json
  {
    "trustedFolders": ["/a/path/to/trust"]
  }
  ```

  "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.

	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
	c.trustedFoldersFeatureEnabled = true
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) 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
}
	assert.Empty(t, c.trustedFolders)
	"reflect"
		if !reflect.DeepEqual(params.Settings, emptySettings) {
		if !reflect.DeepEqual(fetchedSettings[0], emptySettings) {
	if reflect.DeepEqual(settings, emptySettings) {
	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 callBackMock(_ context.Context, request *jrpc2.Request) (interface{}, error) {
			TrustedFolders:              []string{"trustedPath1", "trustedPath2"},
		assert.Contains(t, c.TrustedFolders(), "trustedPath1")
		assert.Contains(t, c.TrustedFolders(), "trustedPath2")
	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")
	})
	"github.com/snyk/snyk-ls/application/config"
	"github.com/snyk/snyk-ls/application/server/lsp"
			w := workspace.Get()
			w.ClearIssues(bgCtx)
			w.ScanWorkspace(bgCtx)
			handleUntrustedFolders(bgCtx, srv)
		case snyk.TrustWorkspaceFoldersCommand:
			err := TrustWorkspaceFolders()
			if err != nil {
				log.Err(err).Msgf("Error on %s command", snyk.TrustWorkspaceFoldersCommand)
				notification.SendError(err)
			}

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
}
	"github.com/snyk/snyk-ls/application/config"
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_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")
	})
}
	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 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"`
}
	(*handlers)["workspace/didChangeWorkspaceFolders"] = WorkspaceDidChangeWorkspaceFoldersHandler(srv)
func WorkspaceDidChangeWorkspaceFoldersHandler(srv *jrpc2.Server) jrpc2.Handler {
		workspace.Get().AddAndRemoveFoldersAndTriggerScan(bgCtx, params)
		handleUntrustedFolders(bgCtx, srv)
		addWorkspaceFolders(params, w)
						snyk.TrustWorkspaceFoldersCommand,
		if config.CurrentConfig().AutomaticAuthentication() || config.CurrentConfig().NonEmptyToken() {
			go handleUntrustedFolders(context.Background(), srv)
		}
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)
		}
	}
}

		case lsp.SnykTrustedFoldersParams:
			notifier(srv, "$/snyk.addTrustedFolders", params)
			log.Info().
				Str("method", "registerNotifier").
				Interface("trustedPaths", params.TrustedFolders).
				Msg("sending trusted Folders to client")
			EnableTrustedFoldersFeature: "false",
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)
}

			ManageBinariesAutomatically: "false",
			EnableTrustedFoldersFeature: "false",
	_, err = loc.Client.Call(ctx, "initialized", nil)
	if err != nil {
		t.Fatal(err, "couldn't send initialized")
	}

			Endpoint:                    os.Getenv("SNYK_API"),
			Token:                       os.Getenv("SNYK_TOKEN"),
			EnableTrustedFoldersFeature: "false",
/*
 * 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)
}
/*
 * 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)
}
	"strings"
	"github.com/snyk/snyk-ls/application/config"

	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
	}
		log.Info().Str("method", method).Msgf("Cached results found: Skipping scan for %s", path)

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
}
	"github.com/snyk/snyk-ls/application/config"
func Test_ProcessResults_SendsDiagnosticsAndHovers(t *testing.T) {
func Test_ProcessResults_whenDifferentPaths_AddsToCache(t *testing.T) {
func Test_ProcessResults_whenSamePaths_AddsToCache(t *testing.T) {
func Test_ProcessResults_whenDifferentPaths_AccumulatesIssues(t *testing.T) {
func Test_ProcessResults_whenSamePaths_AccumulatesIssues(t *testing.T) {
func Test_ProcessResults_whenSamePathsAndDuplicateIssues_DeDuplicates(t *testing.T) {

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())
}
	"github.com/rs/zerolog/log"

	"github.com/snyk/snyk-ls/application/config"
	"github.com/snyk/snyk-ls/internal/notification"
	mutex               sync.Mutex
	folders             map[string]*Folder
	instrumentor        performance.Instrumentor
	scanner             snyk.Scanner
	hoverService        hover.Service
	trustMutex          sync.Mutex
	trustRequestOngoing bool // for debouncing
func (w *Workspace) RemoveFolder(folder string) {
	trusted, _ := w.GetFolderTrust()

	for _, folder := range trusted {
func (w *Workspace) AddAndRemoveFoldersAndTriggerScan(ctx context.Context, params lsp.DidChangeWorkspaceFoldersParams) {
		w.RemoveFolder(uri.PathFromUri(folder.Uri))
func (w *Workspace) ClearIssues(_ context.Context) {

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
}
/*
 * 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)
}
/*
 * 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
}
/*
 * 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())
}
	NavigateToRangeCommand       = "snyk.navigateToRange"
	WorkspaceScanCommand         = "snyk.workspace.scan"
	OpenBrowserCommand           = "snyk.openBrowser"
	LoginCommand                 = "snyk.login"
	CopyAuthLinkCommand          = "snyk.copyAuthLink"
	LogoutCommand                = "snyk.logout"
	TrustWorkspaceFoldersCommand = "snyk.trustWorkspaceFolders"
	c.SetTrustedFolderFeatureEnabled(false)
	c.SetTrustedFolderFeatureEnabled(false)