Skip to content

Commit 45eb60b

Browse files
committed
Process engine js sandboxing
1 parent abff4fb commit 45eb60b

File tree

8 files changed

+290
-1
lines changed

8 files changed

+290
-1
lines changed

core/src/main/java/com/ritense/valtimo/autoconfigure/CamundaContextConfiguration.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
package com.ritense.valtimo.autoconfigure;
1818

1919
import com.ritense.valtimo.CamundaWhitelistedBeansPlugin;
20+
import com.ritense.valtimo.ScriptingWhitelistPlugin;
2021
import com.ritense.valtimo.contract.annotation.ProcessBean;
2122
import java.util.Map;
23+
import java.util.Set;
2224
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
25+
import org.springframework.beans.factory.annotation.Value;
2326
import org.springframework.boot.autoconfigure.AutoConfiguration;
2427
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2528
import org.springframework.context.ApplicationContext;
@@ -28,16 +31,24 @@
2831
import org.springframework.core.annotation.Order;
2932

3033
@AutoConfiguration
31-
@ConditionalOnProperty(prefix = "valtimo.camunda", name = "bean-whitelisting", havingValue = "true", matchIfMissing = true)
3234
public class CamundaContextConfiguration {
3335

3436
@Bean
3537
@Order(Ordering.DEFAULT_ORDER - 1)
38+
@ConditionalOnProperty(prefix = "valtimo.camunda", name = "bean-whitelisting", havingValue = "true", matchIfMissing = true)
3639
public CamundaWhitelistedBeansPlugin camundaWhitelistedBeansPlugin(
3740
@Lazy @ProcessBean Map<String, Object> processBeans,
3841
ApplicationContext applicationContext
3942
) {
4043
return new CamundaWhitelistedBeansPlugin(processBeans, applicationContext);
4144
}
4245

46+
@Bean
47+
@Order(Ordering.DEFAULT_ORDER - 1)
48+
public ScriptingWhitelistPlugin scriptingWhitelistPlugin(
49+
@Value("valtimo.camunda.scripting.allowedClasses") Set<String> allowedScriptingClasses
50+
) {
51+
return new ScriptingWhitelistPlugin(allowedScriptingClasses);
52+
}
53+
4354
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2015-2025 Ritense BV, the Netherlands.
3+
*
4+
* Licensed under EUPL, Version 1.2 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" basis,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.ritense.valtimo
18+
19+
import com.oracle.truffle.js.scriptengine.GraalJSScriptEngine
20+
import org.camunda.bpm.engine.impl.scripting.engine.DefaultScriptEngineResolver
21+
import org.graalvm.polyglot.Context
22+
import org.graalvm.polyglot.HostAccess
23+
import javax.script.ScriptEngine
24+
import javax.script.ScriptEngineManager
25+
26+
class AllowedClassesScriptEngineResolver(
27+
scriptEngineManager: ScriptEngineManager,
28+
otherAllowedClasses: Set<String> = emptySet()
29+
) : DefaultScriptEngineResolver(scriptEngineManager) {
30+
val ALL_ALLOWED = ALLOWED + otherAllowedClasses
31+
32+
override fun getJavaScriptScriptEngine(language: String?): ScriptEngine {
33+
val ctx = Context.newBuilder("js")
34+
.allowHostAccess(HostAccess.ALL)
35+
.allowHostClassLookup(ALL_ALLOWED::contains)
36+
37+
return GraalJSScriptEngine.create(null, ctx)
38+
}
39+
40+
companion object {
41+
private val ALLOWED = mutableSetOf<String?>(
42+
"java.util.ArrayList",
43+
"org.joda.time.DateTime",
44+
"java.util.Date",
45+
"java.lang.Math",
46+
"org.camunda.spin.Spin",
47+
//java.time classes
48+
"java.time.Clock",
49+
"java.time.Duration",
50+
"java.time.Instant",
51+
"java.time.LocalDate",
52+
"java.time.LocalDateTime",
53+
"java.time.LocalTime",
54+
"java.time.MonthDay",
55+
"java.time.OffsetDateTime",
56+
"java.time.OffsetTime",
57+
"java.time.Period",
58+
"java.time.Year",
59+
"java.time.YearMonth",
60+
"java.time.ZonedDateTime",
61+
"java.time.ZoneId",
62+
"java.time.ZoneOffset",
63+
)
64+
}
65+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2015-2024 Ritense BV, the Netherlands.
3+
*
4+
* Licensed under EUPL, Version 1.2 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" basis,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.ritense.valtimo
18+
19+
import mu.KotlinLogging
20+
import org.camunda.bpm.engine.impl.cfg.AbstractProcessEnginePlugin
21+
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl
22+
import javax.script.ScriptEngineManager
23+
24+
class ScriptingWhitelistPlugin(
25+
private val allowedScriptingClasses: Set<String> = emptySet()
26+
) : AbstractProcessEnginePlugin() {
27+
override fun preInit(processEngineConfiguration: ProcessEngineConfigurationImpl?) {
28+
logger.debug { "Registering allowed classes for scripting..." }
29+
requireNotNull(processEngineConfiguration) { "No process engine configuration found. Failed to register allowed scripting classes." }
30+
31+
processEngineConfiguration.setConfigureScriptEngineHostAccess(false)
32+
processEngineConfiguration.setEnableScriptEngineLoadExternalResources(false)
33+
processEngineConfiguration.setScriptEngineResolver(AllowedClassesScriptEngineResolver(ScriptEngineManager(), allowedScriptingClasses))
34+
}
35+
36+
companion object {
37+
val logger = KotlinLogging.logger {}
38+
}
39+
}

core/src/test/kotlin/com/ritense/valtimo/scripttask/JavascriptScriptTaskProcessIntTest.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import com.ritense.authorization.AuthorizationContext.Companion.runWithoutAuthor
2020
import com.ritense.valtimo.BaseIntegrationTest
2121
import com.ritense.valtimo.service.CamundaProcessService
2222
import org.camunda.bpm.engine.HistoryService
23+
import org.camunda.bpm.engine.ScriptEvaluationException
2324
import org.junit.jupiter.api.Test
25+
import org.junit.jupiter.api.assertDoesNotThrow
26+
import org.junit.jupiter.api.assertThrows
2427
import org.springframework.beans.factory.annotation.Autowired
2528
import org.springframework.transaction.annotation.Transactional
2629
import java.util.UUID
@@ -52,4 +55,45 @@ class JavascriptScriptTaskProcessIntTest : BaseIntegrationTest() {
5255
.value
5356
assertEquals(3, c)
5457
}
58+
59+
@Test
60+
fun `whitelisted classes should be allowed in script tasks`() {
61+
62+
assertDoesNotThrow {
63+
runWithoutAuthorization {
64+
camundaProcessService.startProcess(
65+
"javascript-script-task-process-allowed",
66+
UUID.randomUUID().toString(),
67+
emptyMap()
68+
).processInstanceDto
69+
}
70+
}
71+
}
72+
73+
@Test
74+
fun `default whitelisted classes should be allowed in script tasks`() {
75+
76+
assertDoesNotThrow {
77+
runWithoutAuthorization {
78+
camundaProcessService.startProcess(
79+
"javascript-script-task-process-default-allowed",
80+
UUID.randomUUID().toString(),
81+
emptyMap()
82+
).processInstanceDto
83+
}
84+
}
85+
}
86+
87+
@Test
88+
fun `non-whitelisted classes should not be allowed in script tasks`() {
89+
assertThrows<ScriptEvaluationException> {
90+
runWithoutAuthorization {
91+
camundaProcessService.startProcess(
92+
"javascript-script-task-process-unallowed",
93+
UUID.randomUUID().toString(),
94+
emptyMap()
95+
).processInstanceDto
96+
}
97+
}
98+
}
5599
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_18vfwpp" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.23.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.19.0">
3+
<bpmn:process id="javascript-script-task-process-allowed" name="Javascript script task process" isExecutable="true" camunda:historyTimeToLive="180">
4+
<bpmn:startEvent id="StartEvent_1">
5+
<bpmn:outgoing>Flow_0kymcf3</bpmn:outgoing>
6+
</bpmn:startEvent>
7+
<bpmn:sequenceFlow id="Flow_0kymcf3" sourceRef="StartEvent_1" targetRef="Activity_1wy7vpn" />
8+
<bpmn:endEvent id="Event_0k1400n">
9+
<bpmn:incoming>Flow_1muqoyt</bpmn:incoming>
10+
</bpmn:endEvent>
11+
<bpmn:sequenceFlow id="Flow_1muqoyt" sourceRef="Activity_1wy7vpn" targetRef="Event_0k1400n" />
12+
<bpmn:scriptTask id="Activity_1wy7vpn" name="Sum" scriptFormat="javascript">
13+
<bpmn:incoming>Flow_0kymcf3</bpmn:incoming>
14+
<bpmn:outgoing>Flow_1muqoyt</bpmn:outgoing>
15+
<bpmn:script>const Math = Java.type("java.lang.Math");
16+
17+
const result = Math.pow(2,3);</bpmn:script>
18+
</bpmn:scriptTask>
19+
</bpmn:process>
20+
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
21+
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="javascript-script-task-process-allowed">
22+
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
23+
<dc:Bounds x="179" y="99" width="36" height="36" />
24+
</bpmndi:BPMNShape>
25+
<bpmndi:BPMNShape id="Event_0k1400n_di" bpmnElement="Event_0k1400n">
26+
<dc:Bounds x="432" y="99" width="36" height="36" />
27+
</bpmndi:BPMNShape>
28+
<bpmndi:BPMNShape id="Activity_0zbtk6u_di" bpmnElement="Activity_1wy7vpn">
29+
<dc:Bounds x="270" y="77" width="100" height="80" />
30+
<bpmndi:BPMNLabel />
31+
</bpmndi:BPMNShape>
32+
<bpmndi:BPMNEdge id="Flow_0kymcf3_di" bpmnElement="Flow_0kymcf3">
33+
<di:waypoint x="215" y="117" />
34+
<di:waypoint x="270" y="117" />
35+
</bpmndi:BPMNEdge>
36+
<bpmndi:BPMNEdge id="Flow_1muqoyt_di" bpmnElement="Flow_1muqoyt">
37+
<di:waypoint x="370" y="117" />
38+
<di:waypoint x="432" y="117" />
39+
</bpmndi:BPMNEdge>
40+
</bpmndi:BPMNPlane>
41+
</bpmndi:BPMNDiagram>
42+
</bpmn:definitions>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_18vfwpp" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.23.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.19.0">
3+
<bpmn:process id="javascript-script-task-process-default-allowed" name="Javascript script task process" isExecutable="true" camunda:historyTimeToLive="180">
4+
<bpmn:startEvent id="StartEvent_1">
5+
<bpmn:outgoing>Flow_0kymcf3</bpmn:outgoing>
6+
</bpmn:startEvent>
7+
<bpmn:sequenceFlow id="Flow_0kymcf3" sourceRef="StartEvent_1" targetRef="Activity_1wy7vpn" />
8+
<bpmn:endEvent id="Event_0k1400n">
9+
<bpmn:incoming>Flow_1muqoyt</bpmn:incoming>
10+
</bpmn:endEvent>
11+
<bpmn:sequenceFlow id="Flow_1muqoyt" sourceRef="Activity_1wy7vpn" targetRef="Event_0k1400n" />
12+
<bpmn:scriptTask id="Activity_1wy7vpn" name="Sum" scriptFormat="javascript">
13+
<bpmn:incoming>Flow_0kymcf3</bpmn:incoming>
14+
<bpmn:outgoing>Flow_1muqoyt</bpmn:outgoing>
15+
<bpmn:script>const LocalDateTime = Java.type("java.time.LocalDateTime");
16+
17+
const result = LocalDateTime.now()</bpmn:script>
18+
</bpmn:scriptTask>
19+
</bpmn:process>
20+
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
21+
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="javascript-script-task-process-default-allowed">
22+
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
23+
<dc:Bounds x="179" y="99" width="36" height="36" />
24+
</bpmndi:BPMNShape>
25+
<bpmndi:BPMNShape id="Event_0k1400n_di" bpmnElement="Event_0k1400n">
26+
<dc:Bounds x="432" y="99" width="36" height="36" />
27+
</bpmndi:BPMNShape>
28+
<bpmndi:BPMNShape id="Activity_0zbtk6u_di" bpmnElement="Activity_1wy7vpn">
29+
<dc:Bounds x="270" y="77" width="100" height="80" />
30+
<bpmndi:BPMNLabel />
31+
</bpmndi:BPMNShape>
32+
<bpmndi:BPMNEdge id="Flow_0kymcf3_di" bpmnElement="Flow_0kymcf3">
33+
<di:waypoint x="215" y="117" />
34+
<di:waypoint x="270" y="117" />
35+
</bpmndi:BPMNEdge>
36+
<bpmndi:BPMNEdge id="Flow_1muqoyt_di" bpmnElement="Flow_1muqoyt">
37+
<di:waypoint x="370" y="117" />
38+
<di:waypoint x="432" y="117" />
39+
</bpmndi:BPMNEdge>
40+
</bpmndi:BPMNPlane>
41+
</bpmndi:BPMNDiagram>
42+
</bpmn:definitions>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_18vfwpp" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.23.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.19.0">
3+
<bpmn:process id="javascript-script-task-process-unallowed" name="Javascript script task process" isExecutable="true" camunda:historyTimeToLive="180">
4+
<bpmn:startEvent id="StartEvent_1">
5+
<bpmn:outgoing>Flow_0kymcf3</bpmn:outgoing>
6+
</bpmn:startEvent>
7+
<bpmn:sequenceFlow id="Flow_0kymcf3" sourceRef="StartEvent_1" targetRef="Activity_1wy7vpn" />
8+
<bpmn:endEvent id="Event_0k1400n">
9+
<bpmn:incoming>Flow_1muqoyt</bpmn:incoming>
10+
</bpmn:endEvent>
11+
<bpmn:sequenceFlow id="Flow_1muqoyt" sourceRef="Activity_1wy7vpn" targetRef="Event_0k1400n" />
12+
<bpmn:scriptTask id="Activity_1wy7vpn" name="Sum" scriptFormat="javascript">
13+
<bpmn:incoming>Flow_0kymcf3</bpmn:incoming>
14+
<bpmn:outgoing>Flow_1muqoyt</bpmn:outgoing>
15+
<bpmn:script>const HashMap = Java.type("java.util.HashMap");
16+
17+
const result = new HashMap()</bpmn:script>
18+
</bpmn:scriptTask>
19+
</bpmn:process>
20+
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
21+
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="javascript-script-task-process-unallowed">
22+
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
23+
<dc:Bounds x="179" y="99" width="36" height="36" />
24+
</bpmndi:BPMNShape>
25+
<bpmndi:BPMNShape id="Event_0k1400n_di" bpmnElement="Event_0k1400n">
26+
<dc:Bounds x="432" y="99" width="36" height="36" />
27+
</bpmndi:BPMNShape>
28+
<bpmndi:BPMNShape id="Activity_0zbtk6u_di" bpmnElement="Activity_1wy7vpn">
29+
<dc:Bounds x="270" y="77" width="100" height="80" />
30+
<bpmndi:BPMNLabel />
31+
</bpmndi:BPMNShape>
32+
<bpmndi:BPMNEdge id="Flow_0kymcf3_di" bpmnElement="Flow_0kymcf3">
33+
<di:waypoint x="215" y="117" />
34+
<di:waypoint x="270" y="117" />
35+
</bpmndi:BPMNEdge>
36+
<bpmndi:BPMNEdge id="Flow_1muqoyt_di" bpmnElement="Flow_1muqoyt">
37+
<di:waypoint x="370" y="117" />
38+
<di:waypoint x="432" y="117" />
39+
</bpmndi:BPMNEdge>
40+
</bpmndi:BPMNPlane>
41+
</bpmndi:BPMNDiagram>
42+
</bpmn:definitions>

core/src/test/resources/config/application.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ valtimo:
5151
reminderTemplate: bpc-task-reminder
5252
plugin:
5353
encryption-secret: "abcdefghijklmnop"
54+
camunda:
55+
scripting:
56+
allowedClasses:
57+
- java.lang.Math
5458

5559
aws:
5660
profile: ritense

0 commit comments

Comments
 (0)