This is an automated email from the ASF dual-hosted git repository.
ashishvijaywargiya pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
The following commit(s) were added to refs/heads/trunk by this push:
new 8114f9fb6a Fix webtools ProgramExport, Cache Maintenance and Thread
List; normalise the ProgramExport pattern list (#1845)
8114f9fb6a is described below
commit 8114f9fb6a7232861d1ac0386063357d4f4881df
Author: Krishna Uprit <[email protected]>
AuthorDate: Fri Sep 4 10:38:32 2026 +0530
Fix webtools ProgramExport, Cache Maintenance and Thread List; normalise
the ProgramExport pattern list (#1845)
Small fixes around webtools/ProgramExport and the shared Groovy class
loader.
- GroovyUtil: the compile-time AST restrictions meant for inline
${groovy:...}
scriptlets were also being applied to component:// .groovy files, so
screen
scripts that reference java.lang.Thread / java.lang.Runtime —
ProgramExport,
Cache Maintenance (FindUtilCache) and Thread List (Threads) — stopped
compiling and returned HTTP 500. The file-location path now uses its own
GroovyClassLoader; the inline path is unchanged (GroovyUtilTests still
pass).
- ProgramExport: the in-file SecureASTCustomizer combined an empty
static-imports allow-list with indirect import checking and rejected
every
method call, even the built-in example. Switched imports/static imports
to
disallowed-list mode, restored java.lang.Object to the allowed
receivers,
and updated the deprecated setter names.
- ProgramExport: several pattern-list entries matched only the plain
obj.name(
spelling; they now also match Groovy's quoted obj."name"() form,
consistently
across the list.
---------
Co-authored-by: Krishnauprit18 <[email protected]>
---
.../org/apache/ofbiz/base/util/GroovyUtil.java | 38 ++++---
.../ofbiz/webtools/entity/ProgramExport.groovy | 118 +++++++++++++--------
2 files changed, 98 insertions(+), 58 deletions(-)
diff --git
a/framework/base/src/main/java/org/apache/ofbiz/base/util/GroovyUtil.java
b/framework/base/src/main/java/org/apache/ofbiz/base/util/GroovyUtil.java
index 51aec9a1f2..eff00da529 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/util/GroovyUtil.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/GroovyUtil.java
@@ -49,22 +49,35 @@ public final class GroovyUtil {
private static final String MODULE = GroovyUtil.class.getName();
private static final UtilCache<String, Class<?>> PARSED_SCRIPTS =
UtilCache.createUtilCache("script.GroovyLocationParsedCache", 0, 0, false);
private static final GroovyClassLoader GROOVY_CLASS_LOADER;
+ private static final GroovyClassLoader GROOVY_CLASS_LOADER_FOR_LOCATION;
private static final CompilerConfiguration SANDBOXED_COMPILER_CONFIG;
private GroovyUtil() { }
static {
- GroovyClassLoader groovyClassLoader = null;
+ GroovyClassLoader sandboxedClassLoader = null;
+ GroovyClassLoader locationClassLoader = null;
String scriptBaseClass = UtilProperties.getPropertyValue("groovy",
"scriptBaseClass");
if (!scriptBaseClass.isEmpty()) {
- CompilerConfiguration conf = new CompilerConfiguration();
- conf.setScriptBaseClass(scriptBaseClass);
- // Same compile-time AST restrictions as SANDBOXED_COMPILER_CONFIG
below, so that parseClass(),
- // not just eval(), refuses OS-execution APIs and dynamic
class-loading.
- conf.addCompilationCustomizers(buildSecureAstCustomizer());
- groovyClassLoader = new
GroovyClassLoader(GroovyUtil.class.getClassLoader(), conf);
+ // Used by parseClass(String): compiles ${groovy:...} substrings
and inline <script> bodies passed to
+ // ScriptUtil.parseScript(), which can carry caller-supplied text.
Keep the compile-time
+ // SecureASTCustomizer here as an injection backstop (see
GroovyUtilTests).
+ CompilerConfiguration sandboxedConf = new CompilerConfiguration();
+ sandboxedConf.setScriptBaseClass(scriptBaseClass);
+
sandboxedConf.addCompilationCustomizers(buildSecureAstCustomizer());
+ sandboxedClassLoader = new
GroovyClassLoader(GroovyUtil.class.getClassLoader(), sandboxedConf);
+
+ // Used by parseClass(InputStream, location): compiles *.groovy
files resolved from a component://
+ // location (controller events, service implementations, screen
scripts). These are first-party
+ // files shipped with the framework/plugins, never request input,
so they are intentionally NOT
+ // run through SecureASTCustomizer - some legitimately use
java.lang.Thread / java.lang.Runtime
+ // (e.g. the webtools Threads and Cache Maintenance screens) and
must still compile.
+ CompilerConfiguration locationConf = new CompilerConfiguration();
+ locationConf.setScriptBaseClass(scriptBaseClass);
+ locationClassLoader = new
GroovyClassLoader(GroovyUtil.class.getClassLoader(), locationConf);
}
- GROOVY_CLASS_LOADER = groovyClassLoader;
+ GROOVY_CLASS_LOADER = sandboxedClassLoader;
+ GROOVY_CLASS_LOADER_FOR_LOCATION = locationClassLoader;
}
static {
@@ -73,8 +86,9 @@ public final class GroovyUtil {
}
/**
- * Builds a fresh {@link SecureASTCustomizer} applying the compile-time
AST restrictions used by both
- * GROOVY_CLASS_LOADER and SANDBOXED_COMPILER_CONFIG. Blocks OS-execution
APIs and dynamic class-loading
+ * Builds a fresh {@link SecureASTCustomizer} applying the compile-time
AST restrictions used by
+ * GROOVY_CLASS_LOADER (parseClass(String)) and SANDBOXED_COMPILER_CONFIG
(eval()), but not by
+ * GROOVY_CLASS_LOADER_FOR_LOCATION (trusted component:// *.groovy files).
Blocks OS-execution APIs and dynamic class-loading
* as a defence-in-depth measure.
* <p>Returns a new instance on every call rather than a shared constant:
{@code SecureASTCustomizer}
* visits the AST during compilation, so handing the same instance to two
{@code CompilerConfiguration}s
@@ -231,8 +245,8 @@ public final class GroovyUtil {
*/
private static Class<?> parseClass(InputStream in, String location) throws
IOException {
String classText = UtilIO.readString(in);
- if (GROOVY_CLASS_LOADER != null) {
- return GROOVY_CLASS_LOADER.parseClass(classText, location);
+ if (GROOVY_CLASS_LOADER_FOR_LOCATION != null) {
+ return GROOVY_CLASS_LOADER_FOR_LOCATION.parseClass(classText,
location);
} else {
try (GroovyClassLoader classLoader = new GroovyClassLoader()) {
return classLoader.parseClass(classText, location);
diff --git
a/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ProgramExport.groovy
b/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ProgramExport.groovy
index de7b415b11..204ac89c83 100644
---
a/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ProgramExport.groovy
+++
b/framework/webtools/src/main/groovy/org/apache/ofbiz/webtools/entity/ProgramExport.groovy
@@ -77,23 +77,27 @@ if (security.hasPermission('ENTITY_MAINT', session)) {
// (?s) flag for multi-line/dotall matching to prevent whitespace bypass
List<String> dangerousPatterns = [
// Process & Command Execution + Runtime Variants
- /(?s)Runtime\s*\.\s*getRuntime\s*\(\s*\)/,
+ // NOTE: patterns that match a member access ('.name(',
'Type.name', '.name') carry an
+ // optional ['"]? around the name so they also match Groovy's
quoted-member syntax
+ // (obj."name"(), Type."name"()) - a normal language feature that
otherwise bypasses a
+ // literal-text match.
+ /(?s)Runtime\s*\.\s*['"]?getRuntime['"]?\s*\(\s*\)/,
/(?s)['"]java\.lang\.Runtime['"]\.class/,
- /(?s)Runtime\.class\.getDeclaredMethod/,
- /(?s)getRuntime\s*\(\s*\)\.exec/,
+ /(?s)Runtime\s*\.\s*class\s*\.\s*['"]?getDeclaredMethod['"]?/,
+ /(?s)getRuntime\s*\(\s*\)\.\s*['"]?exec['"]?/,
/(?s)ProcessBuilder/,
- /(?s)\.\s*execute\s*\(/,
- /(?s)System\s*\.\s*exit/,
+ /(?s)\.\s*['"]?execute['"]?\s*\(/,
+ /(?s)System\s*\.\s*['"]?exit['"]?/,
// Reflection & ClassLoading
- /(?s)Class\s*\.\s*forName/,
- /(?s)\.newInstance\s*\(/,
- /(?s)\.getDeclaredMethod/,
- /(?s)\.getDeclaredField/,
- /(?s)\.getMethod\s*\(/,
- /(?s)\.getField\s*\(/,
- /(?s)\.invoke\s*\(/,
- /(?s)\.loadClass\s*\(/,
- /(?s)\.getClassLoader\s*\(/,
+ /(?s)Class\s*\.\s*['"]?forName['"]?/,
+ /(?s)\.\s*['"]?newInstance['"]?\s*\(/,
+ /(?s)\.\s*['"]?getDeclaredMethod['"]?/,
+ /(?s)\.\s*['"]?getDeclaredField['"]?/,
+ /(?s)\.\s*['"]?getMethod['"]?\s*\(/,
+ /(?s)\.\s*['"]?getField['"]?\s*\(/,
+ /(?s)\.\s*['"]?invoke['"]?\s*\(/,
+ /(?s)\.\s*['"]?loadClass['"]?\s*\(/,
+ /(?s)\.\s*['"]?getClassLoader['"]?\s*\(/,
/(?s)java\s*\.\s*lang\s*\.\s*reflect/,
/(?s)URLClassLoader/,
/(?s)GroovyClassLoader/,
@@ -101,21 +105,21 @@ if (security.hasPermission('ENTITY_MAINT', session)) {
/(?s)javax\s*\.\s*script/,
/(?s)sun\s*\.\s*misc\s*\.\s*Unsafe/,
// Eval/GroovyShell Blocking
- /(?s)Eval\s*\.\s*me/,
- /(?s)Eval\s*\.\s*x/,
- /(?s)Eval\s*\.\s*xy/,
- /(?s)Eval\s*\.\s*xyz/,
+ /(?s)Eval\s*\.\s*['"]?me['"]?/,
+ /(?s)Eval\s*\.\s*['"]?x['"]?/,
+ /(?s)Eval\s*\.\s*['"]?xy['"]?/,
+ /(?s)Eval\s*\.\s*['"]?xyz['"]?/,
/(?s)GroovyShell/,
- /(?s)\.evaluate\s*\(/,
+ /(?s)\.\s*['"]?evaluate['"]?\s*\(/,
// File System Operations
/(?s)java\s*\.\s*io\s*\.\s*File\s*\(/,
/(?s)new\s+File\s*\(/,
- /(?s)Files\s*\.\s*readAllBytes/,
- /(?s)Paths\s*\.\s*get/,
- /(?s)\.toFile\s*\(/,
- /(?s)\.getResourceAsStream\s*\(/,
- /(?s)\.getText\s*\(/,
- /(?s)\.bytes\b/,
+ /(?s)Files\s*\.\s*['"]?readAllBytes['"]?/,
+ /(?s)Paths\s*\.\s*['"]?get['"]?/,
+ /(?s)\.\s*['"]?toFile['"]?\s*\(/,
+ /(?s)\.\s*['"]?getResourceAsStream['"]?\s*\(/,
+ /(?s)\.\s*['"]?getText['"]?\s*\(/,
+ /(?s)\.\s*(?:bytes\b|['"]bytes['"])/,
// Network Operations
/(?s)Socket\s*\(/,
/(?s)ServerSocket/,
@@ -125,8 +129,8 @@ if (security.hasPermission('ENTITY_MAINT', session)) {
/(?s)java\s*\.\s*net\s*\./,
/(?s)URL\s*\(/,
/(?s)NetworkInterface/,
- /(?s)\.openConnection\s*\(/,
- /(?s)\.connect\s*\(/,
+ /(?s)\.\s*['"]?openConnection['"]?\s*\(/,
+ /(?s)\.\s*['"]?connect['"]?\s*\(/,
// OFBiz Multitenancy Bypass
/(?s)DelegatorFactory/
]
@@ -141,24 +145,45 @@ if (security.hasPermission('ENTITY_MAINT', session)) {
// Groovy Sandbox with SecureASTCustomizer
SecureASTCustomizer secureCustomizer = new SecureASTCustomizer()
secureCustomizer.with {
- // Import whitelist - only safe OFBiz entity classes
- setImportsWhitelist([
- 'org.apache.ofbiz.entity.GenericValue',
- 'org.apache.ofbiz.entity.model.ModelEntity',
- 'org.apache.ofbiz.entity.condition.EntityCondition',
- 'org.apache.ofbiz.entity.condition.EntityOperator',
- 'org.apache.ofbiz.entity.util.EntityQuery',
- 'org.apache.ofbiz.entity.util.EntityFindOptions',
- 'java.util.List',
- 'java.util.Map',
- 'java.util.Set'
+ // Imports and static imports both use disallowed-list (blocklist)
mode instead of an
+ // allowed-list. setIndirectImportCheckEnabled(true) below makes
SecureASTCustomizer check
+ // the *receiver type* of every method call, constructor call, and
method pointer expression
+ // in the script against this same imports configuration - not just
against explicit import
+ // statements (which the script can never write anyway, since
KEYWORD_IMPORT is disallowed
+ // below). An allowed-list here would therefore have to include every
legitimate type ever
+ // used as a receiver (String, Delegator, EntityFindOptions, etc.),
and a narrow allowed-list
+ // combined with indirect checks rejects every expression in the
script, not just imports.
+ // Actual receiver-type restriction for method calls is still tightly
enforced separately by
+ // setAllowedReceivers below; this blocklist only needs to stop
indirect FQCN construction
+ // (e.g. "new java.lang.ProcessBuilder(...)") of genuinely dangerous
classes, matching the
+ // pattern already used in org.apache.ofbiz.base.util.GroovyUtil's
eval() sandbox.
+ // SecureASTCustomizer throws IllegalArgumentException if both an
allowed list and a
+ // disallowed list are set for the same allowed/disallowed pair, so
only the disallowed
+ // setters are used for imports, star imports, static imports and
static star imports here -
+ // do not add any setAllowed(Static)(Star)Imports call alongside these.
+ setDisallowedImports([
+ 'java.lang.Runtime',
+ 'java.lang.ProcessBuilder',
+ 'java.lang.ClassLoader',
+ 'java.lang.Thread',
+ 'java.lang.reflect.Method',
+ 'java.lang.reflect.Field',
+ 'java.net.Socket',
+ 'java.net.ServerSocket',
+ 'groovy.lang.GroovyShell',
+ 'groovy.lang.GroovyClassLoader'
+ ])
+ setDisallowedStarImports([])
+ setDisallowedStaticImports([
+ 'java.lang.Runtime.getRuntime',
+ 'java.lang.Runtime.exec',
+ 'java.lang.System.exit',
+ 'java.lang.Class.forName'
])
- setStarImportsWhitelist([])
- setStaticImportsWhitelist([])
- setStaticStarImportsWhitelist([])
+ setDisallowedStaticStarImports([])
setIndirectImportCheckEnabled(true)
// Constant types whitelist
- setConstantTypesClassesWhiteList([
+ setAllowedConstantTypesClasses([
Object, String, Integer, Long, Float, Double, Boolean,
Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Boolean.TYPE,
BigDecimal, BigInteger,
@@ -170,14 +195,15 @@ if (security.hasPermission('ENTITY_MAINT', session)) {
List, Map, Set
])
// Token and statement restrictions
- setTokensBlacklist([KEYWORD_PACKAGE, KEYWORD_IMPORT])
- setStatementsBlacklist([
+ setDisallowedTokens([KEYWORD_PACKAGE, KEYWORD_IMPORT])
+ setDisallowedStatements([
WhileStatement, ForStatement,
SwitchStatement
])
- setExpressionsBlacklist([MethodPointerExpression])
+ setDisallowedExpressions([MethodPointerExpression])
// Receiver whitelist - only safe OFBiz entity operations
- setReceiversWhiteList([
+ setAllowedReceivers([
+ 'java.lang.Object',
'org.apache.ofbiz.entity.Delegator',
'org.apache.ofbiz.entity.util.EntityQuery',
'org.apache.ofbiz.entity.util.EntityFindOptions',