This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch simplify-request-id-validation-patterns in repository https://gitbox.apache.org/repos/asf/unomi.git
commit 33836927cc90abbb6c1d50246937bfe77103ab4e Author: Serge Huber <[email protected]> AuthorDate: Tue Aug 18 20:41:41 2026 +0200 Simplify identifier validation patterns to a single character class Replace the alternation-based identifier patterns ^(\w|[-_@\.]){0,60}$ with the equivalent single character class ^[\[email protected]]{0,60}$ in the request, event and item schemas. Both forms accept exactly the same identifiers, but the single character class evaluates in linear time whereas the alternation (where '_' matches both branches) can backtrack heavily on some inputs. Add SchemaPatternSafetyTest, which pins the accepted/rejected identifier space and fails if a shipped id pattern regresses to that shape. Co-Authored-By: Claude Opus 4.8 <[email protected]> --- .../META-INF/cxs/schemas/events/event.json | 10 +- .../resources/META-INF/cxs/schemas/items/item.json | 2 +- .../unomi/schema/impl/SchemaPatternSafetyTest.java | 116 +++++++++++++++++++++ .../META-INF/cxs/schemas/rest/requestIds.json | 6 +- 4 files changed, 125 insertions(+), 9 deletions(-) diff --git a/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/event.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/event.json index b4673a760..2e9f75c6c 100644 --- a/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/event.json +++ b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/events/event.json @@ -13,24 +13,24 @@ "properties" : { "eventType" : { "type" : "string", - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "profileId" : { "type" : [ "string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "sessionId" : { "type" : [ "string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "scope" : { "type" : [ "string"], "validateScope": true, - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "sourceId" : { "type" : [ "string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "persistent" : { "type" : "boolean" diff --git a/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/items/item.json b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/items/item.json index 4f46c3bef..e98b21763 100644 --- a/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/items/item.json +++ b/extensions/json-schema/services/src/main/resources/META-INF/cxs/schemas/items/item.json @@ -12,7 +12,7 @@ "properties" : { "itemId" : { "type" : ["null","string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$", + "pattern" : "^[\\[email protected]]{0,60}$", "description" : "The identifier for the item" }, "itemType" : { diff --git a/extensions/json-schema/services/src/test/java/org/apache/unomi/schema/impl/SchemaPatternSafetyTest.java b/extensions/json-schema/services/src/test/java/org/apache/unomi/schema/impl/SchemaPatternSafetyTest.java new file mode 100644 index 000000000..db9591779 --- /dev/null +++ b/extensions/json-schema/services/src/test/java/org/apache/unomi/schema/impl/SchemaPatternSafetyTest.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.unomi.schema.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Guards the identifier-validation patterns in the shipped schemas against a linear-time regression. + * + * These patterns run on identifiers taken from public tracking requests, so they must evaluate in + * linear time. A pattern with overlapping quantified alternatives (for example + * {@code ^(\w|[-_@\.]){0,60}$}, where {@code _} matches both branches) degrades to exponential + * backtracking on the java.util.regex engine, so a bounded id can pin a validation thread. This test + * fails if any shipped id pattern exhibits that behaviour, and also pins down the accepted/rejected + * identifier space so a "simplification" cannot quietly change validation semantics. + */ +public class SchemaPatternSafetyTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String[] SCHEMA_RESOURCES = { + "/META-INF/cxs/schemas/events/event.json", + "/META-INF/cxs/schemas/items/item.json" + }; + + private List<String> collectPatterns(String resource) throws Exception { + try (InputStream is = getClass().getResourceAsStream(resource)) { + assertTrue("Missing shipped schema resource " + resource, is != null); + List<String> patterns = new ArrayList<>(); + collectPatterns(MAPPER.readTree(is), patterns); + return patterns; + } + } + + private void collectPatterns(JsonNode node, List<String> patterns) { + if (node.isObject()) { + for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) { + Map.Entry<String, JsonNode> field = it.next(); + if ("pattern".equals(field.getKey()) && field.getValue().isTextual()) { + patterns.add(field.getValue().asText()); + } else { + collectPatterns(field.getValue(), patterns); + } + } + } else if (node.isArray()) { + for (JsonNode child : node) { + collectPatterns(child, patterns); + } + } + } + + @Test(timeout = 5000) + public void idPatternsAreLinearTimeAndKeepSemantics() throws Exception { + // The classic exponential-backtracking probe for the old id pattern: a run of characters that + // are valid under both alternatives, followed by one invalid character forcing full backtracking. + StringBuilder probe = new StringBuilder(); + for (int i = 0; i < 59; i++) { + probe.append('_'); + } + probe.append('!'); + + List<String> patterns = new ArrayList<>(); + for (String resource : SCHEMA_RESOURCES) { + patterns.addAll(collectPatterns(resource)); + } + assertFalse("Expected id patterns in the shipped schemas", patterns.isEmpty()); + + for (String patternString : patterns) { + Pattern pattern = Pattern.compile(patternString); + + // Must finish effectively instantly; the test-level timeout catches exponential backtracking. + assertFalse("Probe input must be rejected by " + patternString, + pattern.matcher(probe).matches()); + + // Previously accepted identifiers must still be accepted. + assertTrue(pattern.matcher("[email protected]").matches()); + assertTrue(pattern.matcher("").matches()); + assertTrue(pattern.matcher("a.b-c_d@e").matches()); + + // Previously rejected identifiers must still be rejected. + assertFalse(pattern.matcher("white space").matches()); + assertFalse(pattern.matcher("slash/id").matches()); + StringBuilder tooLong = new StringBuilder(); + for (int i = 0; i < 61; i++) { + tooLong.append('a'); + } + assertFalse(pattern.matcher(tooLong).matches()); + } + } +} diff --git a/rest/src/main/resources/META-INF/cxs/schemas/rest/requestIds.json b/rest/src/main/resources/META-INF/cxs/schemas/rest/requestIds.json index 366a79524..ec1cf3808 100644 --- a/rest/src/main/resources/META-INF/cxs/schemas/rest/requestIds.json +++ b/rest/src/main/resources/META-INF/cxs/schemas/rest/requestIds.json @@ -12,15 +12,15 @@ "properties": { "sessionId": { "type": ["null", "string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "profileId": { "type": ["null", "string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" }, "personaId" : { "type" : ["null", "string"], - "pattern" : "^(\\w|[-_@\\.]){0,60}$" + "pattern" : "^[\\[email protected]]{0,60}$" } } }
