Repository: networknt/json-schema-validator
Pull request #1079 (2024-07-01 11:42:31)

Add security considerations and mitigations
Add some details on security considerations and mitigations and some implementations.

codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1079?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) Report
Attention: Patch coverage is `93.75000%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 78.02%. Comparing base [(`48ca3c2`)](https://app.codecov.io/gh/networknt/json-schema-validator/commit/48ca3c22995e32797e9defdb8b77869f0d15caf3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) to head [(`46126f9`)](https://app.codecov.io/gh/networknt/json-schema-validator/commit/46126f98f7afb256ecfc4293361e398274cf0a1a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).
> Report is 42 commits behind head on master.

| [Files](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1079?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) | Patch % | Lines |
|---|---|---|
| [...tworknt/schema/resource/ClasspathSchemaLoader.java](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1079?src=pr&el=tree&filepath=src%2Fmain%2Fjava%2Fcom%2Fnetworknt%2Fschema%2Fresource%2FClasspathSchemaLoader.java&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt#diff-c3JjL21haW4vamF2YS9jb20vbmV0d29ya250L3NjaGVtYS9yZXNvdXJjZS9DbGFzc3BhdGhTY2hlbWFMb2FkZXIuamF2YQ==) | 83.33% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1079?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #1079      +/-   ##
============================================
- Coverage     78.90%   78.02%   -0.89%     
- Complexity     1965     2060      +95     
============================================
  Files           172      201      +29     
  Lines          6352     6875     +523     
  Branches       1255     1255              
============================================
+ Hits           5012     5364     +352     
- Misses          867     1032     +165     
- Partials        473      479       +6     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/networknt/json-schema-validator/pull/1079?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=networknt).


Patch:
diff --git a/README.md b/README.md
index 865009e..e9c668d 100644
--- a/README.md
+++ b/README.md
@@ -551,6 +551,16 @@ The earlier draft specifications contain less keywords that can potentially impa
 
 This does not mean that using a schema with a later draft specification will automatically cause a performance impact. For instance, the `properties` validator will perform checks to determine if annotations need to be collected, and checks if the meta-schema contains the `unevaluatedProperties` keyword and whether the `unevaluatedProperties` keyword exists adjacent the evaluation path.
 
+## Security Considerations
+
+The library assumes that the schemas being loaded are trusted. This security model assumes the use case where the schemas are bundled with the application on the classpath.
+
+| Issue                 | Description                                                                                                                                                                                                                                          | Mitigation
+|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------
+| Schema Loading        | The library by default will load schemas from the classpath and over the internet if needed.                                                                                                                                                         | A `DisallowSchemaLoader` can be configured to not allow schema retrieval. Alternatively an `AllowSchemaLoader` can be configured to restrict the retrieval IRIs that are allowed.
+| Schema Caching        | The library by default preloads and caches references when loading schemas. While there is a max nesting depth when preloading schemas it is still possible to construct a schema that has a fan out that consumes a lot of memory from the server.  | Set `cacheRefs` option in `SchemaValidatorsConfig` to false.
+| Regular Expressions   | The library does not validate if a given regular expression is susceptable to denial of service ([ReDoS](https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS)).                                                     | An `AllowRegularExpressionFactory` can be configured to perform validation on the regular expressions that are allowed.
+| Validation Errors     | The library by default attempts to return all validation errors. The use of applicators such as `allOf` with a large number of schemas may result in a large number of validation errors taking up memory.                                           | Set `failFast` option in `SchemaValidatorsConfig` to immediately return when the first error is encountered. The `OutputFormat.BOOLEAN` or `OutputFormat.FLAG` also can be used.
 
 ## [Quick Start](doc/quickstart.md)
 
diff --git a/src/main/java/com/networknt/schema/AbsoluteIri.java b/src/main/java/com/networknt/schema/AbsoluteIri.java
index 372807a..3c4075c 100644
--- a/src/main/java/com/networknt/schema/AbsoluteIri.java
+++ b/src/main/java/com/networknt/schema/AbsoluteIri.java
@@ -182,7 +182,7 @@ public class AbsoluteIri {
             return "";
         }
         // iri refers to root
-        int start = iri.indexOf(":");
+        int start = iri.indexOf(':');
         if (start == -1) {
             return "";
         } else {
diff --git a/src/main/java/com/networknt/schema/OneOfValidator.java b/src/main/java/com/networknt/schema/OneOfValidator.java
index d964def..1807305 100644
--- a/src/main/java/com/networknt/schema/OneOfValidator.java
+++ b/src/main/java/com/networknt/schema/OneOfValidator.java
@@ -16,13 +16,19 @@
 
 package com.networknt.schema;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.networknt.schema.utils.SetView;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.networknt.schema.utils.SetView;
 
 /**
  * {@link JsonValidator} for oneOf.
@@ -75,10 +81,15 @@ public class OneOfValidator extends BaseJsonValidator {
             if (this.validationContext.getConfig().isDiscriminatorKeywordEnabled()) {
                 DiscriminatorContext discriminatorContext = new DiscriminatorContext();
                 executionContext.enterDiscriminatorContext(discriminatorContext, instanceLocation);
-                
+
                 // check if discriminator present
                 discriminator = (DiscriminatorValidator) this.getParentSchema().getValidators().stream()
                         .filter(v -> "discriminator".equals(v.getKeyword())).findFirst().orElse(null);
+                if (discriminator != null) {
+                    // this is just to make the discriminator context active
+                    discriminatorContext.registerDiscriminator(discriminator.getSchemaLocation(),
+                            (ObjectNode) discriminator.getSchemaNode());
+                }
             }
             executionContext.setFailFast(false);
             for (JsonSchema schema : this.schemas) {
@@ -135,7 +146,8 @@ public class OneOfValidator extends BaseJsonValidator {
                         // found is null triggers on the correct schema
                         childErrors = new SetView<>();
                         childErrors.union(schemaErrors);
-                    } else if (currentDiscriminatorContext.isDiscriminatorIgnore()) {
+                    } else if (currentDiscriminatorContext.isDiscriminatorIgnore()
+                            || !currentDiscriminatorContext.isActive()) {
                         // This is the normal handling when discriminators aren't enabled
                         if (childErrors == null) {
                             childErrors = new SetView<>();
diff --git a/src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java b/src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java
new file mode 100644
index 0000000..b4bc84e
--- /dev/null
+++ b/src/main/java/com/networknt/schema/regex/AllowRegularExpressionFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema.regex;
+
+import java.util.function.Predicate;
+
+import com.networknt.schema.InvalidSchemaException;
+import com.networknt.schema.ValidationMessage;
+
+/**
+ * {@link RegularExpressionFactory} that allows regular expressions to be used.
+ */
+public class AllowRegularExpressionFactory implements RegularExpressionFactory {
+    private final RegularExpressionFactory delegate;
+    private final Predicate<String> allowed;
+
+    public AllowRegularExpressionFactory(RegularExpressionFactory delegate, Predicate<String> allowed) {
+        this.delegate = delegate;
+        this.allowed = allowed;
+    }
+
+    @Override
+    public RegularExpression getRegularExpression(String regex) {
+        if (this.allowed.test(regex)) {
+            // Allowed to delegate
+            return this.delegate.getRegularExpression(regex);
+        }
+        throw new InvalidSchemaException(ValidationMessage.builder()
+                .message("Regular expression ''{1}'' is not allowed to be used.").arguments(regex).build());
+    }
+}
diff --git a/src/main/java/com/networknt/schema/resource/AllowSchemaLoader.java b/src/main/java/com/networknt/schema/resource/AllowSchemaLoader.java
new file mode 100644
index 0000000..a996585
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/AllowSchemaLoader.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema.resource;
+
+import java.util.function.Predicate;
+
+import com.networknt.schema.AbsoluteIri;
+import com.networknt.schema.InvalidSchemaException;
+import com.networknt.schema.ValidationMessage;
+
+/**
+ * {@link SchemaLoader} that allows loading external resources.
+ */
+public class AllowSchemaLoader implements SchemaLoader {
+    private final Predicate<AbsoluteIri> allowed;
+
+    /**
+     * Constructor.
+     * 
+     * @param allowed the predicate to determine which external resource is allowed
+     *                to be loaded
+     */
+    public AllowSchemaLoader(Predicate<AbsoluteIri> allowed) {
+        this.allowed = allowed;
+    }
+
+    @Override
+    public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
+        if (this.allowed.test(absoluteIri)) {
+            // Allow to delegate to the next schema loader
+            return null;
+        }
+        throw new InvalidSchemaException(ValidationMessage.builder()
+                .message("Schema from ''{1}'' is not allowed to be loaded.").arguments(absoluteIri).build());
+    }
+}
diff --git a/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java b/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java
index 1807f1e..9cb41b9 100644
--- a/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java
+++ b/src/main/java/com/networknt/schema/resource/ClasspathSchemaLoader.java
@@ -17,6 +17,7 @@ package com.networknt.schema.resource;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
+import java.util.function.Supplier;
 
 import com.networknt.schema.AbsoluteIri;
 
@@ -24,6 +25,23 @@ import com.networknt.schema.AbsoluteIri;
  * Loads from classpath.
  */
 public class ClasspathSchemaLoader implements SchemaLoader {
+    private final Supplier<ClassLoader> classLoaderSource;
+
+    /**
+     * Constructor.
+     */
+    public ClasspathSchemaLoader() {
+        this(ClasspathSchemaLoader::getClassLoader);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param classLoaderSource the class loader source
+     */
+    public ClasspathSchemaLoader(Supplier<ClassLoader> classLoaderSource) {
+        this.classLoaderSource = classLoaderSource;
+    }
 
     @Override
     public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
@@ -35,19 +53,15 @@ public class ClasspathSchemaLoader implements SchemaLoader {
             name = iri.substring(9);
         }
         if (name != null) {
-            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
-            if (classLoader == null) {
-                classLoader = SchemaLoader.class.getClassLoader();
-            }
-            ClassLoader loader = classLoader;
+            ClassLoader classLoader = this.classLoaderSource.get();
             if (name.startsWith("//")) {
                 name = name.substring(2);
             }
             String resource = name;
             return () -> {
-                InputStream result = loader.getResourceAsStream(resource);
+                InputStream result = classLoader.getResourceAsStream(resource);
                 if (result == null) {
-                    result = loader.getResourceAsStream(resource.substring(1));
+                    result = classLoader.getResourceAsStream(resource.substring(1));
                 }
                 if (result == null) {
                     throw new FileNotFoundException(iri);
@@ -58,4 +72,11 @@ public class ClasspathSchemaLoader implements SchemaLoader {
         return null;
     }
 
+    protected static ClassLoader getClassLoader() {
+        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+        if (classLoader == null) {
+            classLoader = SchemaLoader.class.getClassLoader();
+        }
+        return classLoader;
+    }
 }
diff --git a/src/main/java/com/networknt/schema/resource/DisallowSchemaLoader.java b/src/main/java/com/networknt/schema/resource/DisallowSchemaLoader.java
new file mode 100644
index 0000000..e2d81d1
--- /dev/null
+++ b/src/main/java/com/networknt/schema/resource/DisallowSchemaLoader.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema.resource;
+
+import com.networknt.schema.AbsoluteIri;
+import com.networknt.schema.InvalidSchemaException;
+import com.networknt.schema.ValidationMessage;
+
+/**
+ * {@link SchemaLoader} that disallows loading external resources.
+ */
+public class DisallowSchemaLoader implements SchemaLoader {
+    private static DisallowSchemaLoader INSTANCE = new DisallowSchemaLoader();
+
+    /**
+     * Disallows loading schemas from external resources.
+     *
+     * @return the disallow schema loader
+     */
+    public static DisallowSchemaLoader getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Constructor.
+     */
+    private DisallowSchemaLoader() {
+    }
+
+    @Override
+    public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
+        throw new InvalidSchemaException(ValidationMessage.builder()
+                .message("Schema from ''{1}'' is not allowed to be loaded.").arguments(absoluteIri).build());
+    }
+}
diff --git a/src/main/java/com/networknt/schema/utils/JsonNodes.java b/src/main/java/com/networknt/schema/utils/JsonNodes.java
index f1f8947..3bfd9aa 100644
--- a/src/main/java/com/networknt/schema/utils/JsonNodes.java
+++ b/src/main/java/com/networknt/schema/utils/JsonNodes.java
@@ -67,7 +67,7 @@ public class JsonNodes {
     @SuppressWarnings("unchecked")
     public static <T extends JsonNode> T get(JsonNode node, Object propertyOrIndex) {
         JsonNode value = null;
-        if (propertyOrIndex instanceof Number) {
+        if (propertyOrIndex instanceof Number && node.isArray()) {
             value = node.get(((Number) propertyOrIndex).intValue());
         } else {
             value = node.get(propertyOrIndex.toString());


Test patch:
diff --git a/src/test/java/com/networknt/schema/OneOfValidatorTest.java b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
index 70b4f13..4fdb14a 100644
--- a/src/test/java/com/networknt/schema/OneOfValidatorTest.java
+++ b/src/test/java/com/networknt/schema/OneOfValidatorTest.java
@@ -284,4 +284,189 @@ public class OneOfValidatorTest {
                 + "}";
         assertFalse(schema.validate(example3, InputFormat.JSON, OutputFormat.BOOLEAN));
     }
+
+    /**
+     * Test for when the discriminator keyword is enabled but no discriminator is
+     * present in the schema. This should process as a normal oneOf and return the
+     * error messages.
+     */
+    @Test
+    void oneOfDiscriminatorEnabled() {
+        String schemaData = "{\r\n"
+                + "  \"oneOf\": [\r\n"
+                + "    {\r\n"
+                + "      \"type\": \"string\"\r\n"
+                + "    },\r\n"
+                + "    {\r\n"
+                + "      \"type\": \"number\"\r\n"
+                + "    }\r\n"
+                + "  ]\r\n"
+                + "}";
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
+                SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build());
+        String inputData = "{}";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertEquals(3, messages.size());
+    }
+
+    /**
+     * Standard case where the discriminator is in the same schema as oneOf.
+     * <p>
+     * Note that discriminators do not affect the validation result and can only
+     * affect the messages returned.
+     */
+    @Test
+    void oneOfDiscriminatorEnabledWithDiscriminator() {
+        String schemaData = "{\r\n"
+                + "  \"discriminator\": {\r\n"
+                + "    \"propertyName\": \"type\",\r\n"
+                + "    \"mapping\": {\r\n"
+                + "      \"string\": \"#/$defs/string\",\r\n"
+                + "      \"number\": \"#/$defs/number\"\r\n"
+                + "    }\r\n"
+                + "  },\r\n"
+                + "  \"oneOf\": [\r\n"
+                + "    {\r\n"
+                + "      \"$ref\": \"#/$defs/string\"\r\n"
+                + "    },\r\n"
+                + "    {\r\n"
+                + "      \"$ref\": \"#/$defs/number\"\r\n"
+                + "    }\r\n"
+                + "  ],\r\n"
+                + "  \"$defs\": {\r\n"
+                + "    \"string\": {\r\n"
+                + "      \"properties\": {\r\n"
+                + "        \"type\": {\r\n"
+                + "          \"type\": \"string\"\r\n"
+                + "        },\r\n"
+                + "        \"value\": {\r\n"
+                + "          \"type\": \"string\"\r\n"
+                + "        }\r\n"
+                + "      }\r\n"
+                + "    },\r\n"
+                + "    \"number\": {\r\n"
+                + "      \"properties\": {\r\n"
+                + "        \"type\": {\r\n"
+                + "          \"type\": \"string\"\r\n"
+                + "        },\r\n"
+                + "        \"value\": {\r\n"
+                + "          \"type\": \"number\"\r\n"
+                + "        }\r\n"
+                + "      }\r\n"
+                + "    }\r\n"
+                + "  }\r\n"
+                + "}";
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
+                SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build());
+        // Valid
+        String inputData = "{\r\n"
+                + "  \"type\": \"number\",\r\n"
+                + "  \"value\": 1\r\n"
+                + "}";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertEquals(0, messages.size());
+
+        // Invalid only 1 message returned for number
+        String inputData2 = "{\r\n"
+                + "  \"type\": \"number\",\r\n"
+                + "  \"value\": {}\r\n"
+                + "}";
+        Set<ValidationMessage> messages2 = schema.validate(inputData2, InputFormat.JSON);
+        assertEquals(2, messages2.size());
+
+        // Invalid both messages for string and object returned
+        JsonSchema schema2 = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
+                SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(false).build());
+        Set<ValidationMessage> messages3 = schema2.validate(inputData2, InputFormat.JSON);
+        assertEquals(3, messages3.size());
+    }
+
+    /**
+     * Subclass case where the discriminator is in an allOf inside one of the oneOf references.
+     * <p>
+     * Note that discriminators do not affect the validation result and can only
+     * affect the messages returned.
+     */
+    @Test
+    void oneOfDiscriminatorEnabledWithDiscriminatorInSubclass() {
+        String schemaData = "{\r\n"
+                + "  \"oneOf\": [\r\n"
+                + "    {\r\n"
+                + "      \"$ref\": \"#/$defs/string\"\r\n"
+                + "    },\r\n"
+                + "    {\r\n"
+                + "      \"$ref\": \"#/$defs/number\"\r\n"
+                + "    }\r\n"
+                + "  ],\r\n"
+                + "  \"$defs\": {\r\n"
+                + "    \"typed\": {\r\n"
+                + "      \"discriminator\": {\r\n"
+                + "        \"propertyName\": \"type\",\r\n"
+                + "        \"mapping\": {\r\n"
+                + "          \"string\": \"#/$defs/string\",\r\n"
+                + "          \"number\": \"#/$defs/number\"\r\n"
+                + "        }\r\n"
+                + "      }\r\n"
+                + "    },\r\n"
+                + "    \"string\": {\r\n"
+                + "      \"allOf\": [\r\n"
+                + "        {\r\n"
+                + "          \"$ref\": \"#/$defs/typed\"\r\n"
+                + "        },\r\n"
+                + "        {\r\n"
+                + "          \"properties\": {\r\n"
+                + "            \"type\": {\r\n"
+                + "              \"type\": \"string\"\r\n"
+                + "            },\r\n"
+                + "            \"value\": {\r\n"
+                + "              \"type\": \"string\"\r\n"
+                + "            }\r\n"
+                + "          }\r\n"
+                + "        }\r\n"
+                + "      ]\r\n"
+                + "    },\r\n"
+                + "    \"number\": {\r\n"
+                + "      \"allOf\": [\r\n"
+                + "        {\r\n"
+                + "          \"$ref\": \"#/$defs/typed\"\r\n"
+                + "        },\r\n"
+                + "        {\r\n"
+                + "          \"properties\": {\r\n"
+                + "            \"type\": {\r\n"
+                + "              \"type\": \"string\"\r\n"
+                + "            },\r\n"
+                + "            \"value\": {\r\n"
+                + "              \"type\": \"number\"\r\n"
+                + "            }\r\n"
+                + "          }\r\n"
+                + "        }\r\n"
+                + "      ]\r\n"
+                + "    }\r\n"
+                + "  }\r\n"
+                + "}";
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
+                SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(true).build());
+        // Valid
+        String inputData = "{\r\n"
+                + "  \"type\": \"number\",\r\n"
+                + "  \"value\": 1\r\n"
+                + "}";
+        Set<ValidationMessage> messages = schema.validate(inputData, InputFormat.JSON);
+        assertEquals(0, messages.size());
+
+        // Invalid only 1 message returned for number
+        String inputData2 = "{\r\n"
+                + "  \"type\": \"number\",\r\n"
+                + "  \"value\": {}\r\n"
+                + "}";
+        Set<ValidationMessage> messages2 = schema.validate(inputData2, InputFormat.JSON);
+        assertEquals(2, messages2.size());
+
+        // Invalid both messages for string and object returned
+        JsonSchema schema2 = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData,
+                SchemaValidatorsConfig.builder().discriminatorKeywordEnabled(false).build());
+        Set<ValidationMessage> messages3 = schema2.validate(inputData2, InputFormat.JSON);
+        assertEquals(3, messages3.size());
+    }
+
 }
diff --git a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java
index 43256d2..e00abb4 100644
--- a/src/test/java/com/networknt/schema/oas/OpenApi30Test.java
+++ b/src/test/java/com/networknt/schema/oas/OpenApi30Test.java
@@ -16,6 +16,7 @@
 package com.networknt.schema.oas;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import java.util.List;
 import java.util.Set;
@@ -64,4 +65,16 @@ class OpenApi30Test {
         assertEquals("required", list.get(1).getType());
         assertEquals("bark", list.get(1).getProperty());
     }
+
+    /**
+     * Tests that schema location with number in fragment can resolve.
+     */
+    @Test
+    void jsonPointerWithNumberInFragment() {
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V7, builder -> builder
+                .metaSchema(OpenApi30.getInstance()).defaultMetaSchemaIri(OpenApi30.getInstance().getIri()));
+        JsonSchema schema = factory.getSchema(SchemaLocation.of(
+                "classpath:schema/oas/3.0/petstore.yaml#/paths/~1pet/post/responses/200/content/application~1json/schema"));
+        assertNotNull(schema);
+    }
 }
diff --git a/src/test/java/com/networknt/schema/regex/AllowRegularExpressionFactoryTest.java b/src/test/java/com/networknt/schema/regex/AllowRegularExpressionFactoryTest.java
new file mode 100644
index 0000000..12f5d75
--- /dev/null
+++ b/src/test/java/com/networknt/schema/regex/AllowRegularExpressionFactoryTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema.regex;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InvalidSchemaException;
+
+/**
+ * Test for AllowRegularExpressionFactory.
+ */
+class AllowRegularExpressionFactoryTest {
+    @Test
+    void getRegularExpression() {
+        boolean called[] = { false };
+        RegularExpressionFactory delegate = (regex) -> {
+            called[0] = true;
+            return null;
+        };
+        String allowed = "testing";
+        RegularExpressionFactory factory = new AllowRegularExpressionFactory(delegate, allowed::equals);
+        InvalidSchemaException exception = assertThrows(InvalidSchemaException.class, () -> factory.getRegularExpression("hello"));
+        assertEquals("hello", exception.getValidationMessage().getArguments()[0]);
+        
+        assertDoesNotThrow(() -> factory.getRegularExpression(allowed));
+        assertTrue(called[0]);
+    }
+
+}
diff --git a/src/test/java/com/networknt/schema/resource/AllowSchemaLoaderTest.java b/src/test/java/com/networknt/schema/resource/AllowSchemaLoaderTest.java
new file mode 100644
index 0000000..c8a6a8b
--- /dev/null
+++ b/src/test/java/com/networknt/schema/resource/AllowSchemaLoaderTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema.resource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InvalidSchemaException;
+import com.networknt.schema.JsonSchema;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Test for AllowSchemaLoader.
+ */
+class AllowSchemaLoaderTest {
+
+    @Test
+    void integration() {
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012,
+                builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders
+                        .add(new AllowSchemaLoader(iri -> iri.toString().startsWith("classpath:")))));
+        InvalidSchemaException invalidSchemaException = assertThrows(InvalidSchemaException.class,
+                () -> factory.getSchema(SchemaLocation.of("http://www.example.org/schema")));
+        assertEquals("http://www.example.org/schema",
+                invalidSchemaException.getValidationMessage().getArguments()[0].toString());
+        JsonSchema schema = factory.getSchema(SchemaLocation.of("classpath:schema/example-main.json"));
+        assertNotNull(schema);
+    }
+
+}
diff --git a/src/test/java/com/networknt/schema/resource/DisallowSchemaLoaderTest.java b/src/test/java/com/networknt/schema/resource/DisallowSchemaLoaderTest.java
new file mode 100644
index 0000000..c226a86
--- /dev/null
+++ b/src/test/java/com/networknt/schema/resource/DisallowSchemaLoaderTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.networknt.schema.resource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import com.networknt.schema.InvalidSchemaException;
+import com.networknt.schema.JsonSchemaFactory;
+import com.networknt.schema.SchemaLocation;
+import com.networknt.schema.SpecVersion.VersionFlag;
+
+/**
+ * Test for DisallowSchemaLoader.
+ */
+class DisallowSchemaLoaderTest {
+
+    @Test
+    void integration() {
+        JsonSchemaFactory factory = JsonSchemaFactory.getInstance(VersionFlag.V202012, builder -> builder
+                .schemaLoaders(schemaLoaders -> schemaLoaders.add(DisallowSchemaLoader.getInstance())));
+        InvalidSchemaException invalidSchemaException = assertThrows(InvalidSchemaException.class,
+                () -> factory.getSchema(SchemaLocation.of("classpath:schema/example-main.json")));
+        assertEquals("classpath:schema/example-main.json",
+                invalidSchemaException.getValidationMessage().getArguments()[0].toString());
+    }
+
+}