From 84f642329de804615ff16f34d12a2249f1890850 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Thu, 14 May 2026 21:57:25 -0700 Subject: [PATCH] Fix shell-injection in LicenseHeaderStep SET_FROM_GIT mode parseYear() concatenated file.getAbsolutePath() into a string and ran it through bash -c / cmd /c, so a file in a processed repository whose name contained shell metacharacters could execute arbitrary commands under the user running Spotless. Invoke git directly via ProcessBuilder with an explicit argument list so no shell parses the path. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGES.md | 2 ++ .../spotless/generic/LicenseHeaderStep.java | 22 +++++++++---------- plugin-gradle/CHANGES.md | 2 ++ plugin-maven/CHANGES.md | 2 ++ 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 74a5a8e109..d2e987fdfd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ This document is intended for Spotless developers. We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Fixed +- `LicenseHeaderStep` in `SET_FROM_GIT` year mode no longer invokes `git log` through `bash -c` / `cmd /c`, eliminating a shell-injection vector when processing repositories that contain files whose names include shell metacharacters. ## [4.6.0] - 2026-05-14 ### Added diff --git a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java index ccf0df1bec..bcb743083e 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.Serializable; import java.time.YearMonth; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -34,7 +35,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; @@ -420,14 +420,14 @@ private String setLicenseHeaderYearsFromGitHistory(String raw, File file) throws String oldYear; try { - oldYear = parseYear("git log --follow --find-renames=40% --diff-filter=A", file); + oldYear = parseYear(Arrays.asList("git", "log", "--follow", "--find-renames=40%", "--diff-filter=A"), file); } catch (IllegalArgumentException e) { // Ideally, git log would always find the commit where it was added. // For some reason, that is sometimes not possible - in that case, // we'll settle for just the most recent, even if it was just a modification. - oldYear = parseYear("git log --follow --find-renames=40% --reverse", file); + oldYear = parseYear(Arrays.asList("git", "log", "--follow", "--find-renames=40%", "--reverse"), file); } - String newYear = parseYear("git log --max-count=1", file); + String newYear = parseYear(Arrays.asList("git", "log", "--max-count=1"), file); String yearRange; if (oldYear.equals(newYear)) { yearRange = oldYear; @@ -450,14 +450,12 @@ private String replaceFileName(String raw, File file) { return FILENAME_PATTERN.matcher(header).replaceAll(file.getName()) + content; } - private static String parseYear(String cmd, File file) throws IOException { - String fullCmd = cmd + " -- " + file.getAbsolutePath(); - ProcessBuilder builder = new ProcessBuilder().directory(file.getParentFile()); - if (FileSignature.machineIsWin()) { - builder.command("cmd", "/c", fullCmd); - } else { - builder.command("bash", "-c", fullCmd); - } + private static String parseYear(List cmd, File file) throws IOException { + List fullCmd = new ArrayList<>(cmd.size() + 2); + fullCmd.addAll(cmd); + fullCmd.add("--"); + fullCmd.add(file.getAbsolutePath()); + ProcessBuilder builder = new ProcessBuilder().directory(file.getParentFile()).command(fullCmd); Process process = builder.start(); String output = drain(process.getInputStream()); String error = drain(process.getErrorStream()); diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 67492eb584..7cd19d2b6c 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Fixed +- `licenseHeader` with `setLicenseHeaderYearsFromGitHistory()` no longer runs `git log` through a shell, eliminating a shell-injection vector when formatting files whose names contain shell metacharacters. ## [8.5.0] - 2026-05-14 ### Added diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 5a6a73fa99..e8c1a23b49 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,6 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +### Fixed +- `` with `SET_FROM_GIT` no longer runs `git log` through a shell, eliminating a shell-injection vector when formatting files whose names contain shell metacharacters. ## [3.5.0] - 2026-05-14 ### Added