CVE-2022-24441 (TypeScript, 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/vscode-extension
Files changed:
CHANGELOG.md
package.json
src/snyk/base/modules/snykLib.ts
src/snyk/cli/services/cliService.ts
src/snyk/common/commands/commandController.ts
src/snyk/common/configuration/configuration.ts
src/snyk/common/configuration/trustedFolders.ts
src/snyk/common/constants/languageServer.ts
src/snyk/common/constants/settings.ts
src/snyk/common/languageServer/languageServer.ts
src/snyk/common/languageServer/settings.ts
src/snyk/common/messages/analysisMessages.ts
src/snyk/common/views/analysisTreeNodeProvider.ts
src/snyk/common/watchers/configurationWatcher.ts
src/snyk/snykCode/codeService.ts
src/snyk/snykCode/views/issueTreeProvider.ts
src/snyk/snykOss/services/ossService.ts
src/snyk/snykOss/views/ossVulnerabilityTreeProvider.ts
src/test/unit/cli/services/cliService.test.ts
src/test/unit/common/configuration/trustedFolders.test.ts
src/test/unit/common/languageServer/languageServer.test.ts
src/test/unit/common/languageServer/middleware.test.ts
src/test/unit/snykOss/services/ossService.test.ts
Commit message: feat: workspace trust (#306)



Vulnerable code:
## [1.7.8]
        "contents": "Welcome to Snyk for Visual Studio Code. 👋\nLet's start by connecting VS Code with Snyk:\n[Connect VS Code with Snyk](command:snyk.initiateLogin 'Connect with Snyk')\n👉 Snyk's mission is to finds bugs, fast. Connect with Snyk to start your first analysis!\nBy connecting your account with Snyk, you agree to the Snyk [Privacy Policy](https://snyk.io/policies/privacy), and the Snyk [Terms of Service](https://snyk.io/policies/terms-of-service).",
    // wait until Snyk CLI is downloaded
      if (result instanceof CliError) {
  async test(manualTrigger: boolean, reportTriggeredEvent: boolean): Promise<CliResult | CliError> {
    const foldersToTest = this.workspace.getWorkspaceFolders();
    if (foldersToTest.length == 0) {
      throw new Error('No workspace was opened.');
    }

import { SNYK_LOGIN_COMMAND } from '../constants/languageServer';
import { SNYK_CLI_PATH, SNYK_HAS_AUTHENTICATED, SNYK_LANGUAGE_SERVER_NAME } from '../constants/languageServer';

        this.client.onNotification(SNYK_HAS_AUTHENTICATED, ({ token }: { token: string }) => {
          this.authenticationService.updateToken(token).catch((error: Error) => {
            ErrorHandler.handle(error, this.logger, error.message);
          });
        });

        this.client.onNotification(SNYK_CLI_PATH, ({ cliPath }: { cliPath: string }) => {
          if (!cliPath) {
            ErrorHandler.handle(
              new Error("CLI path wasn't provided by language server on $/snyk.isAvailableCli notification " + cliPath),
              this.logger,
              "CLI path wasn't provided by language server on notification",
            );
            return;
          }

          const currentCliPath = this.configuration.getCliPath();
          if (currentCliPath != cliPath) {
            this.logger.info('Setting Snyk CLI path to: ' + cliPath);
            void this.configuration
              .setCliPath(cliPath)
              .then(() => {
                this.cliReady$.next(cliPath);
              })
              .catch((error: Error) => {
                ErrorHandler.handle(error, this.logger, error.message);
              });
          }
        });
        getWorkspaceFolders: () => ['test-folder'],
      } as IConfiguration,
        getWorkspaceFolders: () => [''],

Fixed code:
## [1.9.0]

### Added

- Added workspace trust feature.

## [1.8.0]
          "snyk.trustedFolders": {
            "type": "array",
            "default": [],
            "description": "Folders to trust for Snyk scans."
          },
        "contents": "Welcome to Snyk for Visual Studio Code. 👋\n👉 Connect with Snyk to start your first analysis!\nWhen scanning folder files, Snyk may automatically execute code such as invoking the package manager to get dependency information. You should only scan projects you trust. [More info](https://docs.snyk.io/ide-tools/visual-studio-code-extension/workspace-trust)\n[Trust workspace and connect](command:snyk.initiateLogin 'Connect with Snyk')\nBy connecting your account with Snyk, you agree to the Snyk [Privacy Policy](https://snyk.io/policies/privacy), and the Snyk [Terms of Service](https://snyk.io/policies/terms-of-service).",
    // wait until Snyk Language Server is downloaded
      if (result instanceof CliError || !result) {
import { getTrustedFolders } from '../../common/configuration/trustedFolders';
import { messages as analysisMessages } from '../../common/messages/analysisMessages';
  private _isAnyWorkspaceFolderTrusted = true;
  get isAnyWorkspaceFolderTrusted(): boolean {
    return this._isAnyWorkspaceFolderTrusted;
  }

  async test(manualTrigger: boolean, reportTriggeredEvent: boolean): Promise<CliResult | CliError | void> {
    let foldersToTest = this.workspace.getWorkspaceFolders();
    if (foldersToTest.length == 0) {
      throw new Error('No workspace was opened.');
    }

    foldersToTest = getTrustedFolders(this.config, foldersToTest);
    if (foldersToTest.length == 0) {
      this.handleNoTrustedFolders();
      this.logger.info(`Skipping Open Source scan. ${analysisMessages.noWorkspaceTrustDescription}`);
      return;
    }
    this._isAnyWorkspaceFolderTrusted = true;

  handleNoTrustedFolders() {
    this._isAnyWorkspaceFolderTrusted = false;
  }

import { SNYK_LOGIN_COMMAND, SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND } from '../constants/languageServer';
    await this.commands.executeCommand(SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND);
  TRUSTED_FOLDERS,

  getTrustedFolders(): string[];

  setTrustedFolders(trustedFolders: string[]): Promise<void>;
  getTrustedFolders(): string[] {
    return (
      this.workspace.getConfiguration<string[]>(CONFIGURATION_IDENTIFIER, this.getConfigName(TRUSTED_FOLDERS)) || []
    );
  }

  async setTrustedFolders(trustedFolders: string[]): Promise<void> {
    await this.workspace.updateConfiguration(
      CONFIGURATION_IDENTIFIER,
      this.getConfigName(TRUSTED_FOLDERS),
      trustedFolders,
      true,
    );
  }
import { IConfiguration } from './configuration';

export function getTrustedFolders(config: IConfiguration, workspaceFolders: string[]): string[] {
  const trustedFolders = config.getTrustedFolders();

  return workspaceFolders.filter(folder => trustedFolders.includes(folder));
}
export const SNYK_ADD_TRUSTED_FOLDERS = '$/snyk.addTrustedFolders';
export const SNYK_TRUST_WORKSPACE_FOLDERS_COMMAND = 'snyk.trustWorkspaceFolders';
export const TRUSTED_FOLDERS = `${CONFIGURATION_IDENTIFIER}.trustedFolders`;
import {
  SNYK_ADD_TRUSTED_FOLDERS,
  SNYK_CLI_PATH,
  SNYK_HAS_AUTHENTICATED,
  SNYK_LANGUAGE_SERVER_NAME,
} from '../constants/languageServer';
        this.registerListeners(this.client);
  private registerListeners(client: LanguageClient): void {
    client.onNotification(SNYK_HAS_AUTHENTICATED, ({ token }: { token: string }) => {
      this.authenticationService.updateToken(token).catch((error: Error) => {
        ErrorHandler.handle(error, this.logger, error.message);
      });
    });

    client.onNotification(SNYK_CLI_PATH, ({ cliPath }: { cliPath: string }) => {
      if (!cliPath) {
        ErrorHandler.handle(
          new Error("CLI path wasn't provided by language server on $/snyk.isAvailableCli notification " + cliPath),
          this.logger,
          "CLI path wasn't provided by language server on notification",
        );
        return;
      }

      const currentCliPath = this.configuration.getCliPath();
      if (currentCliPath != cliPath) {
        this.logger.info('Setting Snyk CLI path to: ' + cliPath);
        void this.configuration
          .setCliPath(cliPath)
          .then(() => {
            this.cliReady$.next(cliPath);
          })
          .catch((error: Error) => {
            ErrorHandler.handle(error, this.logger, error.message);
          });
      }
    });

    client.onNotification(SNYK_ADD_TRUSTED_FOLDERS, ({ trustedFolders }: { trustedFolders: string[] }) => {
      this.configuration.setTrustedFolders(trustedFolders).catch((error: Error) => {
        ErrorHandler.handle(error, this.logger, error.message);
      });
    });
  }

  enableTrustedFoldersFeature?: string;
  trustedFolders?: string[];
      enableTrustedFoldersFeature: 'true',
      trustedFolders: configuration.getTrustedFolders(),
  noWorkspaceTrust: 'No workspace folder was granted trust',
  noWorkspaceTrustDescription:
    'None of workspace folders were trusted. If you trust the workspace, you can add it to the list of trusted folders in the extension settings, or when prompted by the extension next time.',
  protected getNoWorkspaceTrustTreeNode(): TreeNode {
    return new TreeNode({
      text: messages.noWorkspaceTrust,
      command: {
        command: SNYK_SHOW_OUTPUT_COMMAND,
        title: '',
      },
    });
  }

  TRUSTED_FOLDERS,
        TRUSTED_FOLDERS,
import { getTrustedFolders } from '../common/configuration/trustedFolders';
import { messages as generalAnalysisMessages } from '../common/messages/analysisMessages';
  isAnyWorkspaceFolderTrusted: boolean;
  private _isAnyWorkspaceFolderTrusted = true;
  get isAnyWorkspaceFolderTrusted(): boolean {
    return this._isAnyWorkspaceFolderTrusted;
  }
    paths = getTrustedFolders(this.config, paths);
    if (!paths.length) {
      this._isAnyWorkspaceFolderTrusted = false;
      this.viewManagerService.refreshCodeAnalysisViews(enabledFeatures);
      this.logger.info(`Skipping Code scan. ${generalAnalysisMessages.noWorkspaceTrustDescription}`);
      return;
    }
    this._isAnyWorkspaceFolderTrusted = true;

    } else if (!this.snykCode.isAnyWorkspaceFolderTrusted) {
      return [this.getNoWorkspaceTrustTreeNode()];
  override handleNoTrustedFolders(): void {
    super.handleNoTrustedFolders();
    this.viewManagerService.refreshOssView();
  }

    } else if (!this.ossService.isAnyWorkspaceFolderTrusted) {
      return [this.getNoWorkspaceTrustTreeNode()];
    const testFolderPath = 'test-folder';
      getTrustedFolders: () => [testFolderPath],
        getWorkspaceFolders: () => [testFolderPath],
import { deepStrictEqual } from 'assert';
import { IConfiguration } from '../../../../snyk/common/configuration/configuration';
import { getTrustedFolders } from '../../../../snyk/common/configuration/trustedFolders';

suite('Trusted Folders', () => {
  test('Single folder trusted', () => {
    const config = {
      getTrustedFolders: () => ['/test/workspace', '/test/workspace2'],
    } as IConfiguration;
    const workspaceFolders = ['/test/workspace'];

    const trustedFolders = getTrustedFolders(config, workspaceFolders);

    deepStrictEqual(trustedFolders, ['/test/workspace']);
  });

  test('Multiple folders trusted', () => {
    const config = {
      getTrustedFolders: () => ['/test/workspace', '/test/workspace2'],
    } as IConfiguration;
    const workspaceFolders = ['/test/workspace', '/test/workspace2'];

    const trustedFolders = getTrustedFolders(config, workspaceFolders);

    deepStrictEqual(trustedFolders, ['/test/workspace', '/test/workspace2']);
  });

  test('No folders trusted', () => {
    const config = {
      getTrustedFolders: (): string[] => [],
    } as IConfiguration;
    const workspaceFolders = ['/test/workspace', '/test/workspace2'];

    const trustedFolders = getTrustedFolders(config, workspaceFolders);

    deepStrictEqual(trustedFolders, []);
  });
});
      getTrustedFolders(): string[] {
        return ['/trusted/test/folder'];
      },
      enableTrustedFoldersFeature: 'true',
      trustedFolders: ['/trusted/test/folder'],
      getTrustedFolders: () => ['/trusted/test/folder'],
    assert.strictEqual(serverResult.enableTrustedFoldersFeature, 'true');
    assert.deepStrictEqual(serverResult.trustedFolders, configuration.getTrustedFolders());
    const testFolderPath = '';
        getTrustedFolders: () => [testFolderPath],
      } as unknown as IConfiguration,
        getWorkspaceFolders: () => [testFolderPath],