kestra-io/kestra · error · PebbleException
RegexTimeoutException: e.getMessage()
Error message
RegexTimeoutException: e.getMessage()
What it means
Thrown by the 'replace' Pebble filter when `regexp=true` is set and one of the find patterns in `replace_pairs` triggers catastrophic backtracking, raising `RegexUtils.RegexTimeoutException`. Kestra enforces a regex execution timeout to prevent ReDoS; exceeding it aborts the substitution.
Source
Thrown at core/src/main/java/io/kestra/core/runners/pebble/filters/ReplaceFilter.java:57
null,
MessageFormat.format("The argument ''{0}'' is required.", ARGUMENT_PAIRS), lineNumber,
self.getName()
);
}
final boolean regexp = args.containsKey(ARGUMENT_REGEXP) ? (Boolean) args.get(ARGUMENT_REGEXP) : false;
Map<?, ?> replacePair = (Map<?, ?>) args.get(ARGUMENT_PAIRS);
try {
if (input instanceof Map) {
return processMap((Map<String, Object>) input, replacePair, regexp);
} else if (input instanceof List) {
return processList((List<Object>) input, replacePair, regexp);
} else {
return processString(input.toString(), replacePair, regexp);
}
} catch (RegexUtils.RegexTimeoutException e) {
throw new PebbleException(e, e.getMessage(), lineNumber, self.getName());
}
}
@SuppressWarnings("unchecked")
private Object processMap(Map<String, Object> inputMap, Map<?, ?> replacePair, boolean regexp) {
Map<String, Object> resultMap = new HashMap<>();
for (Map.Entry<String, Object> entry : inputMap.entrySet()) {
Object value = entry.getValue();
if (value instanceof String stringValue) {
resultMap.put(entry.getKey(), processString(stringValue, replacePair, regexp));
} else if (value instanceof Map) {
resultMap.put(entry.getKey(), processMap((Map<String, Object>) value, replacePair, regexp));
} else if (value instanceof List<?>) {
resultMap.put(entry.getKey(), processList((List<Object>) value, replacePair, regexp));
} else {
resultMap.put(entry.getKey(), processString(value.toString(), replacePair, regexp));
}
}View on GitHub (pinned to 823fada927)
Solutions
- If you do not need regex semantics, set `regexp=false` (the default) so keys are treated as literals.
- Rewrite the vulnerable pattern to a linear-time equivalent.
- Pre-truncate long input before substitution.
- Audit each key in `replace_pairs` for nested unbounded quantifiers.
Example fix
# before
{{ body | replace(replace_pairs={"(a+)+b":"x"}, regexp=true) }}
# after - literal match (no regex)
{{ body | replace(replace_pairs={"ab":"x"}) }} Defensive patterns
Strategy: validation
Validate before calling
# Prefer literal mode; only enable regexp with audited patterns and short inputs:
{% set safe = (body ?? "") | slice(0, 10000) %}
{{ safe | replace(replace_pairs={"a":"b"}) }} {# regexp defaults to false #} Prevention
- Set `regexp=false` (default) when keys are literal strings.
- Rewrite regex keys to linear equivalents; avoid nested quantifiers.
- Pre-truncate long input before substitution.
- Audit each key for ReDoS potential.
When it happens
Trigger: A find-pair key that is a vulnerable regex (e.g. `(a+)+`) applied to a long input; multiple regex pairs where any one backtracks explosively; user-controlled input flowing into the find key.
Common situations: Enabling `regexp=true` with patterns copy-pasted from elsewhere; large log/HTTP bodies processed with greedy nested quantifiers; not realizing keys are interpreted as regexes.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- RegexTimeoutException: e.getMessage()
- The argument ''{0}'' is required.
- Unable to hold the lock inside the configured timeout of {}
- An error occurred while flattening the list.
- The 'jq' filter expects an argument 'expression'.
AI-assisted analysis of kestra-io/kestra@823fada927 (2026-08-14).
Data as JSON: /api/errors/4c8bcba702934648.
Report an issue: GitHub.