CVE-2022-22984: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: The package snyk before 1.1064.0; the package snyk-mvn-plugin before 2.31.3; the package snyk-gradle-plugin before 3.24.5; the package @snyk/snyk-cocoapods-plugin before 2.5.3; the package snyk-sbt-plugin before 2.16.2; the package snyk-python-plugin before 1.24.2; the package snyk-docker-plugin before 5.6.5; the package @snyk/snyk-hex-plugin before 1.1.6 are vulnerable to Command Injection due to an incomplete fix for [CVE-2022-40764](https://security.snyk.io/vuln/SNYK-JS-SNYK-3037342). A successful exploit allows attackers to run arbitrary commands on the host system where the Snyk CLI is installed by passing in crafted command line flags. In order to exploit this vulnerability, a user would have to execute the snyk test command on untrusted files. In most cases, an attacker positioned to control the command line arguments to the Snyk CLI would already be positioned to execute arbitrary commands. However, this could be abused in specific scenarios, such as continuous integration pipelines, where developers can control the arguments passed to the Snyk CLI to leverage this component as part of a wider attack against an integration/build pipeline. This issue has been addressed in the latest Snyk Docker images available at https://hub.docker.com/r/snyk/snyk as of 2022-11-29. Images downloaded and built prior to that date should be updated. The issue has also been addressed in the Snyk TeamCity CI/CD plugin as of version v20221130.093605.
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: 6.3
Published: 2022-11-30T13:15Z
Repository: https://github.com/snyk/snyk-sbt-plugin
Language: TypeScript
Files changed: .snyk, lib/index.ts, lib/sub-process.ts, package.json, test/functional/sbt-plugin.test.ts
Commit date: 2022-11-29 08:03:12 +0000
Commit message: fix: escape child process arguments

Diff: diff --git a/.snyk b/.snyk
new file mode 100644
index 0000000..8c7c8ba
--- /dev/null
+++ b/.snyk
@@ -0,0 +1,10 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.19.0
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
+ignore:
+  'snyk:lic:npm:shescape:MPL-2.0':
+    - '*':
+        reason: None Given
+        expires: 2122-12-29T08:01:32.423Z
+        created: 2022-11-29T08:01:32.424Z
+patch: {}
diff --git a/lib/index.ts b/lib/index.ts
index 84e628b..86a0108 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -263,11 +263,11 @@ export function buildArgs(
   sbtArgs,
   isCoursierProject?: boolean,
   isOutputGraph?: boolean,
 ) {
   // force plain output so we don't have to parse colour codes
-  let args = ['"-Dsbt.log.noformat=true"'];
+  let args = ['-Dsbt.log.noformat=true'];
   if (sbtArgs) {
     args = args.concat(sbtArgs);
   }
 
   if (isOutputGraph) {
diff --git a/lib/sub-process.ts b/lib/sub-process.ts
index 004d126..5650910 100644
--- a/lib/sub-process.ts
+++ b/lib/sub-process.ts
@@ -1,8 +1,9 @@
 import * as childProcess from 'child_process';
 import * as treeKill from 'tree-kill';
 import * as debugModule from 'debug';
+import { quoteAll } from 'shescape';
 
 // To enable debugging output, run the CLI as `DEBUG=snyk-sbt-plugin snyk ...`
 const debugLogging = debugModule('snyk-sbt-plugin');
 
 // Disabled by default, to set run the CLI as `PROC_TIMEOUT=100000 snyk ...`
@@ -16,10 +17,11 @@ export const execute = (
 ): Promise<string>  => {
   const spawnOptions: {cwd?: string, shell: boolean} = {shell: true};
   if (options && options.cwd) {
     spawnOptions.cwd = options.cwd;
   }
+  args = quoteAll(args, spawnOptions);
 
   return new Promise((resolve, reject) => {
     const out = {
       stdout: '',
       stderr: '',
diff --git a/package.json b/package.json
index e402a79..3bf2529 100644
--- a/package.json
+++ b/package.json
@@ -42,10 +42,11 @@
     "typescript": "^4.7.4"
   },
   "dependencies": {
     "debug": "^4.1.1",
     "semver": "^6.1.2",
+    "shescape": "1.6.1",
     "tmp": "^0.1.0",
     "tree-kill": "^1.2.2",
     "tslib": "^1.10.0"
   },
   "jest-junit": {
diff --git a/test/functional/sbt-plugin.test.ts b/test/functional/sbt-plugin.test.ts
index d06cc3a..e8bb899 100644
--- a/test/functional/sbt-plugin.test.ts
+++ b/test/functional/sbt-plugin.test.ts
@@ -5,22 +5,22 @@ test('check build args with array not coursier', (t) => {
   const result = plugin.buildArgs([
     '-Paxis',
     '-Pjaxen',
   ], false);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis',
     '-Pjaxen',
     'dependencyTree',
   ]);
   t.end();
 });
 
 test('check build args with string not coursie', (t) => {
   const result = plugin.buildArgs('-Paxis -Pjaxen', false);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis -Pjaxen',
     'dependencyTree',
   ]);
   t.end();
 });
@@ -29,52 +29,52 @@ test('check build args with array for coursier', (t) => {
   const result = plugin.buildArgs([
     '-Paxis',
     '-Pjaxen',
   ], true);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis',
     '-Pjaxen',
     'coursierDependencyTree',
   ]);
   t.end();
 });
 
 test('check build args with string for coursier', (t) => {
   const result = plugin.buildArgs('-Paxis -Pjaxen', true);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis -Pjaxen',
     'coursierDependencyTree',
   ]);
   t.end();
 });
 
 test('check build args with string for snykRenderTree', (t) => {
   const result = plugin.buildArgs('-Paxis -Pjaxen', false, true);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis -Pjaxen',
     'snykRenderTree',
   ]);
   t.end();
 });
 
 test('check build args with string for coursier and not snykRenderTree', (t) => {
   const result = plugin.buildArgs('-Paxis -Pjaxen', true, false);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis -Pjaxen',
     'coursierDependencyTree',
   ]);
   t.end();
 });
 
 test('check build args with string for not coursier and not snykRenderTree', (t) => {
   const result = plugin.buildArgs('-Paxis -Pjaxen', false, false);
   t.deepEqual(result, [
-    '"-Dsbt.log.noformat=true\"',
+    '-Dsbt.log.noformat=true',
     '-Paxis -Pjaxen',
     'dependencyTree',
   ]);
   t.end();
 });

Vulnerable code:   let args = ['"-Dsbt.log.noformat=true"'];
    '"-Dsbt.log.noformat=true\"',
    '"-Dsbt.log.noformat=true\"',
    '"-Dsbt.log.noformat=true\"',
    '"-Dsbt.log.noformat=true\"',
    '"-Dsbt.log.noformat=true\"',
    '"-Dsbt.log.noformat=true\"',
    '"-Dsbt.log.noformat=true\"',
Fixed code: # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.19.0
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
ignore:
  'snyk:lic:npm:shescape:MPL-2.0':
    - '*':
        reason: None Given
        expires: 2122-12-29T08:01:32.423Z
        created: 2022-11-29T08:01:32.424Z
patch: {}
  let args = ['-Dsbt.log.noformat=true'];
import { quoteAll } from 'shescape';
  args = quoteAll(args, spawnOptions);
    "shescape": "1.6.1",
    '-Dsbt.log.noformat=true',
    '-Dsbt.log.noformat=true',
    '-Dsbt.log.noformat=true',
    '-Dsbt.log.noformat=true',
    '-Dsbt.log.noformat=true',
    '-Dsbt.log.noformat=true',
    '-Dsbt.log.noformat=true',