Skip to content

Commit 6b4b7dd

Browse files
jymaireclaude
andcommitted
test(security): unit-test requirement shell-quoting and config scrubbing
Add fast, dependency-free tests for the two audit-remediation helpers: shellQuote (metacharacter neutralization, embedded-quote escaping, version operators, env markers) and scrubSensitiveValues (top-level and nested-map redaction, secret-key variants, non-sensitive values preserved). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent a7ff36d commit 6b4b7dd

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package io.kestra.plugin.soda;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.LinkedHashMap;
5+
import java.util.Map;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
import static org.hamcrest.MatcherAssert.assertThat;
10+
import static org.hamcrest.Matchers.*;
11+
12+
/**
13+
* Fast, dependency-free unit tests for the security-hardening helpers introduced in the
14+
* 2026-06-30 audit remediation PR: shell-quoting of pip requirements ({@link AbstractSoda})
15+
* and sensitive-value scrubbing of the rendered configuration ({@link Scan}). Both helpers are
16+
* {@code private static} pure functions, so they are exercised directly via reflection rather than
17+
* through the Docker/BigQuery integration path used by {@code ScanTest}.
18+
*/
19+
class SecurityHardeningTest {
20+
21+
private static String shellQuote(String value) throws Exception {
22+
Method method = AbstractSoda.class.getDeclaredMethod("shellQuote", String.class);
23+
method.setAccessible(true);
24+
return (String) method.invoke(null, value);
25+
}
26+
27+
@SuppressWarnings("unchecked")
28+
private static Map<String, Object> scrub(Map<String, Object> input) throws Exception {
29+
Method method = Scan.class.getDeclaredMethod("scrubSensitiveValues", Map.class);
30+
method.setAccessible(true);
31+
return (Map<String, Object>) method.invoke(null, input);
32+
}
33+
34+
// --- shellQuote -------------------------------------------------------------------------
35+
36+
@Test
37+
void shellQuote_wrapsPlainRequirement() throws Exception {
38+
assertThat(shellQuote("soda-core-postgres"), is("'soda-core-postgres'"));
39+
}
40+
41+
@Test
42+
void shellQuote_preservesVersionOperatorsLiterally() throws Exception {
43+
// `>` and `<` used to be interpreted as shell redirection when interpolated bare.
44+
assertThat(shellQuote("soda-core[postgres]>=3.0,<4.0"), is("'soda-core[postgres]>=3.0,<4.0'"));
45+
}
46+
47+
@Test
48+
void shellQuote_neutralizesShellMetacharacters() throws Exception {
49+
// Command-injection attempt: the `;` and everything after must stay inside the quotes.
50+
String quoted = shellQuote("pkg; rm -rf /");
51+
assertThat(quoted, is("'pkg; rm -rf /'"));
52+
// Nothing escapes the single-quoted span, so no bare metacharacter is exposed to the shell.
53+
assertThat(quoted.startsWith("'"), is(true));
54+
assertThat(quoted.endsWith("'"), is(true));
55+
}
56+
57+
@Test
58+
void shellQuote_escapesEmbeddedSingleQuote() throws Exception {
59+
// The one character that cannot live inside single quotes is escaped as '\'' .
60+
assertThat(shellQuote("a'b"), is("'a'\\''b'"));
61+
}
62+
63+
@Test
64+
void shellQuote_preservesEnvironmentMarkers() throws Exception {
65+
assertThat(
66+
shellQuote("pkg; python_version >= \"3.8\""),
67+
is("'pkg; python_version >= \"3.8\"'")
68+
);
69+
}
70+
71+
// --- scrubSensitiveValues ---------------------------------------------------------------
72+
73+
@Test
74+
void scrub_redactsTopLevelSensitiveKeys() throws Exception {
75+
Map<String, Object> input = new LinkedHashMap<>();
76+
input.put("password", "hunter2");
77+
input.put("host", "db.internal");
78+
79+
Map<String, Object> result = scrub(input);
80+
81+
assertThat(result.get("password"), is("******"));
82+
assertThat(result.get("host"), is("db.internal"));
83+
}
84+
85+
@Test
86+
void scrub_recursesIntoNestedMaps() throws Exception {
87+
Map<String, Object> connection = new LinkedHashMap<>();
88+
connection.put("password", "s3cret");
89+
connection.put("port", 5432);
90+
91+
Map<String, Object> input = new LinkedHashMap<>();
92+
input.put("connection", connection);
93+
94+
Map<String, Object> result = scrub(input);
95+
96+
@SuppressWarnings("unchecked")
97+
Map<String, Object> scrubbedConnection = (Map<String, Object>) result.get("connection");
98+
assertThat(scrubbedConnection.get("password"), is("******"));
99+
// Non-sensitive leaf values keep their original value and type.
100+
assertThat(scrubbedConnection.get("port"), is(5432));
101+
}
102+
103+
@Test
104+
void scrub_redactsSecretKeyVariants() throws Exception {
105+
Map<String, Object> input = new LinkedHashMap<>();
106+
input.put("api_key", "abc");
107+
input.put("token", "xyz");
108+
input.put("account_info_json", "{...}");
109+
input.put("type", "postgres");
110+
111+
Map<String, Object> result = scrub(input);
112+
113+
assertThat(result.get("api_key"), is("******"));
114+
assertThat(result.get("token"), is("******"));
115+
assertThat(result.get("account_info_json"), is("******"));
116+
assertThat(result.get("type"), is("postgres"));
117+
}
118+
}

0 commit comments

Comments
 (0)