From 03d44967ed0bec808baed09c3f55d9e97a3466fe Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Wed, 10 Jun 2026 08:27:36 -0400 Subject: [PATCH 1/7] fixes #1174 TextNodes as schema seem to validate any value --- .../com/networknt/schema/SchemaRegistry.java | 35 +++++++++++- .../com/networknt/schema/Issue1174Test.java | 55 +++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/test/java/com/networknt/schema/Issue1174Test.java diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index 38ed062b..780457f4 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -469,9 +469,11 @@ public SchemaLoader getSchemaLoader() { * @return the schema */ protected Schema newSchema(SchemaLocation schemaUri, JsonNode schemaNode) { + SchemaLocation schemaLocation = getSchemaLocation(schemaUri); + validateSchemaNodeNotNull(schemaLocation, schemaNode); final SchemaContext schemaContext = createSchemaContext(schemaNode); - Schema jsonSchema = doCreate(schemaContext, getSchemaLocation(schemaUri), - schemaNode, null, false); + validateSchemaNodeType(schemaLocation, schemaNode, schemaContext); + Schema jsonSchema = doCreate(schemaContext, schemaLocation, schemaNode, null, false); preload(jsonSchema); return jsonSchema; } @@ -507,10 +509,37 @@ public Schema create(SchemaContext schemaContext, SchemaLocation schemaLocation, private Schema doCreate(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, boolean suppressSubSchemaRetrieval) { - return Schema.from(withDialect(schemaContext, schemaNode), schemaLocation, schemaNode, + validateSchemaNodeNotNull(schemaLocation, schemaNode); + SchemaContext schemaContextToUse = withDialect(schemaContext, schemaNode); + return Schema.from(schemaContextToUse, schemaLocation, schemaNode, parentSchema, suppressSubSchemaRetrieval); } + private void validateSchemaNodeNotNull(SchemaLocation schemaLocation, JsonNode schemaNode) { + if (schemaNode == null) { + throw new SchemaException("Schema at " + schemaLocation + " must not be null"); + } + } + + private void validateSchemaNodeType(SchemaLocation schemaLocation, JsonNode schemaNode, + SchemaContext schemaContext) { + if (schemaNode.isObject()) { + return; + } + if (schemaNode.isBoolean() && supportsBooleanSchema(schemaContext)) { + return; + } + String expected = supportsBooleanSchema(schemaContext) ? "object or boolean" : "object"; + throw new SchemaException("Schema at " + schemaLocation + " must be " + expected + " but was " + + schemaNode.getNodeType()); + } + + private boolean supportsBooleanSchema(SchemaContext schemaContext) { + return schemaContext != null + && schemaContext.getDialect().getSpecificationVersion().getOrder() >= SpecificationVersion.DRAFT_6 + .getOrder(); + } + /** * Determines the schema context to use for the schema given the parent schema * context. diff --git a/src/test/java/com/networknt/schema/Issue1174Test.java b/src/test/java/com/networknt/schema/Issue1174Test.java new file mode 100644 index 00000000..86b881dc --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue1174Test.java @@ -0,0 +1,55 @@ +package com.networknt.schema; + +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 java.util.List; + +import org.junit.jupiter.api.Test; + +import tools.jackson.databind.node.JsonNodeFactory; + +class Issue1174Test { + @Test + void textNodeShouldNotBeAcceptedAsRootSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + + SchemaException exception = assertThrows(SchemaException.class, + () -> registry.getSchema(JsonNodeFactory.instance.textNode("false"))); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + + @Test + void textNodeContainingJsonShouldNotBeAcceptedAsRootSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + + SchemaException exception = assertThrows(SchemaException.class, + () -> registry.getSchema(JsonNodeFactory.instance.textNode("{\"type\":\"string\"}"))); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + + @Test + void booleanSchemaShouldBeAcceptedForDraft7() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + Schema schema = registry.getSchema("false"); + + List errors = schema.validate("42", InputFormat.JSON); + + assertEquals(1, errors.size()); + } + + @Test + void booleanSchemaShouldNotBeAcceptedForDraft4() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); + + SchemaException exception = assertThrows(SchemaException.class, () -> registry.getSchema("false")); + + assertTrue(exception.getMessage().contains("must be object")); + assertTrue(exception.getMessage().contains("BOOLEAN")); + } +} From 116e818aea776f2b6cc7ab2e534cf805fff6f5ed Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Thu, 11 Jun 2026 13:24:36 -0400 Subject: [PATCH 2/7] Address loaded schema validation for issue 1174 --- .../com/networknt/schema/SchemaRegistry.java | 1 + .../com/networknt/schema/Issue1174Test.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index 780457f4..e78464db 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -686,6 +686,7 @@ public Schema getSchema(final InputStream schemaStream, InputFormat inputFormat) */ public Schema getSchema(final SchemaLocation schemaUri) { Schema schema = loadSchema(schemaUri); + validateSchemaNodeType(schema.getSchemaLocation(), schema.getSchemaNode(), schema.getSchemaContext()); preload(schema); return schema; } diff --git a/src/test/java/com/networknt/schema/Issue1174Test.java b/src/test/java/com/networknt/schema/Issue1174Test.java index 86b881dc..7745c58e 100644 --- a/src/test/java/com/networknt/schema/Issue1174Test.java +++ b/src/test/java/com/networknt/schema/Issue1174Test.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; @@ -33,6 +34,18 @@ void textNodeContainingJsonShouldNotBeAcceptedAsRootSchema() { assertTrue(exception.getMessage().contains("STRING")); } + @Test + void textNodeShouldNotBeAcceptedAsLoadedRootSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, + builder -> builder.schemas(Collections.singletonMap("https://www.example.org/text-schema", "\"false\""))); + + SchemaException exception = assertThrows(SchemaException.class, + () -> registry.getSchema(SchemaLocation.of("https://www.example.org/text-schema"))); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + @Test void booleanSchemaShouldBeAcceptedForDraft7() { SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); @@ -43,6 +56,17 @@ void booleanSchemaShouldBeAcceptedForDraft7() { assertEquals(1, errors.size()); } + @Test + void loadedBooleanSchemaShouldBeAcceptedForDraft7() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, + builder -> builder.schemas(Collections.singletonMap("https://www.example.org/false-schema", "false"))); + Schema schema = registry.getSchema(SchemaLocation.of("https://www.example.org/false-schema")); + + List errors = schema.validate("42", InputFormat.JSON); + + assertEquals(1, errors.size()); + } + @Test void booleanSchemaShouldNotBeAcceptedForDraft4() { SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_4); From b510def14fb4a6a7981099d553585dfb3436a906 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Thu, 11 Jun 2026 14:07:22 -0400 Subject: [PATCH 3/7] Address schema node validation review comments --- .../com/networknt/schema/SchemaRegistry.java | 39 +++++++++++++++++-- .../schema/keyword/RefValidator.java | 14 +++---- .../schema/DependentRequiredTest.java | 6 +-- .../com/networknt/schema/Issue1174Test.java | 38 ++++++++++++++++++ .../java/com/networknt/schema/SchemaTest.java | 12 +++--- .../schema/UnknownMetaSchemaTest.java | 6 +-- src/test/resources/draft2020-12/issue656.json | 13 +++---- src/test/resources/schema/issue456-v7.json | 4 +- 8 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index e78464db..8aca66c0 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -509,8 +509,17 @@ public Schema create(SchemaContext schemaContext, SchemaLocation schemaLocation, private Schema doCreate(SchemaContext schemaContext, SchemaLocation schemaLocation, JsonNode schemaNode, Schema parentSchema, boolean suppressSubSchemaRetrieval) { + return doCreate(schemaContext, schemaLocation, schemaNode, parentSchema, suppressSubSchemaRetrieval, true); + } + + private Schema doCreate(SchemaContext schemaContext, SchemaLocation schemaLocation, + JsonNode schemaNode, Schema parentSchema, boolean suppressSubSchemaRetrieval, + boolean validateSchemaNodeType) { validateSchemaNodeNotNull(schemaLocation, schemaNode); SchemaContext schemaContextToUse = withDialect(schemaContext, schemaNode); + if (validateSchemaNodeType) { + validateSchemaNodeType(schemaLocation, schemaNode, schemaContextToUse); + } return Schema.from(schemaContextToUse, schemaLocation, schemaNode, parentSchema, suppressSubSchemaRetrieval); } @@ -752,6 +761,20 @@ public Schema getSchema(final JsonNode jsonNode) { * @return the schema */ public Schema loadSchema(final SchemaLocation schemaUri) { + return loadSchema(schemaUri, true); + } + + /** + * Loads the schema. + * + * @param schemaUri the absolute IRI of the schema which can map to the + * retrieval IRI. + * @param validateLoadedSchema true to validate that the loaded node is a schema; + * false when loading a document container to resolve a + * fragment before using it as a schema + * @return the schema + */ + public Schema loadSchema(final SchemaLocation schemaUri, boolean validateLoadedSchema) { if (schemaCacheEnabled) { // ConcurrentHashMap computeIfAbsent does not allow calls that result in a // recursive update to the map. @@ -761,19 +784,27 @@ public Schema loadSchema(final SchemaLocation schemaUri) { synchronized (this) { // acquire lock on shared registry object to prevent deadlock cachedUriSchema = schemaCache.get(schemaUri); if (cachedUriSchema == null) { - cachedUriSchema = getMappedSchema(schemaUri); + cachedUriSchema = getMappedSchema(schemaUri, validateLoadedSchema); if (cachedUriSchema != null) { schemaCache.put(schemaUri, cachedUriSchema); } } } } + if (validateLoadedSchema && cachedUriSchema != null) { + validateSchemaNodeType(cachedUriSchema.getSchemaLocation(), cachedUriSchema.getSchemaNode(), + cachedUriSchema.getSchemaContext()); + } return cachedUriSchema; } - return getMappedSchema(schemaUri); + return getMappedSchema(schemaUri, validateLoadedSchema); } protected Schema getMappedSchema(final SchemaLocation schemaUri) { + return getMappedSchema(schemaUri, true); + } + + protected Schema getMappedSchema(final SchemaLocation schemaUri, boolean validateLoadedSchema) { InputStreamSource inputStreamSource = this.schemaLoader.getSchemaResource(schemaUri.getAbsoluteIri()); if (inputStreamSource != null) { try (InputStream inputStream = inputStreamSource.getInputStream()) { @@ -792,13 +823,13 @@ protected Schema getMappedSchema(final SchemaLocation schemaUri) { // Schema without fragment SchemaContext schemaContext = new SchemaContext(dialect, this); return doCreate(schemaContext, schemaUri, schemaNode, null, - true /* retrieved via id, resolving will not change anything */); + true /* retrieved via id, resolving will not change anything */, validateLoadedSchema); } else { // Schema with fragment pointing to sub schema final SchemaContext schemaContext = createSchemaContext(schemaNode); SchemaLocation documentLocation = new SchemaLocation(schemaUri.getAbsoluteIri()); Schema document = doCreate(schemaContext, documentLocation, schemaNode, null, - false); + false, false); return document.getRefSchema(schemaUri.getFragment()); } } catch (IOException e) { diff --git a/src/main/java/com/networknt/schema/keyword/RefValidator.java b/src/main/java/com/networknt/schema/keyword/RefValidator.java index a769399a..6926a8e8 100644 --- a/src/main/java/com/networknt/schema/keyword/RefValidator.java +++ b/src/main/java/com/networknt/schema/keyword/RefValidator.java @@ -66,13 +66,13 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, SchemaLocation schemaLocation = SchemaLocation.of(schemaUriFinal); // This should retrieve schemas regardless of the protocol that is in the uri. return new SchemaRef(getSupplier(() -> { - Schema schemaResource = schemaContext.getSchemaResources().get(schemaUriFinal); - if (schemaResource == null) { - schemaResource = schemaContext.getSchemaRegistry().loadSchema(schemaLocation); - if (schemaResource != null) { - copySchemaResources(schemaContext, schemaResource); - } - } + Schema schemaResource = schemaContext.getSchemaResources().get(schemaUriFinal); + if (schemaResource == null) { + schemaResource = schemaContext.getSchemaRegistry().loadSchema(schemaLocation, index < 0); + if (schemaResource != null) { + copySchemaResources(schemaContext, schemaResource); + } + } if (index < 0) { if (schemaResource == null) { return null; diff --git a/src/test/java/com/networknt/schema/DependentRequiredTest.java b/src/test/java/com/networknt/schema/DependentRequiredTest.java index 55a68b35..bc39ff6e 100644 --- a/src/test/java/com/networknt/schema/DependentRequiredTest.java +++ b/src/test/java/com/networknt/schema/DependentRequiredTest.java @@ -19,8 +19,8 @@ class DependentRequiredTest { " \"$schema\":\"https://json-schema.org/draft/2019-09/schema\"," + " \"type\": \"object\"," + " \"properties\": {" + - " \"optional\": \"string\"," + - " \"requiredWhenOptionalPresent\": \"string\"" + + " \"optional\": { \"type\": \"string\" }," + + " \"requiredWhenOptionalPresent\": { \"type\": \"string\" }" + " }," + " \"dependentRequired\": {" + " \"optional\": [ \"requiredWhenOptionalPresent\" ]," + @@ -63,4 +63,4 @@ private static List whenValidate(String content) throws JacksonException return schema.validate(mapper.readTree(content)); } -} \ No newline at end of file +} diff --git a/src/test/java/com/networknt/schema/Issue1174Test.java b/src/test/java/com/networknt/schema/Issue1174Test.java index 7745c58e..5f6b81b0 100644 --- a/src/test/java/com/networknt/schema/Issue1174Test.java +++ b/src/test/java/com/networknt/schema/Issue1174Test.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2026 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; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,6 +61,29 @@ void textNodeShouldNotBeAcceptedAsLoadedRootSchema() { assertTrue(exception.getMessage().contains("STRING")); } + @Test + void textNodeShouldNotBeAcceptedAsReferencedRootSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, + builder -> builder.schemas(Collections.singletonMap("https://www.example.org/text-schema", "\"false\""))); + Schema schema = registry.getSchema("{\"$ref\":\"https://www.example.org/text-schema\"}"); + + SchemaException exception = assertThrows(SchemaException.class, () -> schema.validate("42", InputFormat.JSON)); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + + @Test + void textNodeShouldNotBeAcceptedAsSubSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); + + SchemaException exception = assertThrows(SchemaException.class, + () -> registry.getSchema("{\"type\":\"object\",\"properties\":{\"a\":\"false\"}}")); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + @Test void booleanSchemaShouldBeAcceptedForDraft7() { SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); diff --git a/src/test/java/com/networknt/schema/SchemaTest.java b/src/test/java/com/networknt/schema/SchemaTest.java index ff1ac400..2220f489 100644 --- a/src/test/java/com/networknt/schema/SchemaTest.java +++ b/src/test/java/com/networknt/schema/SchemaTest.java @@ -44,12 +44,12 @@ void concurrency() throws Exception { + " \"name\": {\r\n" + " \"type\": \"string\",\r\n" + " \"description\": \"The name\"\r\n" - + " },\r\n" - + " \"required\": [\r\n" - + " \"name\"\r\n" - + " ]\r\n" - + " }\r\n" - + "}"; + + " }\r\n" + + " },\r\n" + + " \"required\": [\r\n" + + " \"name\"\r\n" + + " ]\r\n" + + "}"; String inputData = "{\r\n" + " \"name\": 1\r\n" + "}"; diff --git a/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java index f6858446..c7f7aa9b 100644 --- a/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java +++ b/src/test/java/com/networknt/schema/UnknownMetaSchemaTest.java @@ -12,9 +12,9 @@ class UnknownMetaSchemaTest { - private final String schema1 = "{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; - private final String schema2 = "{\"$schema\":\"https://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; - private final String schema3 = "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"},\"required\":[\"data\"]}}"; + private final String schema1 = "{\"$schema\":\"http://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"}},\"required\":[\"data\"]}"; + private final String schema2 = "{\"$schema\":\"https://json-schema.org/draft-07/schema\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"}},\"required\":[\"data\"]}"; + private final String schema3 = "{\"$schema\":\"http://json-schema.org/draft-07/schema#\",\"title\":\"thingModel\",\"description\":\"description of thing\",\"type\":\"object\",\"properties\":{\"data\":{\"type\":\"integer\"}},\"required\":[\"data\"]}"; private final String json = "{\"data\":1}"; diff --git a/src/test/resources/draft2020-12/issue656.json b/src/test/resources/draft2020-12/issue656.json index fc012ce0..7923214b 100644 --- a/src/test/resources/draft2020-12/issue656.json +++ b/src/test/resources/draft2020-12/issue656.json @@ -85,7 +85,9 @@ "type": "string", "format": "date" }, - "frequency": "integer" + "frequency": { + "type": "integer" + } }, "required": [ "drug", @@ -109,12 +111,7 @@ }, "discharge-instructions": { "type": "string" - }, - "required": [ - "surgery-date", - "discharge-instructions" - ], - "unevaluatedProperties": false + } } } } @@ -160,4 +157,4 @@ } ] } -] \ No newline at end of file +] diff --git a/src/test/resources/schema/issue456-v7.json b/src/test/resources/schema/issue456-v7.json index 46fbe089..f31dd560 100644 --- a/src/test/resources/schema/issue456-v7.json +++ b/src/test/resources/schema/issue456-v7.json @@ -5,7 +5,9 @@ "description": "Test description", "type": "object", "properties": { - "id": "string", + "id": { + "type": "string" + }, "details": { "oneOf": [ { From 3e9ec3ee1164cae51d231f678867e0f6458263bc Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Thu, 11 Jun 2026 14:31:38 -0400 Subject: [PATCH 4/7] Preserve mapped schema override path --- src/main/java/com/networknt/schema/SchemaRegistry.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index 8aca66c0..f9fe44ce 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -784,7 +784,8 @@ public Schema loadSchema(final SchemaLocation schemaUri, boolean validateLoadedS synchronized (this) { // acquire lock on shared registry object to prevent deadlock cachedUriSchema = schemaCache.get(schemaUri); if (cachedUriSchema == null) { - cachedUriSchema = getMappedSchema(schemaUri, validateLoadedSchema); + cachedUriSchema = validateLoadedSchema ? getMappedSchema(schemaUri) + : getMappedSchema(schemaUri, false); if (cachedUriSchema != null) { schemaCache.put(schemaUri, cachedUriSchema); } @@ -797,7 +798,7 @@ public Schema loadSchema(final SchemaLocation schemaUri, boolean validateLoadedS } return cachedUriSchema; } - return getMappedSchema(schemaUri, validateLoadedSchema); + return validateLoadedSchema ? getMappedSchema(schemaUri) : getMappedSchema(schemaUri, false); } protected Schema getMappedSchema(final SchemaLocation schemaUri) { From b1e95873267f40367bcbacd6f66c8b83f8627431 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Thu, 11 Jun 2026 14:42:58 -0400 Subject: [PATCH 5/7] Validate referenced document fragment schemas --- .../com/networknt/schema/keyword/RefValidator.java | 12 +++++++----- .../java/com/networknt/schema/Issue1174Test.java | 12 ++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/networknt/schema/keyword/RefValidator.java b/src/main/java/com/networknt/schema/keyword/RefValidator.java index 6926a8e8..0ceb1e64 100644 --- a/src/main/java/com/networknt/schema/keyword/RefValidator.java +++ b/src/main/java/com/networknt/schema/keyword/RefValidator.java @@ -62,13 +62,15 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, // This will determine the correct absolute uri for the refUri. This decision will take into // account the current uri of the parent schema. - String schemaUriFinal = resolve(parentSchema, refUri); - SchemaLocation schemaLocation = SchemaLocation.of(schemaUriFinal); - // This should retrieve schemas regardless of the protocol that is in the uri. - return new SchemaRef(getSupplier(() -> { + String schemaUriFinal = resolve(parentSchema, refUri); + SchemaLocation schemaLocation = SchemaLocation.of(schemaUriFinal); + boolean validateLoadedSchema = index < 0 + || SchemaLocation.Fragment.isDocumentFragment(refValue.substring(index)); + // This should retrieve schemas regardless of the protocol that is in the uri. + return new SchemaRef(getSupplier(() -> { Schema schemaResource = schemaContext.getSchemaResources().get(schemaUriFinal); if (schemaResource == null) { - schemaResource = schemaContext.getSchemaRegistry().loadSchema(schemaLocation, index < 0); + schemaResource = schemaContext.getSchemaRegistry().loadSchema(schemaLocation, validateLoadedSchema); if (schemaResource != null) { copySchemaResources(schemaContext, schemaResource); } diff --git a/src/test/java/com/networknt/schema/Issue1174Test.java b/src/test/java/com/networknt/schema/Issue1174Test.java index 5f6b81b0..8a0db511 100644 --- a/src/test/java/com/networknt/schema/Issue1174Test.java +++ b/src/test/java/com/networknt/schema/Issue1174Test.java @@ -73,6 +73,18 @@ void textNodeShouldNotBeAcceptedAsReferencedRootSchema() { assertTrue(exception.getMessage().contains("STRING")); } + @Test + void textNodeShouldNotBeAcceptedAsReferencedDocumentFragmentSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, + builder -> builder.schemas(Collections.singletonMap("https://www.example.org/text-schema", "\"false\""))); + Schema schema = registry.getSchema("{\"$ref\":\"https://www.example.org/text-schema#\"}"); + + SchemaException exception = assertThrows(SchemaException.class, () -> schema.validate("42", InputFormat.JSON)); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + @Test void textNodeShouldNotBeAcceptedAsSubSchema() { SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7); From 4587ac5adbaa99c7979fd884bbad3d5cc7a0c009 Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Thu, 11 Jun 2026 15:02:24 -0400 Subject: [PATCH 6/7] Remove duplicate schema type validation --- src/main/java/com/networknt/schema/SchemaRegistry.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/networknt/schema/SchemaRegistry.java b/src/main/java/com/networknt/schema/SchemaRegistry.java index f9fe44ce..a4e4a9df 100644 --- a/src/main/java/com/networknt/schema/SchemaRegistry.java +++ b/src/main/java/com/networknt/schema/SchemaRegistry.java @@ -472,7 +472,6 @@ protected Schema newSchema(SchemaLocation schemaUri, JsonNode schemaNode) { SchemaLocation schemaLocation = getSchemaLocation(schemaUri); validateSchemaNodeNotNull(schemaLocation, schemaNode); final SchemaContext schemaContext = createSchemaContext(schemaNode); - validateSchemaNodeType(schemaLocation, schemaNode, schemaContext); Schema jsonSchema = doCreate(schemaContext, schemaLocation, schemaNode, null, false); preload(jsonSchema); return jsonSchema; From ed04ee9eff93548bf172ba739856cc730d67828a Mon Sep 17 00:00:00 2001 From: Steve Hu Date: Thu, 11 Jun 2026 15:12:41 -0400 Subject: [PATCH 7/7] Validate loaded schemas for anchor refs --- .../schema/keyword/RefValidator.java | 20 +++++++++---------- .../com/networknt/schema/Issue1174Test.java | 12 +++++++++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/networknt/schema/keyword/RefValidator.java b/src/main/java/com/networknt/schema/keyword/RefValidator.java index 0ceb1e64..b8fe4c19 100644 --- a/src/main/java/com/networknt/schema/keyword/RefValidator.java +++ b/src/main/java/com/networknt/schema/keyword/RefValidator.java @@ -60,12 +60,12 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, refUri = refValue; } - // This will determine the correct absolute uri for the refUri. This decision will take into - // account the current uri of the parent schema. + // This will determine the correct absolute uri for the refUri. This decision will take into + // account the current uri of the parent schema. String schemaUriFinal = resolve(parentSchema, refUri); SchemaLocation schemaLocation = SchemaLocation.of(schemaUriFinal); - boolean validateLoadedSchema = index < 0 - || SchemaLocation.Fragment.isDocumentFragment(refValue.substring(index)); + String fragment = index < 0 ? null : refValue.substring(index); + boolean validateLoadedSchema = fragment == null || !SchemaLocation.Fragment.isJsonPointerFragment(fragment); // This should retrieve schemas regardless of the protocol that is in the uri. return new SchemaRef(getSupplier(() -> { Schema schemaResource = schemaContext.getSchemaResources().get(schemaUriFinal); @@ -78,12 +78,12 @@ static SchemaRef getRefSchema(Schema parentSchema, SchemaContext schemaContext, if (index < 0) { if (schemaResource == null) { return null; - } - return schemaResource; - } else { - String newRefValue = refValue.substring(index); - String find = schemaLocation.getAbsoluteIri() + newRefValue; - Schema findSchemaResource = schemaContext.getSchemaResources().get(find); + } + return schemaResource; + } else { + String newRefValue = fragment; + String find = schemaLocation.getAbsoluteIri() + newRefValue; + Schema findSchemaResource = schemaContext.getSchemaResources().get(find); if (findSchemaResource == null) { findSchemaResource = schemaContext.getDynamicAnchors().get(find); } diff --git a/src/test/java/com/networknt/schema/Issue1174Test.java b/src/test/java/com/networknt/schema/Issue1174Test.java index 8a0db511..7e1d6225 100644 --- a/src/test/java/com/networknt/schema/Issue1174Test.java +++ b/src/test/java/com/networknt/schema/Issue1174Test.java @@ -85,6 +85,18 @@ void textNodeShouldNotBeAcceptedAsReferencedDocumentFragmentSchema() { assertTrue(exception.getMessage().contains("STRING")); } + @Test + void textNodeShouldNotBeAcceptedAsReferencedAnchorSchema() { + SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7, + builder -> builder.schemas(Collections.singletonMap("https://www.example.org/text-schema", "\"false\""))); + Schema schema = registry.getSchema("{\"$ref\":\"https://www.example.org/text-schema#myAnchor\"}"); + + SchemaException exception = assertThrows(SchemaException.class, () -> schema.validate("42", InputFormat.JSON)); + + assertTrue(exception.getMessage().contains("must be object or boolean")); + assertTrue(exception.getMessage().contains("STRING")); + } + @Test void textNodeShouldNotBeAcceptedAsSubSchema() { SchemaRegistry registry = SchemaRegistry.withDefaultDialect(SpecificationVersion.DRAFT_7);