elsa-core/test/integration/Elsa.Bpmn.Interchange.IntegrationTests/Assets/nested-subprocess-duplicate-id.bpmn
Sipke Schoorstra 933d1739bd
fix(bpmn): refuse documents with duplicate element ids before recursion (#8080)
* fix(bpmn): refuse documents with duplicate element ids before recursion

A subProcess nested inside another subProcess with the same id made
EnsureCapabilitiesSatisfied, BpmnWorkBinder.BindScope and the interchange
library's own BpmnXmlWriter recurse without terminating, overflowing the
stack and killing the process (.NET cannot catch StackOverflowException).
Refuse such a document up front, coded bpmn.import.duplicate-element-id
(422), on POST bpmn/import and PUT bpmn/definitions/{id}/document, listing
the duplicated ids.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(bpmn): include process ids in the duplicate-id check

EnsureElementIdsUnique only pooled element ids, never a process
definition's own ProcessId. A top-level process's id is never one of
its own elements, so a subprocess reusing its parent's id (or two
top-level processes sharing an id) went undetected and still
overflowed the stack in EnsureCapabilitiesSatisfied, BpmnWorkBinder
and BpmnXmlWriter the same way a repeated element id does. Add every
top-level process's own id to the pool; a nested process definition's
own id needs no equivalent addition since it is always exactly the
element id that opens it, already counted once via its owner's
elements.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 11:26:07 -07:00

48 lines
2.2 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:vw="https://bpmn.valenceworks.io/schema/bpmn"
id="Definitions_nested-subprocess-duplicate-id"
targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="nested-subprocess-duplicate-id" name="Nested Subprocess Duplicate Id" isExecutable="true">
<bpmn:startEvent id="Start_1">
<bpmn:outgoing>Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<!-- The outer subprocess and the subprocess nested directly inside it share the same id ("Outer"), which is
invalid BPMN (ids must be unique within a document) and, before elsa-core#8074's fix, overflowed the stack
of any code that finds a scope's nested processes by matching this id back out of the flat binding list. -->
<bpmn:subProcess id="Outer" name="Outer">
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_2</bpmn:outgoing>
<bpmn:startEvent id="Outer_Start">
<bpmn:outgoing>Outer_Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:subProcess id="Outer" name="Inner reusing its parent's id">
<bpmn:incoming>Outer_Flow_1</bpmn:incoming>
<bpmn:outgoing>Outer_Flow_2</bpmn:outgoing>
<bpmn:startEvent id="Inner_Start">
<bpmn:outgoing>Inner_Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Inner_End">
<bpmn:incoming>Inner_Flow_1</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Inner_Flow_1" sourceRef="Inner_Start" targetRef="Inner_End" />
</bpmn:subProcess>
<bpmn:endEvent id="Outer_End">
<bpmn:incoming>Outer_Flow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Outer_Flow_1" sourceRef="Outer_Start" targetRef="Outer" />
<bpmn:sequenceFlow id="Outer_Flow_2" sourceRef="Outer" targetRef="Outer_End" />
</bpmn:subProcess>
<bpmn:endEvent id="End_1">
<bpmn:incoming>Flow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1" sourceRef="Start_1" targetRef="Outer" />
<bpmn:sequenceFlow id="Flow_2" sourceRef="Outer" targetRef="End_1" />
</bpmn:process>
</bpmn:definitions>