rzo1 commented on code in PR #1021:
URL: https://github.com/apache/opennlp/pull/1021#discussion_r3129956619
##########
opennlp-api/src/main/java/opennlp/tools/util/ext/ExtensionLoader.java:
##########
@@ -51,11 +119,22 @@ private ExtensionLoader() {
*
* @return the instance of the extension class
*
- * @throws ExtensionNotLoadedException Thrown if the load operation failed.
+ * @throws ExtensionNotLoadedException Thrown if the load operation failed or
+ * the class is not in an allowed package.
*/
@SuppressWarnings("unchecked")
public static <T> T instantiateExtension(Class<T> clazz, String
extensionClassName) {
+ // Validate BEFORE Class.forName() — static initializers execute during
forName(),
+ // so this check must precede the load to prevent gadget-chain RCE via
static init.
Review Comment:
This is actually CWE-470 (unsafe reflection via static initializers), not a
deserialization gadget chain.
##########
opennlp-api/src/main/java/opennlp/tools/util/ext/ExtensionLoader.java:
##########
@@ -19,22 +19,90 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import opennlp.tools.commons.Internal;
/**
* The {@link ExtensionLoader} is responsible to load extensions to the
OpenNLP library.
* <p>
+ * Only classes whose fully-qualified name starts with a registered package
prefix are
+ * permitted. The default allowed prefix is {@code opennlp.}, which covers all
built-in
+ * factories and serializers.
+ * <p>
+ * To allow custom extension classes from other packages, either:
+ * <ul>
+ * <li>Call {@link #registerAllowedPackage(String)} programmatically before
loading
+ * any model that uses the custom class.</li>
+ * <li>Set the system property {@code OPENNLP_EXT_ALLOWED_PACKAGES} to a
+ * comma-separated list of package prefixes at JVM startup, e.g.
+ * {@code -DOPENNLP_EXT_ALLOWED_PACKAGES=com.acme.nlp.,com.other.}.</li>
+ * </ul>
+ * <p>
* <b>Note:</b>
* Do not use this class, internal use only!
*/
@Internal
public class ExtensionLoader {
+ /**
+ * System property for supplying additional allowed package prefixes.
+ * Value is a comma-separated list, e.g. {@code com.acme.nlp.,com.other.}.
+ * <p>
+ * This property is read once at class-load time. If it cannot be set via
+ * {@code -D} at JVM startup (e.g. in embedded or test scenarios), call
+ * {@link #registerAllowedPackage(String)} before loading any model that
+ * uses a custom factory or serializer.
+ */
+ public static final String ALLOWED_PACKAGES_PROPERTY =
"OPENNLP_EXT_ALLOWED_PACKAGES";
+
+ /**
+ * Package prefixes whose classes are permitted to be instantiated as
extensions.
+ * Seeded from {@code opennlp.} plus any prefixes in {@link
#ALLOWED_PACKAGES_PROPERTY}.
+ */
+ private static final Set<String> ALLOWED_PREFIXES = initAllowedPrefixes();
+
+ private static Set<String> initAllowedPrefixes() {
+ Set<String> prefixes = new
CopyOnWriteArraySet<>(Collections.singleton("opennlp."));
+ String prop = System.getProperty(ALLOWED_PACKAGES_PROPERTY);
+ if (prop != null && !prop.isBlank()) {
+ Arrays.stream(prop.split(","))
+ .map(String::trim)
+ .filter(s -> !s.isBlank())
+ .map(s -> s.endsWith(".") ? s : s + ".")
+ .forEach(prefixes::add);
+ }
+ return prefixes;
+ }
+
private ExtensionLoader() {
}
- // Pass in the type (interface) of the class to load
+ /**
+ * Registers an additional package prefix whose classes are permitted to be
+ * loaded as OpenNLP extensions. Call this once at application startup,
before
+ * loading any model that uses a custom factory or serializer from that
package.
+ * <p>
+ * The prefix is normalized to end with {@code '.'} to prevent collision
attacks
+ * (e.g. registering {@code "com.acme"} cannot be exploited via {@code
"com.acmeevil.*"}).
+ *
+ * @param packagePrefix The package prefix to allow, e.g. {@code
"com.example.nlp"}.
+ * Must not be {@code null} or blank.
+ * @throws IllegalArgumentException if {@code packagePrefix} is null or
blank.
+ */
+ public static void registerAllowedPackage(String packagePrefix) {
+ Objects.requireNonNull(packagePrefix, "packagePrefix must not be null");
Review Comment:
This does not throw an IllegalArgumentException, see Javadoc.
##########
opennlp-api/src/main/java/opennlp/tools/util/ext/ExtensionLoader.java:
##########
@@ -19,22 +19,90 @@
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
import opennlp.tools.commons.Internal;
/**
* The {@link ExtensionLoader} is responsible to load extensions to the
OpenNLP library.
* <p>
+ * Only classes whose fully-qualified name starts with a registered package
prefix are
+ * permitted. The default allowed prefix is {@code opennlp.}, which covers all
built-in
+ * factories and serializers.
+ * <p>
+ * To allow custom extension classes from other packages, either:
+ * <ul>
+ * <li>Call {@link #registerAllowedPackage(String)} programmatically before
loading
+ * any model that uses the custom class.</li>
+ * <li>Set the system property {@code OPENNLP_EXT_ALLOWED_PACKAGES} to a
+ * comma-separated list of package prefixes at JVM startup, e.g.
+ * {@code -DOPENNLP_EXT_ALLOWED_PACKAGES=com.acme.nlp.,com.other.}.</li>
+ * </ul>
+ * <p>
* <b>Note:</b>
* Do not use this class, internal use only!
*/
@Internal
public class ExtensionLoader {
+ /**
+ * System property for supplying additional allowed package prefixes.
+ * Value is a comma-separated list, e.g. {@code com.acme.nlp.,com.other.}.
+ * <p>
+ * This property is read once at class-load time. If it cannot be set via
+ * {@code -D} at JVM startup (e.g. in embedded or test scenarios), call
+ * {@link #registerAllowedPackage(String)} before loading any model that
+ * uses a custom factory or serializer.
+ */
+ public static final String ALLOWED_PACKAGES_PROPERTY =
"OPENNLP_EXT_ALLOWED_PACKAGES";
+
+ /**
+ * Package prefixes whose classes are permitted to be instantiated as
extensions.
+ * Seeded from {@code opennlp.} plus any prefixes in {@link
#ALLOWED_PACKAGES_PROPERTY}.
+ */
+ private static final Set<String> ALLOWED_PREFIXES = initAllowedPrefixes();
+
+ private static Set<String> initAllowedPrefixes() {
+ Set<String> prefixes = new
CopyOnWriteArraySet<>(Collections.singleton("opennlp."));
+ String prop = System.getProperty(ALLOWED_PACKAGES_PROPERTY);
Review Comment:
Just add a `System.getProperty(ALLOWED_PACKAGES_PROPERTY, "")`, so you can
avoid the NULL check.
##########
opennlp-api/src/main/java/opennlp/tools/util/ext/ExtensionLoader.java:
##########
@@ -98,4 +177,13 @@ public static <T> T instantiateExtension(Class<T> clazz,
String extensionClassNa
clazz.getName() + ", the class or service " + extensionClassName +
" could not be located!");
}
+
+ /**
+ * Resets allowed package prefixes to defaults ({@code opennlp.} plus any
+ * prefixes in {@link #ALLOWED_PACKAGES_PROPERTY}). Package-private — for
tests only.
+ */
+ static void resetAllowedPackages() {
Review Comment:
Can we remove this and instead pair it with a
`unregisterAllowedPackage(String)` symmetric to what we have in `register...` ?
##########
opennlp-api/src/main/java/opennlp/tools/util/ext/ExtensionLoader.java:
##########
@@ -51,11 +119,22 @@ private ExtensionLoader() {
*
* @return the instance of the extension class
*
- * @throws ExtensionNotLoadedException Thrown if the load operation failed.
+ * @throws ExtensionNotLoadedException Thrown if the load operation failed or
+ * the class is not in an allowed package.
*/
@SuppressWarnings("unchecked")
public static <T> T instantiateExtension(Class<T> clazz, String
extensionClassName) {
+ // Validate BEFORE Class.forName() — static initializers execute during
forName(),
+ // so this check must precede the load to prevent gadget-chain RCE via
static init.
+ boolean allowed =
ALLOWED_PREFIXES.stream().anyMatch(extensionClassName::startsWith);
Review Comment:
This will fail if `null` is pased as an argument.
##########
opennlp-tools/src/test/java/opennlp/tools/util/ext/ExtensionLoaderTest.java:
##########
@@ -33,11 +33,152 @@ public String generateTestString() {
}
}
+ @AfterEach
+ void reset() {
+ System.clearProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY);
+ ExtensionLoader.resetAllowedPackages();
+ }
+
+ // --- existing test ---
+
@Test
void testLoadingStringGenerator() {
TestStringGenerator g =
ExtensionLoader.instantiateExtension(TestStringGenerator.class,
TestStringGeneratorImpl.class.getName());
Assertions.assertEquals("test", g.generateTestString());
}
+ // --- allowlist tests ---
+
+ /**
+ * Classes in opennlp.* are allowed by default — no registration needed.
+ */
+ @Test
+ void testBuiltinOpennlpPackageAllowedByDefault() {
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * A class outside opennlp.* is rejected without registration.
+ * This is the core security invariant — untrusted class names from model
+ * manifests must not reach Class.forName() without an explicit allowlist
entry.
+ */
+ @Test
+ void testUnregisteredPackageIsRejected() {
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.example.exploit.MaliciousFactory"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"),
+ "exception message should mention allowed package");
+ }
+
+ /**
+ * Allowlist check runs before Class.forName() — even for non-existent
classes
+ * the error must be "not in an allowed package", never "could not be
located".
+ */
+ @Test
+ void testAllowlistGateRunsBeforeClassForName() {
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.example.DoesNotExistOnClasspath$$Probe"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"),
+ "allowlist must reject before Class.forName(); got: " +
ex.getMessage());
+ Assertions.assertFalse(ex.getMessage().contains("could not be located"),
+ "Class.forName() must not have been reached; got: " + ex.getMessage());
+ }
+
+ /**
+ * After registerAllowedPackage(), a class from that package is permitted.
+ */
+ @Test
+ void testRegisteredPackageIsAllowed() {
+ ExtensionLoader.registerAllowedPackage("opennlp.tools.util.ext");
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * Prefix collision: registering "com.acme" must not permit "com.acmeevil.*".
+ */
+ @Test
+ void testPrefixCollisionPrevented() {
+ ExtensionLoader.registerAllowedPackage("com.acme");
+
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.acmeevil.Exploit"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"));
+ }
+
+ /**
+ * registerAllowedPackage() rejects null and blank inputs.
+ */
+ @Test
+ void testRegisterAllowedPackageRejectsNullAndBlank() {
+ Assertions.assertThrows(NullPointerException.class,
+ () -> ExtensionLoader.registerAllowedPackage(null));
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> ExtensionLoader.registerAllowedPackage(""));
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> ExtensionLoader.registerAllowedPackage(" "));
+ }
+
+ // --- system property escape hatch tests ---
+
+ /**
+ * OPENNLP_EXT_ALLOWED_PACKAGES system property permits a custom package
+ * without requiring a registerAllowedPackage() call — the operational
escape hatch.
+ */
+ @Test
+ void testSystemPropertyAddsAllowedPackage() {
+ System.setProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY,
"opennlp.tools.util.ext");
+ ExtensionLoader.resetAllowedPackages(); // re-initialize from updated
system property
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
Review Comment:
TestStringGeneratorImpl lives in opennlp.tools.util.ext, which is already
admitted by the default opennlp. prefix.
##########
opennlp-tools/src/test/java/opennlp/tools/util/ext/ExtensionLoaderTest.java:
##########
@@ -33,11 +33,152 @@ public String generateTestString() {
}
}
+ @AfterEach
+ void reset() {
+ System.clearProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY);
+ ExtensionLoader.resetAllowedPackages();
+ }
+
+ // --- existing test ---
+
@Test
void testLoadingStringGenerator() {
TestStringGenerator g =
ExtensionLoader.instantiateExtension(TestStringGenerator.class,
TestStringGeneratorImpl.class.getName());
Assertions.assertEquals("test", g.generateTestString());
}
+ // --- allowlist tests ---
+
+ /**
+ * Classes in opennlp.* are allowed by default — no registration needed.
+ */
+ @Test
+ void testBuiltinOpennlpPackageAllowedByDefault() {
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * A class outside opennlp.* is rejected without registration.
+ * This is the core security invariant — untrusted class names from model
+ * manifests must not reach Class.forName() without an explicit allowlist
entry.
+ */
+ @Test
+ void testUnregisteredPackageIsRejected() {
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.example.exploit.MaliciousFactory"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"),
+ "exception message should mention allowed package");
+ }
+
+ /**
+ * Allowlist check runs before Class.forName() — even for non-existent
classes
+ * the error must be "not in an allowed package", never "could not be
located".
+ */
+ @Test
+ void testAllowlistGateRunsBeforeClassForName() {
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.example.DoesNotExistOnClasspath$$Probe"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"),
+ "allowlist must reject before Class.forName(); got: " +
ex.getMessage());
+ Assertions.assertFalse(ex.getMessage().contains("could not be located"),
+ "Class.forName() must not have been reached; got: " + ex.getMessage());
+ }
+
+ /**
+ * After registerAllowedPackage(), a class from that package is permitted.
+ */
+ @Test
+ void testRegisteredPackageIsAllowed() {
+ ExtensionLoader.registerAllowedPackage("opennlp.tools.util.ext");
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * Prefix collision: registering "com.acme" must not permit "com.acmeevil.*".
+ */
+ @Test
+ void testPrefixCollisionPrevented() {
+ ExtensionLoader.registerAllowedPackage("com.acme");
+
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.acmeevil.Exploit"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"));
+ }
+
+ /**
+ * registerAllowedPackage() rejects null and blank inputs.
+ */
+ @Test
+ void testRegisterAllowedPackageRejectsNullAndBlank() {
+ Assertions.assertThrows(NullPointerException.class,
+ () -> ExtensionLoader.registerAllowedPackage(null));
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> ExtensionLoader.registerAllowedPackage(""));
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> ExtensionLoader.registerAllowedPackage(" "));
+ }
+
+ // --- system property escape hatch tests ---
+
+ /**
+ * OPENNLP_EXT_ALLOWED_PACKAGES system property permits a custom package
+ * without requiring a registerAllowedPackage() call — the operational
escape hatch.
+ */
+ @Test
+ void testSystemPropertyAddsAllowedPackage() {
+ System.setProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY,
"opennlp.tools.util.ext");
+ ExtensionLoader.resetAllowedPackages(); // re-initialize from updated
system property
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * OPENNLP_EXT_ALLOWED_PACKAGES accepts multiple comma-separated prefixes.
+ */
+ @Test
+ void testSystemPropertyMultiplePackages() {
+ System.setProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY,
+ "com.example.nlp, opennlp.tools.util.ext");
+ ExtensionLoader.resetAllowedPackages();
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
Review Comment:
**TestStringGeneratorImpl lives in opennlp.tools.util.ext, which is already
admitted by the default opennlp. prefix.**
##########
opennlp-tools/src/test/java/opennlp/tools/util/ext/ExtensionLoaderTest.java:
##########
@@ -33,11 +33,152 @@ public String generateTestString() {
}
}
+ @AfterEach
+ void reset() {
+ System.clearProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY);
+ ExtensionLoader.resetAllowedPackages();
+ }
+
+ // --- existing test ---
+
@Test
void testLoadingStringGenerator() {
TestStringGenerator g =
ExtensionLoader.instantiateExtension(TestStringGenerator.class,
TestStringGeneratorImpl.class.getName());
Assertions.assertEquals("test", g.generateTestString());
}
+ // --- allowlist tests ---
+
+ /**
+ * Classes in opennlp.* are allowed by default — no registration needed.
+ */
+ @Test
+ void testBuiltinOpennlpPackageAllowedByDefault() {
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * A class outside opennlp.* is rejected without registration.
+ * This is the core security invariant — untrusted class names from model
+ * manifests must not reach Class.forName() without an explicit allowlist
entry.
+ */
+ @Test
+ void testUnregisteredPackageIsRejected() {
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.example.exploit.MaliciousFactory"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"),
+ "exception message should mention allowed package");
+ }
+
+ /**
+ * Allowlist check runs before Class.forName() — even for non-existent
classes
+ * the error must be "not in an allowed package", never "could not be
located".
+ */
+ @Test
+ void testAllowlistGateRunsBeforeClassForName() {
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.example.DoesNotExistOnClasspath$$Probe"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"),
+ "allowlist must reject before Class.forName(); got: " +
ex.getMessage());
+ Assertions.assertFalse(ex.getMessage().contains("could not be located"),
+ "Class.forName() must not have been reached; got: " + ex.getMessage());
+ }
+
+ /**
+ * After registerAllowedPackage(), a class from that package is permitted.
+ */
+ @Test
+ void testRegisteredPackageIsAllowed() {
+ ExtensionLoader.registerAllowedPackage("opennlp.tools.util.ext");
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * Prefix collision: registering "com.acme" must not permit "com.acmeevil.*".
+ */
+ @Test
+ void testPrefixCollisionPrevented() {
+ ExtensionLoader.registerAllowedPackage("com.acme");
+
+ ExtensionNotLoadedException ex = Assertions.assertThrows(
+ ExtensionNotLoadedException.class,
+ () -> ExtensionLoader.instantiateExtension(
+ TestStringGenerator.class,
+ "com.acmeevil.Exploit"));
+
+ Assertions.assertTrue(ex.getMessage().contains("not in an allowed
package"));
+ }
+
+ /**
+ * registerAllowedPackage() rejects null and blank inputs.
+ */
+ @Test
+ void testRegisterAllowedPackageRejectsNullAndBlank() {
+ Assertions.assertThrows(NullPointerException.class,
+ () -> ExtensionLoader.registerAllowedPackage(null));
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> ExtensionLoader.registerAllowedPackage(""));
+
+ Assertions.assertThrows(IllegalArgumentException.class,
+ () -> ExtensionLoader.registerAllowedPackage(" "));
+ }
+
+ // --- system property escape hatch tests ---
+
+ /**
+ * OPENNLP_EXT_ALLOWED_PACKAGES system property permits a custom package
+ * without requiring a registerAllowedPackage() call — the operational
escape hatch.
+ */
+ @Test
+ void testSystemPropertyAddsAllowedPackage() {
+ System.setProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY,
"opennlp.tools.util.ext");
+ ExtensionLoader.resetAllowedPackages(); // re-initialize from updated
system property
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
+ TestStringGeneratorImpl.class.getName()));
+ }
+
+ /**
+ * OPENNLP_EXT_ALLOWED_PACKAGES accepts multiple comma-separated prefixes.
+ */
+ @Test
+ void testSystemPropertyMultiplePackages() {
+ System.setProperty(ExtensionLoader.ALLOWED_PACKAGES_PROPERTY,
+ "com.example.nlp, opennlp.tools.util.ext");
+ ExtensionLoader.resetAllowedPackages();
+
+ Assertions.assertDoesNotThrow(() ->
+ ExtensionLoader.instantiateExtension(TestStringGenerator.class,
Review Comment:
To actually exercise the property path, these tests need a class name that
is not in opennlp.*. One shape:
- Set the property to java.lang (or similar).
- Call instantiateExtension(TestStringGenerator.class, "java.lang.String").
- Assert the thrown ExtensionNotLoadedException message is not "not in an
allowed package"; it will be an assignability/load failure because String isn't
a TestStringGenerator, which proves the allowlist gate let it through.
Or, symmetrically: same class name, assert rejection without the property
and acceptance (past the gate) with it. Either way, the test must distinguish
the property code path from the default code path, which these two currently
don't.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]