Description
Groovy code injection occurs when an untrusted string is interpreted as Groovy source code. Eval, GroovyShell.evaluate/run, and Script.evaluate execute the supplied expression or script. GroovyShell.parse and GroovyClassLoader.parseClass compile source into an executable script or class; features such as Grape dependency resolution and AST transformations can also run during compilation.
An attacker who controls the source text may read or modify data and files, access internal networks, or start processes with the application's privileges. Infinite loops or excessive allocations can exhaust resources. Removing selected characters or known method names does not adequately restrict Groovy syntax and metaprogramming, so it is not a safe sanitization strategy.
Potential impact
- Arbitrary code or operating-system command execution with application privileges
- Access to files, environment variables, credentials, databases, and internal services
- Modification of application data and bypass of privilege boundaries
- Denial of service through excessive CPU, memory, threads, or output
Remediation
- Keep Groovy source as fixed, developer-controlled text. Prefer Java logic, or map a finite set of user-selectable keys to reviewed functions or scripts.
- If a fixed script needs request values, pass them as data through
BindingorEvalvalue arguments. Do not concatenate or interpolate them into source code. - If user-authored code is required, run it outside the application process in a separate process, container, or virtual machine. Remove application secrets, unnecessary mounts, and network access. Use an unprivileged account and enforce CPU, memory, process, output, and execution-time limits, terminating runs that exceed them.
SecureASTCustomizercan restrict syntax as defense in depth, but is not a complete sandbox. Disable unnecessary Grape and compilation features and minimize allowed capabilities. Do not rely on a customClassLoaderdenylist or Java Security Manager as the security boundary. Security Manager is permanently disabled from JDK 24.- Use a supported stable Groovy release with current security patches. Audit script identifiers and execution outcomes without logging sensitive source or binding values.
Examples
Before
import groovy.lang.GroovyShell;
import groovy.lang.Script;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
class UnsafeGroovyController {
@PostMapping("/exec")
Object execute(@RequestParam("script") String userScript) {
Script script = new GroovyShell().parse(userScript); // BAD: compile user input
return script.run();
}
}
parse creates a Script for later execution; it does not validate the source as safe. Compilation features can act before run(), so delaying that call does not provide isolation.
After
Where possible, remove dynamic-language execution and let users select only operations controlled by the server.
import java.util.Map;
import java.util.function.IntUnaryOperator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
class SafeController {
private static final Map<String, IntUnaryOperator> OPERATIONS = Map.of(
"double", value -> value * 2,
"square", value -> value * value);
@GetMapping("/calculate")
int calculate(@RequestParam("operation") String operation,
@RequestParam("value") int value) {
IntUnaryOperator selected = OPERATIONS.get(operation);
if (selected == null) {
throw new IllegalArgumentException("unsupported operation");
}
return selected.applyAsInt(value);
}
}
If Groovy is necessary and the source is fixed, keep external values separate as binding data.
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
Object doubleValue(int value) {
Binding binding = new Binding();
binding.setVariable("value", value);
return new GroovyShell(binding).evaluate("return value * 2");
}
References
- Apache Groovy 5.1.1 API
- Groovy Eval API
- GroovyShell API
- Script API
- GroovyClassLoader API
- Groovy integration guide
- SecureASTCustomizer API
- Groovy 5.1.0 changelog:
groovy.asttest.enable - JEP 486: Permanently Disable the Security Manager
- CWE-94: Improper Control of Generation of Code
- OWASP Top 10:2025 A05 Injection
- OWASP Top 10:2021 A03 Injection
- OWASP ASVS 5.0