This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 8c7a7580ac Wrapping Flowable's Groovy scriptTasks with security 
sandbox (#1418)
8c7a7580ac is described below

commit 8c7a7580ac853e749f8c6ccac39216a21d13264f
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Tue Jun 9 13:33:10 2026 +0200

    Wrapping Flowable's Groovy scriptTasks with security sandbox (#1418)
---
 .../GroovySandboxScriptEngineImpl.java             | 86 ++++++++++++++++++++++
 .../core/flowable/FlowableWorkflowContext.java     | 28 ++++++-
 pom.xml                                            |  2 +-
 3 files changed, 113 insertions(+), 3 deletions(-)

diff --git 
a/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandboxScriptEngineImpl.java
 
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandboxScriptEngineImpl.java
new file mode 100644
index 0000000000..9f8939133d
--- /dev/null
+++ 
b/core/spring/src/main/java/org/apache/syncope/core/spring/implementation/GroovySandboxScriptEngineImpl.java
@@ -0,0 +1,86 @@
+/*
+ * 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.syncope.core.spring.implementation;
+
+import groovy.grape.GrabAnnotationTransformation;
+import groovy.lang.GroovyClassLoader;
+import java.io.Reader;
+import java.util.Set;
+import javax.script.ScriptContext;
+import javax.script.ScriptException;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.jsr223.GroovyScriptEngineImpl;
+import org.jenkinsci.plugins.scriptsecurity.sandbox.blacklists.Blacklist;
+import 
org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.RejectASTTransformsCustomizer;
+import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor;
+import org.kohsuke.groovy.sandbox.GroovyInterceptor;
+import org.kohsuke.groovy.sandbox.SandboxTransformer;
+import org.springframework.util.function.ThrowingSupplier;
+
+public class GroovySandboxScriptEngineImpl extends GroovyScriptEngineImpl {
+
+    private static final GroovyClassLoader GROOVY_CLASSLOADER;
+
+    static {
+        CompilerConfiguration cc = new CompilerConfiguration();
+        cc.addCompilationCustomizers(new RejectASTTransformsCustomizer(), new 
SandboxTransformer());
+        
cc.setDisabledGlobalASTTransformations(Set.of(GrabAnnotationTransformation.class.getName()));
+
+        GROOVY_CLASSLOADER = new 
GroovyClassLoader(Thread.currentThread().getContextClassLoader(), cc);
+    }
+
+    protected final Blacklist blackList;
+
+    public GroovySandboxScriptEngineImpl(final Blacklist blackList) {
+        super(GROOVY_CLASSLOADER);
+        this.blackList = blackList;
+    }
+
+    protected Object sandboxEval(final ThrowingSupplier<Object> eval) {
+        GroovyInterceptor interceptor = null;
+        try {
+            interceptor = new SandboxInterceptor(blackList);
+            interceptor.register();
+        } catch (NoClassDefFoundError noClassDefFound) {
+            // ignore
+        }
+
+        try {
+            return eval.get();
+        } finally {
+            if (interceptor != null) {
+                try {
+                    interceptor.unregister();
+                } catch (NoClassDefFoundError noClassDefFound) {
+                    // ignore
+                }
+            }
+        }
+    }
+
+    @Override
+    public Object eval(final Reader reader, final ScriptContext ctx) throws 
ScriptException {
+        return sandboxEval(() -> super.eval(reader, ctx));
+    }
+
+    @Override
+    public Object eval(final String script, final ScriptContext ctx) throws 
ScriptException {
+        return sandboxEval(() -> super.eval(script, ctx));
+    }
+}
diff --git 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
index 1b1a643cfa..d0fbecfa92 100644
--- 
a/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
+++ 
b/ext/flowable/flowable-bpmn/src/main/java/org/apache/syncope/core/flowable/FlowableWorkflowContext.java
@@ -19,6 +19,8 @@
 package org.apache.syncope.core.flowable;
 
 import java.util.List;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.core.flowable.api.UserRequestHandler;
 import org.apache.syncope.core.flowable.impl.FlowableBpmnProcessManager;
@@ -48,14 +50,19 @@ import 
org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.provisioning.api.data.UserDataBinder;
 import 
org.apache.syncope.core.provisioning.api.notification.NotificationManager;
 import org.apache.syncope.core.provisioning.api.rules.RuleProvider;
+import 
org.apache.syncope.core.spring.implementation.GroovySandboxScriptEngineImpl;
 import org.apache.syncope.core.spring.security.SecurityProperties;
 import org.apache.syncope.core.workflow.api.UserWorkflowAdapter;
+import org.flowable.common.engine.api.FlowableException;
 import org.flowable.common.engine.impl.AbstractEngineConfiguration;
 import org.flowable.common.engine.impl.cfg.IdGenerator;
 import org.flowable.common.engine.impl.persistence.StrongUuidGenerator;
+import org.flowable.common.engine.impl.scripting.JSR223FlowableScriptEngine;
+import org.flowable.common.engine.impl.scripting.ScriptingEngines;
 import org.flowable.idm.spring.SpringIdmEngineConfiguration;
 import org.flowable.idm.spring.configurator.SpringIdmEngineConfigurator;
 import org.flowable.spring.SpringProcessEngineConfiguration;
+import org.jenkinsci.plugins.scriptsecurity.sandbox.blacklists.Blacklist;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import 
org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.ApplicationEventPublisher;
@@ -140,9 +147,10 @@ public class FlowableWorkflowContext {
     public SpringProcessEngineConfiguration processEngineConfiguration(
             final WorkflowFlowableProperties props,
             final SpringIdmEngineConfigurator syncopeIdmEngineConfigurator,
-            final IdGenerator idGenerator,
             final SyncopeEntitiesVariableType syncopeEntitiesVariableType,
-            final SyncopeFormHandlerHelper syncopeFormHandlerHelper) {
+            final SyncopeFormHandlerHelper syncopeFormHandlerHelper,
+            final IdGenerator idGenerator,
+            final Blacklist groovyBlackList) {
 
         SpringProcessEngineConfiguration conf = new 
SpringProcessEngineConfiguration();
         
conf.setDatabaseSchemaUpdate(AbstractEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
@@ -154,6 +162,22 @@ public class FlowableWorkflowContext {
         conf.setFormHandlerHelper(syncopeFormHandlerHelper);
         conf.setIdGenerator(idGenerator);
         conf.setPreBpmnParseHandlers(List.of(new 
ShellServiceTaskDisablingBpmnParseHandler()));
+
+        ScriptEngine groovyScriptEngine = new 
GroovySandboxScriptEngineImpl(groovyBlackList);
+        groovyScriptEngine.getContext().
+                setAttribute("#jsr223.groovy.engine.keep.globals", "weak", 
ScriptContext.ENGINE_SCOPE);
+        conf.setScriptEngine(new JSR223FlowableScriptEngine() {
+
+            @Override
+            protected ScriptEngine getEngineByName(final String language) {
+                if 
(ScriptingEngines.GROOVY_SCRIPTING_LANGUAGE.equals(language)) {
+                    return groovyScriptEngine;
+                }
+
+                throw new FlowableException("Can't find scripting engine for 
'" + language + "'");
+            }
+        });
+
         return conf;
     }
 
diff --git a/pom.xml b/pom.xml
index 3ccf8c8ed6..806bdc8c6c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -415,7 +415,7 @@ under the License.
     
<project.build.outputTimestamp>2025-11-25T06:53:52Z</project.build.outputTimestamp>
     <syncope.version>${project.version}</syncope.version>
 
-    <connid.version>1.6.1.0</connid.version>
+    <connid.version>1.6.1.1-SNAPSHOT</connid.version>
     <connid.soap.version>1.5.0</connid.soap.version>
     <connid.rest.version>1.1.1</connid.rest.version>
     <connid.db.version>2.4.1</connid.db.version>

Reply via email to