You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opennlp.apache.org by rz...@apache.org on 2026/09/05 17:11:09 UTC
(opennlp) branch main updated: OPENNLP-1925: Document RegEx exemption and harden ReDoS guard test (#1268)
This is an automated email from the ASF dual-hosted git repository.
rzo1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opennlp.git
The following commit(s) were added to refs/heads/main by this push:
new b81a2a7d1 OPENNLP-1925: Document RegEx exemption and harden ReDoS guard test (#1268)
b81a2a7d1 is described below
commit b81a2a7d1ea6c94fe58689cb0c84e93e24ee229e
Author: Kristian Rickert <kr...@gmail.com>
AuthorDate: Sat Sep 5 13:11:02 2026 -0400
OPENNLP-1925: Document RegEx exemption and harden ReDoS guard test (#1268)
---
.../opennlp/tools/namefind/RegexNameFinder.java | 3 ++
.../tools/namefind/RegexNameFinderFactory.java | 3 ++
.../tools/namefind/RegexNameFinderFactoryTest.java | 47 ++++++++++++++--------
3 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinder.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinder.java
index b8239c28e..c62841ebd 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinder.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinder.java
@@ -29,6 +29,9 @@ import opennlp.tools.util.Span;
/**
* A {@link TokenNameFinder} implementation based on a series of regular expressions.
+ * <p>
+ * Please note: RegEx is not allowed in the processing path of OpenNLP.
+ * However, a RegEx step is an obvious exception.
*/
public final class RegexNameFinder implements TokenNameFinder {
diff --git a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinderFactory.java b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinderFactory.java
index 0890ae051..003d94512 100644
--- a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinderFactory.java
+++ b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/namefind/RegexNameFinderFactory.java
@@ -26,6 +26,9 @@ import java.util.regex.Pattern;
*
* Returns a {@link RegexNameFinder} based on a selection of
* defaults or a configuration and a selection of defaults.
+ * <p>
+ * Please note: RegEx is not allowed in the processing path of OpenNLP.
+ * However, a RegEx step is an obvious exception.
*/
public class RegexNameFinderFactory {
diff --git a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/RegexNameFinderFactoryTest.java b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/RegexNameFinderFactoryTest.java
index 6fdea6414..9bc72dbb2 100644
--- a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/RegexNameFinderFactoryTest.java
+++ b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/namefind/RegexNameFinderFactoryTest.java
@@ -20,10 +20,15 @@ package opennlp.tools.namefind;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
+import java.util.stream.Stream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import opennlp.tools.tokenize.WhitespaceTokenizer;
import opennlp.tools.util.Span;
@@ -95,24 +100,32 @@ public class RegexNameFinderFactoryTest {
* Crafted inputs that used to drive the built-in EMAIL and URL patterns into
* catastrophic backtracking / deep recursion (ReDoS, CWE-1333 / CWE-400 / CWE-674).
* The hardened patterns must finish quickly regardless of input length.
+ * <p>
+ * Each attack runs as its own test so a regression names the exact input that
+ * got slow. Each case does one warmup run outside the timed section, so the
+ * budget measures the match itself rather than JVM cold start on throttled
+ * machines.
*/
- @Test
- void testBuiltinPatternsAreNotVulnerableToReDoS() {
- final String emailLocalBlowup = "a".repeat(100_000) + "@ ";
- final String emailDomainBlowup = "x@a" + "-a".repeat(60_000) + " ";
- final String urlPathRecursion = "http://a.com/" + "a".repeat(100_000) + " ";
- final String urlNestedBlowup = "http://a.com" + "/a".repeat(50_000) + "%z";
- // The reported StackOverflowError repro: a long ?a&a&a&... query string, which
- // drove the nested (&(...)+ ... )* group in the old URL pattern into deep recursion.
- final String urlQueryRecursion = "http://a.com/p?a" + "&a".repeat(50_000) + "= ";
-
- Assertions.assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {
- for (String attack : new String[] {
- emailLocalBlowup, emailDomainBlowup, urlPathRecursion, urlNestedBlowup, urlQueryRecursion}) {
- String[] tokens = WhitespaceTokenizer.INSTANCE.tokenize(attack);
- regexNameFinder.find(tokens);
- }
- });
+ private static Stream<Arguments> reDoSAttacks() {
+ return Stream.of(
+ Arguments.of(Named.of("emailLocalBlowup", "a".repeat(100_000) + "@ ")),
+ Arguments.of(Named.of("emailDomainBlowup", "x@a" + "-a".repeat(60_000) + " ")),
+ Arguments.of(Named.of("urlPathRecursion", "http://a.com/" + "a".repeat(100_000) + " ")),
+ Arguments.of(Named.of("urlNestedBlowup", "http://a.com" + "/a".repeat(50_000) + "%z")),
+ // The reported StackOverflowError repro: a long ?a&a&a&... query string, which
+ // drove the nested (&(...)+ ... )* group in the old URL pattern into deep recursion.
+ Arguments.of(Named.of("urlQueryRecursion", "http://a.com/p?a" + "&a".repeat(50_000) + "= ")));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("reDoSAttacks")
+ void testBuiltinPatternsAreNotVulnerableToReDoS(String attack) {
+ String[] tokens = WhitespaceTokenizer.INSTANCE.tokenize(attack);
+ // Warmup: JIT-compile the pattern engine before the timed run.
+ regexNameFinder.find(tokens);
+
+ Assertions.assertTimeoutPreemptively(Duration.ofSeconds(2),
+ () -> regexNameFinder.find(tokens));
}
/**