Skip to content

Commit a7ff36d

Browse files
jymaireclaude
andcommitted
fix(security): shell-quote pip requirements instead of allowlist filtering
Replace the SAFE_REQUIREMENT_PATTERN allowlist with per-token single-quoting. Inside single quotes /bin/sh treats every character literally, so all shell metacharacters are neutralized while valid requirements.txt syntax (version ranges, extras, VCS URLs, environment markers) is preserved. Also fixes a latent bug where an unquoted `pkg>=1.0` had its `>` parsed as shell redirection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7967d7c commit a7ff36d

1 file changed

Lines changed: 17 additions & 12 deletions

File tree

src/main/java/io/kestra/plugin/soda/AbstractSoda.java

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import java.util.HashMap;
99
import java.util.List;
1010
import java.util.Map;
11-
import java.util.regex.Pattern;
11+
import java.util.stream.Collectors;
1212

1313
import com.fasterxml.jackson.annotation.JsonSetter;
1414
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -44,7 +44,6 @@
4444
public abstract class AbstractSoda extends Task {
4545
private static final String DEFAULT_IMAGE = "sodadata/soda-core";
4646
protected static final ObjectMapper MAPPER = JacksonMapper.ofYaml();
47-
private static final Pattern SAFE_REQUIREMENT_PATTERN = Pattern.compile("^[a-zA-Z0-9_\\-.\\[\\]>=<,!~ ]+$");
4847

4948
@Schema(
5049
title = "Runner to use",
@@ -187,24 +186,30 @@ private String virtualEnvCommand(RunContext runContext, Path workingDirectory, L
187186
renderer.add("python -m venv --system-site-packages " + workingDirectory + " > /dev/null");
188187

189188
if (requirements != null) {
190-
for (String requirement : requirements) {
191-
if (!SAFE_REQUIREMENT_PATTERN.matcher(requirement).matches()) {
192-
throw new IllegalArgumentException(
193-
"Invalid requirement '" + requirement + "': requirements must only contain " +
194-
"alphanumeric characters and the following symbols: _ - . [ ] > = < , ! ~ and space. " +
195-
"Shell metacharacters are not allowed."
196-
);
197-
}
198-
}
189+
String installArgs = requirements.stream()
190+
.map(AbstractSoda::shellQuote)
191+
.collect(Collectors.joining(" "));
199192

200193
renderer.addAll(
201194
Arrays.asList(
202195
"./bin/pip install pip --upgrade > /dev/null",
203-
"./bin/pip install " + String.join(" ", requirements) + " > /dev/null"
196+
"./bin/pip install " + installArgs + " > /dev/null"
204197
)
205198
);
206199
}
207200

208201
return String.join("\n", renderer);
209202
}
203+
204+
/**
205+
* Wraps a value in single quotes for safe interpolation into a {@code /bin/sh -c} command line.
206+
* Inside single quotes the shell treats every character literally, so all metacharacters
207+
* ({@code ; & | > < * $ ` ( )} …) are neutralized while any valid {@code requirements.txt} syntax
208+
* (version specifiers, extras, VCS URLs, environment markers) is preserved verbatim. The only
209+
* character that cannot appear inside single quotes — the single quote itself — is escaped with
210+
* the standard {@code '\''} sequence (close quote, escaped quote, reopen quote).
211+
*/
212+
private static String shellQuote(String value) {
213+
return "'" + value.replace("'", "'\\''") + "'";
214+
}
210215
}

0 commit comments

Comments
 (0)