VYPR
High severityNVD Advisory· Published Oct 23, 2024· Updated Oct 24, 2024

CVE-2024-48964

CVE-2024-48964

Description

The package Snyk CLI before 1.1294.0 is vulnerable to Code Injection when scanning an untrusted Gradle project. The vulnerability can be triggered if Snyk test is run inside the untrusted project due to the improper handling of the current working directory name. Snyk recommends only scanning trusted projects.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Snyk CLI before 1.1294.0 vulnerable to code injection via malicious Gradle project directory name, enabling arbitrary command execution.

Vulnerability

CVE-2024-48964 is a code injection vulnerability in the Snyk CLI Gradle plugin affecting versions prior to 1.1294.0. The root cause is improper sanitization of the current working directory name when constructing and executing Gradle commands. An attacker can create a project with a directory name containing shell metacharacters or command sequences, which are then incorporated into the command line without proper escaping [1][2].

Exploitation

The vulnerability can be triggered if Snyk test is executed inside an untrusted Gradle project. No authentication or special privileges beyond file system access are required. By crafting a directory name with malicious payloads (e.g., using backticks or semicolons), an attacker can inject arbitrary commands that are executed by the Snyk CLI process. The fix in commit [2] introduces safer argument handling and wrapper process generation to prevent injection.

Impact

Successful exploitation allows arbitrary command execution with the privileges of the user running Snyk CLI. This can lead to data exfiltration, installation of malware, or lateral movement within the environment. The vulnerability did not affect trusting projects, as the malicious directory name is the trigger.

Mitigation

Snyk has patched this vulnerability in version 1.1294.0 of the CLI. Users are strongly advised to update immediately and to only scan projects from trusted sources. There is no known workaround other than avoiding use of the CLI on untrusted projects.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
snyk-gradle-pluginnpm
< 4.5.04.5.0

Affected products

5

Patches

1
2f5ee7579f00

feat: handle gradle wrapper files for windows (#292)

https://github.com/snyk/snyk-gradle-pluginDamilola OlufemiOct 3, 2024via ghsa
6 files changed · +82 32
  • lib/index.ts+35 9 modified
    @@ -396,11 +396,13 @@ function getVersionBuildInfo(
     export async function getGradleVersion(
       root: string,
       command: string,
    +  args?: string[],
     ): Promise<string> {
       debugLog('`gradle -v` command run: ' + command);
       let gradleVersionOutput = '[COULD NOT RUN gradle -v]';
    +  const completeArgs = args ? args.concat(['-v']) : ['-v'];
       try {
    -    gradleVersionOutput = await subProcess.execute(command, ['-v'], {
    +    gradleVersionOutput = await subProcess.execute(command, completeArgs, {
           cwd: root,
         });
       } catch (_) {
    @@ -409,6 +411,22 @@ export async function getGradleVersion(
       return gradleVersionOutput;
     }
     
    +export function generateWrapperProcessArgs(
    +  commandPath: string,
    +  args: string[],
    +): { command: string; args: string[] } {
    +  let parseArgs: string[] = [];
    +  let command = commandPath;
    +  const isWinLocal = /^win/.test(os.platform());
    +  if (isWinLocal && command !== 'gradle') {
    +    command = 'cmd.exe';
    +    parseArgs.push('/c');
    +    parseArgs.push(commandPath);
    +  }
    +  parseArgs = parseArgs.concat(args);
    +  return { command, args: parseArgs };
    +}
    +
     async function getAllDepsWithPlugin(
       root: string,
       targetFile: string,
    @@ -427,12 +445,15 @@ async function getAllDepsWithPlugin(
         gradleVersion,
       );
     
    -  const fullCommandText = 'gradle command: ' + command + ' ' + args.join(' ');
    -  debugLog('Executing ' + fullCommandText);
    +  const { command: wrapperedCommand, args: wrapperArgs } =
    +    generateWrapperProcessArgs(command, args);
     
    +  const fullCommandText =
    +    'gradle command: ' + wrapperedCommand + ' ' + wrapperArgs.join(' ');
    +  debugLog('Executing ' + fullCommandText);
       const stdoutText = await subProcess.execute(
    -    command,
    -    args,
    +    wrapperedCommand,
    +    wrapperArgs,
         { cwd: root },
         printIfEcho,
       );
    @@ -467,7 +488,13 @@ async function getAllDeps(
       snykHttpClient: SnykHttpClient,
     ): Promise<JsonDepsScriptResult> {
       const command = getCommand(root, targetFile);
    -  const gradleVersion = await getGradleVersion(root, command);
    +  const { command: wrapperedCommand, args: wrapperArgs } =
    +    generateWrapperProcessArgs(command, []);
    +  const gradleVersion = await getGradleVersion(
    +    root,
    +    wrapperedCommand,
    +    wrapperArgs,
    +  );
       if (gradleVersion.match(/Gradle 1/)) {
         throw new Error('Gradle 1.x is not supported');
       }
    @@ -636,7 +663,6 @@ function toCamelCase(input: string) {
     
     function getCommand(root: string, targetFile: string) {
       const isWinLocal = /^win/.test(os.platform()); // local check, can be stubbed in tests
    -  const quotLocal = isWinLocal ? '"' : "'";
       const wrapperScript = isWinLocal ? 'gradlew.bat' : './gradlew';
       // try to find a sibling wrapper script first
       let pathToWrapper = path.resolve(
    @@ -645,12 +671,12 @@ function getCommand(root: string, targetFile: string) {
         wrapperScript,
       );
       if (fs.existsSync(pathToWrapper)) {
    -    return quotLocal + pathToWrapper + quotLocal;
    +    return pathToWrapper;
       }
       // now try to find a wrapper in the root
       pathToWrapper = path.resolve(root, wrapperScript);
       if (fs.existsSync(pathToWrapper)) {
    -    return quotLocal + pathToWrapper + quotLocal;
    +    return pathToWrapper;
       }
       return 'gradle';
     }
    
  • lib/sub-process.ts+3 3 modified
    @@ -1,6 +1,6 @@
     import * as childProcess from 'child_process';
     import debugModule = require('debug');
    -import { quoteAll } from 'shescape';
    +import { escapeAll } from 'shescape';
     
     const debugLogging = debugModule('snyk-gradle-plugin');
     
    @@ -12,7 +12,7 @@ export function execute(
       perLineCallback?: (s: string) => Promise<void>,
     ): Promise<string> {
       const spawnOptions: childProcess.SpawnOptions = {
    -    shell: true,
    +    shell: false,
         env: { ...process.env },
       };
       if (options?.cwd) {
    @@ -22,7 +22,7 @@ export function execute(
         spawnOptions.env = { ...process.env, ...options.env };
       }
     
    -  args = quoteAll(args, spawnOptions);
    +  args = escapeAll(args, spawnOptions);
     
       // Before spawning an external process, we look if we need to restore the system proxy configuration,
       // which overides the cli internal proxy configuration.
    
  • test/fixtures-with-wrappers/with-lock-file/dep-graph.json+1 1 modified
    @@ -144,4 +144,4 @@
           }
         ]
       }
    -}
    +}
    \ No newline at end of file
    
  • test/system/darwin.test.ts+2 2 modified
    @@ -34,7 +34,7 @@ test('darwin without wrapper invokes gradle directly', async () => {
     test('darwin with wrapper invokes wrapper script', async () => {
       await expect(inspect(rootWithWrapper, 'build.gradle')).rejects.toThrow();
       expect(subProcessExecSpy.mock.calls[0][0]).toBe(
    -    "'" + path.join(rootWithWrapper, 'gradlew') + "'",
    +    path.join(rootWithWrapper, 'gradlew'),
       );
     });
     
    @@ -43,6 +43,6 @@ test('darwin with wrapper in root invokes wrapper script', async () => {
         inspect(subWithWrapper, path.join('app', 'build.gradle')),
       ).rejects.toThrow();
       expect(subProcessExecSpy.mock.calls[0][0]).toBe(
    -    "'" + path.join(subWithWrapper, 'gradlew') + "'",
    +    path.join(subWithWrapper, 'gradlew'),
       );
     });
    
  • test/system/plugin.test.ts+16 1 modified
    @@ -2,7 +2,8 @@ import * as fs from 'fs';
     import * as path from 'path';
     import * as depGraphLib from '@snyk/dep-graph';
     import { fixtureDir } from '../common';
    -import { inspect } from '../../lib';
    +import { generateWrapperProcessArgs, inspect } from '../../lib';
    +import * as os from 'os';
     
     const rootNoWrapper = fixtureDir('no wrapper');
     const withInitScript = fixtureDir('with-init-script');
    @@ -212,3 +213,17 @@ test('repeated transitive lines not pruned if verbose graph', async () => {
       });
       expect(result.dependencyGraph?.equals(expected)).toBe(true);
     });
    +
    +test('generateWrapperProcessArgs should return gradle is wrapper is not used', () => {
    +  const result = generateWrapperProcessArgs('gradle', ['-v']);
    +  expect(result).toEqual({ command: 'gradle', args: ['-v'] });
    +});
    +test('generateWrapperProcessArgs should return wrapped command is os is windows ', () => {
    +  const platformMock = jest.spyOn(os, 'platform');
    +  platformMock.mockReturnValue('win32');
    +  const result = generateWrapperProcessArgs('foo/bar/gradlew.bat', ['-v']);
    +  expect(result).toEqual({
    +    command: 'cmd.exe',
    +    args: ['/c', 'foo/bar/gradlew.bat', '-v'],
    +  });
    +});
    
  • test/system/windows.test.ts+25 16 modified
    @@ -9,6 +9,7 @@ const rootWithWrapper = fixtureDir('with-wrapper');
     const subWithWrapper = fixtureDir('with-wrapper-in-root');
     let subProcessExecSpy;
     let platformMock;
    +const isWinLocal = /^win/.test(os.platform());
     
     beforeAll(() => {
       platformMock = jest.spyOn(os, 'platform');
    @@ -24,23 +25,31 @@ afterAll(() => {
     afterEach(() => {
       jest.clearAllMocks();
     });
    +if (isWinLocal) {
    +  test('windows with wrapper in root invokes wrapper bat', async () => {
    +    await expect(
    +      inspect(subWithWrapper, path.join('app', 'build.gradle')),
    +    ).rejects.toThrow();
    +    expect(subProcessExecSpy.mock.calls[0]).toEqual([
    +      'cmd.exe',
    +      ['/c', `${subWithWrapper}\\gradlew.bat`, '-v'],
    +      {
    +        cwd: `${subWithWrapper}`,
    +      },
    +    ]);
    +  });
     
    -test('windows with wrapper in root invokes wrapper bat', async () => {
    -  await expect(
    -    inspect(subWithWrapper, path.join('app', 'build.gradle')),
    -  ).rejects.toThrow();
    -  expect(subProcessExecSpy.mock.calls[0][0]).toBe(
    -    '"' + path.join(subWithWrapper, 'gradlew.bat') + '"',
    -  );
    -});
    -
    -test('windows with wrapper invokes wrapper bat', async () => {
    -  await expect(inspect(rootWithWrapper, 'build.gradle')).rejects.toThrow();
    -  expect(subProcessExecSpy.mock.calls[0][0]).toBe(
    -    '"' + path.join(rootWithWrapper, 'gradlew.bat') + '"',
    -  );
    -});
    -
    +  test('windows with wrapper invokes wrapper bat', async () => {
    +    await expect(inspect(rootWithWrapper, 'build.gradle')).rejects.toThrow();
    +    expect(subProcessExecSpy.mock.calls[0]).toEqual([
    +      'cmd.exe',
    +      ['/c', `${rootWithWrapper}\\gradlew.bat`, '-v'],
    +      {
    +        cwd: `${rootWithWrapper}`,
    +      },
    +    ]);
    +  });
    +}
     test('windows without wrapper invokes gradle directly', async () => {
       await expect(inspect(rootNoWrapper, 'build.gradle')).rejects.toThrow();
       expect(subProcessExecSpy.mock.calls[0][0]).toBe('gradle');
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.