CVE-2022-24441 (Go, published 2022-11-30T13:15Z)
Description: 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
Severity: nan (CVSS v3 base score: 8.8)
Weakness: CWE-78 \u2014 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
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.
Repository: https://github.com/snyk/snyk-ls
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 message: feat: add trust management mechanism (#187)



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)