This is an automated email from the ASF dual-hosted git repository. luigidemasi pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-kit.git
commit 80f57109bd3d20c90deec53c6f4e3e0a81d0e818 Author: Jinal Sarvaiya <[email protected]> AuthorDate: Fri Nov 6 14:39:10 2026 +0000 fix #100: fail-closed on malformed inline JSON and array-wrapped envelopes --- .../camel/kit/ship/controller/ShipStageResult.java | 19 +++++++++- .../kit/ship/controller/ShipStageResultTest.java | 44 ++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java index 71628e01..614c0bf0 100644 --- a/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java +++ b/kit/camel-kit-core/src/main/java/org/apache/camel/kit/ship/controller/ShipStageResult.java @@ -408,11 +408,26 @@ record ShipStageResult( } private static boolean looksLikeEnvelope(String span) { + String trimmed = span.trim(); + if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) { + return false; + } try { JsonNode node = JSON.readTree(span); - return node.isObject() && node.has("materialAmbiguity"); - } catch (JsonProcessingException e) { + if (node.isObject()) { + return node.has("materialAmbiguity"); + } + if (node.isArray()) { + for (JsonNode element : node) { + if (element.isObject() && element.has("materialAmbiguity")) { + return true; + } + } + } return false; + } catch (JsonProcessingException e) { + // fail-closed: unparseable JSON starting with {/[ is suspicious + return true; } } diff --git a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java index f7d27a63..10f9eea8 100644 --- a/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java +++ b/kit/camel-kit-core/src/test/java/org/apache/camel/kit/ship/controller/ShipStageResultTest.java @@ -536,6 +536,50 @@ class ShipStageResultTest { "backtick-wrapped correction envelope with escaped key"); } + @Test + void rejectsInlineBacktickWithDuplicateKey() throws Exception { + ObjectNode fenced = result(null, null); + ObjectNode bare = result(null, null); + bare.put("materialAmbiguity", true); + bare.withArray("unansweredQuestions").addObject() + .put("question", "Which retry limit?").putNull("defaultApplied"); + String bareJson = JSON.writeValueAsString(bare); + String duplicated = bareJson.substring(0, bareJson.length() - 1) + + ",\"materialAmbiguity\":false}"; + String input = "```json\n" + JSON.writeValueAsString(fenced) + + "\n```\nCorrection: `" + duplicated + "`"; + assertRejected(Stage.DESIGN, input, + "backtick-wrapped duplicate-key correction"); + } + + @Test + void rejectsInlineBacktickWithTrailingDocument() throws Exception { + ObjectNode fenced = result(null, null); + ObjectNode bare = result(null, null); + bare.put("materialAmbiguity", true); + bare.withArray("unansweredQuestions").addObject() + .put("question", "Which retry limit?").putNull("defaultApplied"); + String bareJson = JSON.writeValueAsString(bare); + String trailing = bareJson + bareJson; + String input = "```json\n" + JSON.writeValueAsString(fenced) + + "\n```\nCorrection: `" + trailing + "`"; + assertRejected(Stage.DESIGN, input, + "backtick-wrapped trailing-document correction"); + } + + @Test + void rejectsInlineBacktickArrayWrappedCorrection() throws Exception { + ObjectNode fenced = result(null, null); + ObjectNode bare = result(null, null); + bare.put("materialAmbiguity", true); + bare.withArray("unansweredQuestions").addObject() + .put("question", "Which retry limit?").putNull("defaultApplied"); + String input = "```json\n" + JSON.writeValueAsString(fenced) + + "\n```\nCorrection: `[" + JSON.writeValueAsString(bare) + "]`"; + assertRejected(Stage.DESIGN, input, + "backtick-wrapped array-wrapped correction"); + } + @Test void acceptsHarmlessInlineJsonExample() throws Exception { ObjectNode valid = result("149-local-controller", null);
