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 2cd0f0e33fd7a47f512f3ead3f53eaf618573a94
Author: Jinal Sarvaiya <[email protected]>
AuthorDate: Thu Nov 5 08:53:29 2026 +0000

    fix #100: reject mixed fenced/bare envelopes and null-padded array wrappers
---
 .../camel/kit/ship/controller/ShipStageResult.java | 11 +++++++++--
 .../kit/ship/controller/ShipStageResultTest.java   | 22 ++++++++++++++++++++++
 2 files changed, 31 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 d91dbd00..7de329df 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
@@ -295,17 +295,24 @@ record ShipStageResult(
         Matcher m = JSON_FENCE.matcher(text);
         if (m.find()) {
             String first = m.group(1).strip();
+            int fenceStart = m.start();
+            int fenceEnd = m.end();
             if (m.find()) {
                 throw new IOException("Pi stage result contains conflicting 
fenced JSON envelopes");
             }
+            String before = text.substring(0, fenceStart);
+            String after = text.substring(fenceEnd);
+            if (before.indexOf('{') >= 0 || after.indexOf('{') >= 0) {
+                throw new IOException("Pi stage result contains conflicting 
JSON envelopes");
+            }
             return first;
         }
         int start = text.indexOf('{');
         int end = text.lastIndexOf('}');
         if (start >= 0 && end > start) {
             String prefix = text.substring(0, start).strip();
-            if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) == 
'[') {
-                return stripped;
+            if (!prefix.isEmpty() && prefix.indexOf('[') >= 0) {
+                throw new IOException("Pi stage result is wrapped in an 
array");
             }
             return text.substring(start, end + 1);
         }
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 0260c3a7..40100ada 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
@@ -439,6 +439,21 @@ class ShipStageResultTest {
         assertRejected(Stage.DISCOVERY, multiFence, "conflicting fenced 
envelopes");
     }
 
+    @Test
+    void rejectsMixedFencedAndBareEnvelopes() 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 fencedThenBare = "```json\n" + JSON.writeValueAsString(fenced)
+                                + "\n```\nCorrection:\n" + 
JSON.writeValueAsString(bare);
+        assertRejected(Stage.DESIGN, fencedThenBare, "fenced then bare 
envelope");
+        String bareThenFenced = JSON.writeValueAsString(bare)
+                                + "\nCorrected:\n```json\n" + 
JSON.writeValueAsString(fenced) + "\n```\n";
+        assertRejected(Stage.DESIGN, bareThenFenced, "bare then fenced 
envelope");
+    }
+
     @Test
     void rejectsArrayWrappedEnvelope() throws Exception {
         ObjectNode valid = result("149-local-controller", null);
@@ -446,6 +461,13 @@ class ShipStageResultTest {
         assertRejected(Stage.DISCOVERY, array, "array-wrapped envelope");
     }
 
+    @Test
+    void rejectsNullPaddedArrayWrapper() throws Exception {
+        ObjectNode valid = result("149-local-controller", null);
+        String nullPadded = "[null, " + JSON.writeValueAsString(valid) + "]";
+        assertRejected(Stage.DISCOVERY, nullPadded, "null-padded array 
wrapper");
+    }
+
     @Test
     void rejectsOversizedProseAroundValidFence() throws Exception {
         ObjectNode valid = result("149-local-controller", null);

Reply via email to