This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 4_1_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/4_1_X by this push:
new 2334436b4b Wrapping Flowable's Groovy scriptTasks with security
sandbox (#1418)
2334436b4b is described below
commit 2334436b4ba1b915f3b6159d7747a8b03303609b
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 3f1514a9e1..0ee09a2c8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -415,7 +415,7 @@ under the License.
<project.build.outputTimestamp>2026-05-22T08:01:03Z</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>