From the translator: In developing the CUBA Platform, we have laid into this framework the ability to execute custom scripts for more flexible customization of the business logic of applications. Whether this opportunity is good or bad (and we are talking not only about CUBA), there are many disputes, but the fact that control over the execution of user scripts is necessary is not a matter for anyone. One of the useful features of Groovy for managing the execution of custom scripts is presented in this translation of the Cédric Champeau article. Despite the fact that he recently left the Groovy development team, the community of programmers, apparently, for a long time we will enjoy the fruits of his work.
One of the most commonly used ways to use Groovy is scripting, since Groovy makes it easy to execute code dynamically, in runtime. Depending on the application, the scripts may be located in different places: the file system, the database, remote services ... but most importantly, the developer of the application executing the scripts does not necessarily write them himself. Moreover, scripts can work in a limited environment (limited memory, limit on the number of file descriptors, execution time ...), or you may want to prevent the user from using all the features of the language in the script.
This post will tell you
SecureASTCustomizer
For example, imagine that you need to do so that the user can calculate mathematical expressions. One implementation option is to embed an internal DSL, create a parser, and finally, an interpreter for these expressions. This, of course, will have to work, but if you need to improve performance, for example, by generating bytecode for expressions instead of calculating them in the interpreter or using caching of classes generated in runtime, then Groovy is a great option.
There are many options described in the documentation , but the simplest example is simply using the Eval
class:
Example.java
int sum = (Integer) Eval.me("1+1");
Code 1+1
parsed, compiled into bytecode, Groovy is loaded and executed in runtime. Of course, in this sample code is very simple, and you will need to add parameters, but the idea is that the executable code can be arbitrary. And this may not be exactly what you need. In the calculator, you need to resolve something like these:
1+1 x+y 1+(2*x)**y cos(alpha)*r v=1+x
but certainly not
println 'Hello' (0..100).each { println 'Blah' } Pong p = new Pong() println(new File('/etc/passwd').text) System.exit(-1) Eval.me('System.exit(-1)') // a script within a script!
This is where the difficulties begin, and it also becomes clear that we need to solve several problems:
The calculator example is fairly simple, but for more complex DSLs, people may not notice that they are writing problem code, especially if DSL is so simple that non-developers can use it .
A few years ago I was in such a situation. I developed an engine that executed Groovy “scripts” written by linguists. One of the problems, for example, was that they could inadvertently create an endless loop. The code was executed on the server, and there appeared a stream devouring 100% of the CPU, after which it was necessary to restart the application server. I had to look for a way to solve the problem without affecting DSL, tools or application performance.
In fact, many people have similar needs. Over the past 4 years, I have been talking to a lot of people who have had the same question: How can I prevent users from messing around with Groovy scripts?
At that time I already had my own decision and I knew that other people also developed something similar. In the end, Guillaume Laforge suggested that I create a mechanism in Groovy core to help solve these problems. It appeared in Groovy 1.8.0 as a compiler customizer .
Compiler customizers are a set of classes that modify the process of compiling Groovy scripts. You can write your customizer, but Groovy delivers:
The customizer of AST transformations helped me solve the problem of an infinite loop using the @ThreadInterrupt
transformation, but SecureASTCustomizer is the thing that is probably misunderstood in the overwhelming majority of cases.
I should apologize for that. Then I could not think of a better name. The most important part in the name “SecureASTCustomizer” is AST . The purpose of this mechanism was to restrict access to certain functions of the AST. The word "secure" in the title is generally superfluous, and I will explain why. There is even a post on Jenkins Kawaguchi’s famous Jenkins blog called “Disastrous Groovy SecureASTCustomizer” . And everything is very well written there. SecureASTCustomizer was created without sandboxing. It was created to restrict the language at compile time, but not execution. Now I think the best name would be GrammarCustomizer . But, as you undoubtedly know, there are three difficulties in computer science: invalidation of a cache, inventing names and an error per unit.
Now imagine that you consider the secure AST customizer as a means to ensure the security of your script, and your task is not to allow the user to call System.exit
from a script. The documentation says that calls can be banned in special receivers, creating black or white lists. If security is needed, I always recommend whitelists that strictly prescribe what is allowed, but not blacklists that prohibit anything. Because hackers always think about something that you could not take into account. I will give an example.
Here's how to set up a primitive sandbox script engine using SecureASTCustomizer
. Although I could write them in Groovy, I give examples of Java configuration, so that the difference between the integration code and the scripts is more explicit.
public class Sandbox { public static void main(String[] args) { CompilerConfiguration conf = new CompilerConfiguration(); SecureASTCustomizer customizer = new SecureASTCustomizer(); customizer.setReceiversBlackList(Arrays.asList(System.class.getName())); conf.addCompilationCustomizers(customizer); GroovyShell shell = new GroovyShell(conf); Object v = shell.evaluate("System.exit(-1)"); System.out.println("Result = " +v); } }
System
class as the receiver of method calls is blacklistedIf you run this class, then during the execution of the script an error will crash:
General error during canonicalization: Method calls not allowed on [java.lang.System] java.lang.SecurityException: Method calls not allowed on [java.lang.System]
This output is generated by an application with a secure AST customizer, which prevents the System
class from running. Success! So we protected our script! But wait ...
Protect, speak? And what if I do this:
def c = System c.exit(-1)
If you run the program again, you will see that it crashes without error and without displaying the result on the screen. The exit code of the process is -1, which means that the user script has been launched! What happened? During compilation, the secure AST customizer is unable to recognize that c.exit
is a call to the System
method in principle, because it works at the AST level! It analyzes the method call, but in this case the method call is c.exit(-1)
, then it determines the receiver and checks whether it is on the white (or black) list. In this case, the receiver is c
, this variable is declared via def , and this is the same as declaring it as Object
, and the secure AST customizer will think that the type of the variable c
is Object
, and not System
!
In general, there are many ways to circumvent the various configurations created on the secure AST customizer. Here are some cool ones:
((Object)System).exit(-1) Class.forName('java.lang.System').exit(-1) ('java.lang.System' as Class).exit(-1) import static java.lang.System.exit exit(-1)
and there may be a lot more. The dynamic nature of Groovy eliminates the ability to fix these problems during compilation. However, a solution exists. One option is to rely on the standard JVM security manager. However, this is a ponderous and voluminous solution for the entire system at once, and this is tantamount to shooting a gun at sparrows. In addition, it does not work in all cases, for example, if you want to prohibit reading files, but not creating ...
This restriction - rather, for many of us, rather a disappointment - led to the creation of a solution based on checks during execution . This type of check has no such problems. For example, because you will know the actual receiver type of the message before you start validating a method call. Of particular interest are the following implementations:
However, none of these implementations are absolutely reliable and secure. For example, the Kosuke version is based on the hacking of the internal call site caching implementation. The problem is that it is not compatible with the invokedynamic version of Groovy, and these inner classes will not be in future versions of Groovy. Simon's version, on the other hand, is based on AST transformations, but leaves many possible holes.
In the end, me and my friends Corinne Krish, Fabrizia Matrat and Sebastian Blanc decided to create a new sandboxing mechanism in runtime that would not have problems like these projects. We started to implement it at the hackathon in Nice, and at the Greach conference last year we made a report about it . This mechanism is based on AST transformations and essentially rewrites the code in order to check before each method call, attempt to access the class field, increment a variable, binary expression ... This implementation is still not ready, and not a lot of work has been done on it. As I realized that the problem with the methods and parameters called via "implicit this" has not yet been solved, as, for example, in the builders:
xml { cars { // cars is a method call on an implicit this: "this".cars(...) car(make:'Renault', model: 'Clio') } }
To date, I still have not found a way to solve this problem because of Groovy’s meta-object protocol architecture, which is based on the fact that the receiver throws an exception when it cannot find a method before switching to another receiver. In short, this means that you cannot find out the type of receiver before actually calling a method. And if the call was passed, then it's too late ...
Until recently, I did not have an optimal solution to this problem for the case in which the executable script uses the dynamic properties of the language. But now it is time to explain how you can significantly improve the situation if you are ready to sacrifice a little the dynamism of the language.
Let's return to the main problem with SecureASTCustomizer: it works with an abstract syntax tree and does not have information about specific message types and receivers. But with Groovy 2, Groovy has an extra compilation, and in Groovy 2.1 we added extensions for type checking .
Type checking extensions are a very powerful thing: they allow the Groovy DSL developer to help the compiler with type inference, and also allow you to generate compilation errors in cases where they do not normally occur. These extensions are used internally by Groovy to support a static compiler, for example when implementing traits or a markup template engine .
What if instead of using the results of the parser, we could rely on information from the type checking mechanism? Take the code that our hacker tried to write:
((Object)System).exit(-1)
If you enable type checking, the code will not compile:
1 compilation error: [Static type checking] - Cannot find matching method java.lang.Object#exit(java.lang.Integer). Please check if the declared type is right and if the method exists.
So, this code is no longer compiled. And what if we take this code:
def c = System c.exit(-1)
As you can see, it passes the type check, being wrapped in a method and executed using the groovy
command:
@groovy.transform.TypeChecked // or even @CompileStatic void foo() { def c = System c.exit(-1) } foo()
The type checker detects that the exit
method is called from the System
class and is valid. Here it does not help us. But what we know is that if this code passes type checking, it means that the compiler recognizes the call to the receiver with the type System
. In general, the idea is to bar a call using a type checking extension.
Before we dive into sandboxing in detail, we’ll try to “secure” our script with the usual type checking extension. Registering such an extension is easy: just set the extensions
parameter for the @TypeChecked
annotation (or @CompileStatic
if you are using static compilation):
@TypeChecked(extensions=['SecureExtension1.groovy']) void foo() { def c = System c.exit(-1) } foo()
An extension search will occur in the classpath in source code format (you can make precompiled extensions for type checking, but we do not consider them in this article):
SecureExtension1.groovy
onMethodSelection { expr, methodNode -> if (methodNode.declaringClass.name=='java.lang.System') { addStaticTypeError("Method call is not allowed!", expr) } }
System
That's all you need. Now run the code again and you will see a compilation error!
/home/cchampeau/tmp/securetest.groovy: 6: [Static type checking] - Method call is not allowed! @ line 6, column 3. c.exit(-1) ^ 1 error
This time, thanks to type checker, c
recognized as an instance of the System
class, and we can bar the call. This is a very simple example, and it doesn’t demonstrate all that can be done with AST’s secure customizer in terms of configuration. In the extension we wrote , the checks are hardcoded , but it might be better to make them customizable. So let's make an example more complicated.
Suppose your application calculates certain indicators for a document and allows users to customize them. In this case DSL:
score
variableSample custom script:
abs(cos(1+score))
This DSL is easy to configure. This is a variant of what we defined above:
Sandbox.java
CompilerConfiguration conf = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("java.lang.Math"); conf.addCompilationCustomizers(customizer); Binding binding = new Binding(); binding.setVariable("score", 2.0d); GroovyShell shell = new GroovyShell(binding,conf); Double userScore = (Double) shell.evaluate("abs(cos(1+score))"); System.out.println("userScore = " + userScore);
import static java.lang.Math.*
to all scriptsscore
available for the scriptThere are ways to cache scripts instead of parsing and compiling them every time. See the documentation for details.
So, our script works, but nothing prevents a hacker from running a malicious code. Since we plan to use type checking, I would recommend using the @CompileStatic
transform:
Implicitly adding the @CompileStatic
annotation to scripts is quite simple. You only need to update the compiler configuration:
ASTTransformationCustomizer astcz = new ASTTransformationCustomizer(CompileStatic.class); conf.addCompilationCustomizers(astcz);
Now if you try to run the script again, you will see a compilation error:
Script1.groovy: 1: [Static type checking] - The variable [score] is undeclared. @ line 1, column 11. abs(cos(1+score)) ^ Script1.groovy: 1: [Static type checking] - Cannot find matching method int#plus(java.lang.Object). Please check if the declared type is right and if the method exists. @ line 1, column 9. abs(cos(1+score)) ^ 2 errors
What happened? If you read the script from the point of view of the compiler, it becomes clear that it does not know anything about the "score" variable. But as a developer, you know that this is a double
variable, but the compiler cannot derive it. For this purpose, extensions for type checking are created: you can give the compiler additional information, and the compilation will be fine. In this case, we need to specify that the score
variable is of type double
.
Therefore, you can slightly change the way that the @CompileStatic
annotation is @CompileStatic
:
ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SecureExtension2.groovy")), CompileStatic.class);
This “emulates” code annotated by @CompileStatic(extensions=['SecureExtension2.groovy'])
. Now, of course, we will need to write an extension that will recognize the score
variable:
SecureExtension2.groovy
unresolvedVariable { var -> if (var.name=='score') { return makeDynamic(var, double_TYPE) } }
score
double
A full description of the DSL extensions for type checking can be found in this section of the documentation , but here is an example of a combined compilation mode: the compiler cannot define the score
variable. You, as a DSL developer, know that a variable actually exists and its type is double
, so makeDynamic
here to say: “ok, don't worry, I know what I'm doing, this variable can be defined dynamically with double
type ". That's all!
Now let's put it all together. We wrote one extension for type checking, which prevents calls to methods of the System
class, on the one hand, and the other, which defines the score
variable, on the other. So, if we connect them, we get the first full extension for type checking:
SecureExtension3.groovy
// disallow calls on System onMethodSelection { expr, methodNode -> if (methodNode.declaringClass.name=='java.lang.System') { addStaticTypeError("Method call is not allowed!", expr) } } // resolve the score variable unresolvedVariable { var -> if (var.name=='score') { return makeDynamic(var, double_TYPE) } }
Remember to update the configuration in your Java class to use the new extension for type checking:
ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SecureExtension3.groovy")), CompileStatic.class);
Run the code again - it still works. Now try this:
abs(cos(1+score)) System.exit(-1)
Script compilation will crash with the error:
Script1.groovy: 1: [Static type checking] - Method call is not allowed! @ line 1, column 19. abs(cos(1+score));System.exit(-1) ^ 1 error
Congratulations, you just wrote the first type-checking extension that prevents malicious code from running!
So, everything is going well, we can prohibit calls to methods of the System
class, but it seems that new vulnerabilities will soon be discovered, and we will need to prevent the launch of malicious code. So instead of hardcoding everything in an extension, we will try to make our extension universal and customizable. This is probably the most difficult, because there is no direct way to pass context to an extension for type checking. The idea is thus based on using the thread local variable (curve method, yes) to transfer configuration data to the type checker.
First, we will make the list of variables customizable. This is what Java code will look like:
Sandbox.java
public class Sandbox { public static final String VAR_TYPES = "sandboxing.variable.types"; public static final ThreadLocal<Map<String, Object>> COMPILE_OPTIONS = new ThreadLocal<>(); public static void main(String[] args) { CompilerConfiguration conf = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("java.lang.Math"); ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SecureExtension4.groovy")), CompileStatic.class); conf.addCompilationCustomizers(astcz); conf.addCompilationCustomizers(customizer); Binding binding = new Binding(); binding.setVariable("score", 2.0d); try { Map<String,ClassNode> variableTypes = new HashMap<String, ClassNode>(); variableTypes.put("score", ClassHelper.double_TYPE); Map<String,Object> options = new HashMap<String, Object>(); options.put(VAR_TYPES, variableTypes); COMPILE_OPTIONS.set(options); GroovyShell shell = new GroovyShell(binding, conf); Double userScore = (Double) shell.evaluate("abs(cos(1+score));System.exit(-1)"); System.out.println("userScore = " + userScore); } finally { COMPILE_OPTIONS.remove(); } } }
ThreadLocal
,SecureExtension4.groovy
variableTypes
— “ → ”score
options
—:
import static Sandbox.* def typesOfVariables = COMPILE_OPTIONS.get()[VAR_TYPES] unresolvedVariable { var -> if (typesOfVariables[var.name]) { return makeDynamic(var, typesOfVariables[var.name]) } }
thread local, , type checker . , unresolvedVariable
, , , type checker, . , . !
. , .
. , . , , . , System.exit
, :
java.lang.System#exit(int)
, Java, :
public class Sandbox { public static final String WHITELIST_PATTERNS = "sandboxing.whitelist.patterns"; // ... public static void main(String[] args) { // ... try { Map<String,ClassNode> variableTypes = new HashMap<String, ClassNode>(); variableTypes.put("score", ClassHelper.double_TYPE); Map<String,Object> options = new HashMap<String, Object>(); List<String> patterns = new ArrayList<String>(); patterns.add("java\\.lang\\.Math#"); options.put(VAR_TYPES, variableTypes); options.put(WHITELIST_PATTERNS, patterns); COMPILE_OPTIONS.set(options); GroovyShell shell = new GroovyShell(binding, conf); Double userScore = (Double) shell.evaluate("abs(cos(1+score));System.exit(-1)"); System.out.println("userScore = " + userScore); } finally { COMPILE_OPTIONS.remove(); } } }
java.lang.Math
:
import groovy.transform.CompileStatic import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.MethodNode import org.codehaus.groovy.ast.Parameter import org.codehaus.groovy.transform.stc.ExtensionMethodNode import static Sandbox.* @CompileStatic private static String prettyPrint(ClassNode node) { node.isArray()?"${prettyPrint(node.componentType)}[]":node.toString(false) } @CompileStatic private static String toMethodDescriptor(MethodNode node) { if (node instanceof ExtensionMethodNode) { return toMethodDescriptor(node.extensionMethodNode) } def sb = new StringBuilder() sb.append(node.declaringClass.toString(false)) sb.append("#") sb.append(node.name) sb.append('(') sb.append(node.parameters.collect { Parameter it -> prettyPrint(it.originType) }.join(',')) sb.append(')') sb } def typesOfVariables = COMPILE_OPTIONS.get()[VAR_TYPES] def whiteList = COMPILE_OPTIONS.get()[WHITELIST_PATTERNS] onMethodSelection { expr, MethodNode methodNode -> def descr = toMethodDescriptor(methodNode) if (!whiteList.any { descr =~ it }) { addStaticTypeError("You tried to call a method which is not allowed, what did you expect?: $descr", expr) } } unresolvedVariable { var -> if (typesOfVariables[var.name]) { return makeDynamic(var, typesOfVariables[var.name]) } }
MethodNode
, :
Script1.groovy: 1: [Static type checking] - You tried to call a method which is not allowed, what did you expect?: java.lang.System#exit(int) @ line 1, column 19. abs(cos(1+score));System.exit(-1) ^ 1 error
Exactly what is needed! , , . , ! , , . , ( foo.text
, foo.getText()
).
, type checker' "property selection", , . , , . , , — . .
SandboxingTypeCheckingExtension.groovy
import groovy.transform.CompileStatic import org.codehaus.groovy.ast.ClassCodeVisitorSupport import org.codehaus.groovy.ast.ClassHelper import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.MethodNode import org.codehaus.groovy.ast.Parameter import org.codehaus.groovy.ast.expr.PropertyExpression import org.codehaus.groovy.control.SourceUnit import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys import org.codehaus.groovy.transform.stc.ExtensionMethodNode import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport import static Sandbox.* class SandboxingTypeCheckingExtension extends GroovyTypeCheckingExtensionSupport.TypeCheckingDSL { @CompileStatic private static String prettyPrint(ClassNode node) { node.isArray()?"${prettyPrint(node.componentType)}[]":node.toString(false) } @CompileStatic private static String toMethodDescriptor(MethodNode node) { if (node instanceof ExtensionMethodNode) { return toMethodDescriptor(node.extensionMethodNode) } def sb = new StringBuilder() sb.append(node.declaringClass.toString(false)) sb.append("#") sb.append(node.name) sb.append('(') sb.append(node.parameters.collect { Parameter it -> prettyPrint(it.originType) }.join(',')) sb.append(')') sb } @Override Object run() { // Fetch white list of regular expressions of authorized method calls def whiteList = COMPILE_OPTIONS.get()[WHITELIST_PATTERNS] def typesOfVariables = COMPILE_OPTIONS.get()[VAR_TYPES] onMethodSelection { expr, MethodNode methodNode -> def descr = toMethodDescriptor(methodNode) if (!whiteList.any { descr =~ it }) { addStaticTypeError("You tried to call a method which is not allowed, what did you expect?: $descr", expr) } } unresolvedVariable { var -> if (isDynamic(var) && typesOfVariables[var.name]) { storeType(var, typesOfVariables[var.name]) handled = true } } // handling properties (like foo.text) is harder because the type checking extension // does not provide a specific hook for this. Harder, but not impossible! afterVisitMethod { methodNode -> def visitor = new PropertyExpressionChecker(context.source, whiteList) visitor.visitMethod(methodNode) } } private class PropertyExpressionChecker extends ClassCodeVisitorSupport { private final SourceUnit unit private final List<String> whiteList PropertyExpressionChecker(final SourceUnit unit, final List<String> whiteList) { this.unit = unit this.whiteList = whiteList } @Override protected SourceUnit getSourceUnit() { unit } @Override void visitPropertyExpression(final PropertyExpression expression) { super.visitPropertyExpression(expression) ClassNode owner = expression.objectExpression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER) if (owner) { if (expression.spreadSafe && StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf(owner, classNodeFor(Collection))) { owner = typeCheckingVisitor.inferComponentType(owner, ClassHelper.int_TYPE) } def descr = "${prettyPrint(owner)}#${expression.propertyAsString}" if (!whiteList.any { descr =~ it }) { addStaticTypeError("Property is not allowed: $descr", expression) } } } } }``` sandbox', assert' , , : ``Sandbox.java`` ```java public class Sandbox { public static final String WHITELIST_PATTERNS = "sandboxing.whitelist.patterns"; public static final String VAR_TYPES = "sandboxing.variable.types"; public static final ThreadLocal<Map<String, Object>> COMPILE_OPTIONS = new ThreadLocal<Map<String, Object>>(); public static void main(String[] args) { CompilerConfiguration conf = new CompilerConfiguration(); ImportCustomizer customizer = new ImportCustomizer(); customizer.addStaticStars("java.lang.Math"); ASTTransformationCustomizer astcz = new ASTTransformationCustomizer( singletonMap("extensions", singletonList("SandboxingTypeCheckingExtension.groovy")), CompileStatic.class); conf.addCompilationCustomizers(astcz); conf.addCompilationCustomizers(customizer); Binding binding = new Binding(); binding.setVariable("score", 2.0d); try { Map<String, ClassNode> variableTypes = new HashMap<String, ClassNode>(); variableTypes.put("score", ClassHelper.double_TYPE); Map<String, Object> options = new HashMap<String, Object>(); List<String> patterns = new ArrayList<String>(); // allow method calls on Math patterns.add("java\\.lang\\.Math#"); // allow constructors calls on File patterns.add("File#<init>"); // because we let the user call each/times/... patterns.add("org\\.codehaus\\.groovy\\.runtime\\.DefaultGroovyMethods"); options.put(VAR_TYPES, variableTypes); options.put(WHITELIST_PATTERNS, patterns); COMPILE_OPTIONS.set(options); GroovyShell shell = new GroovyShell(binding, conf); Object result; try { result = shell.evaluate("Eval.me('1')"); // error assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("System.exit(-1)"); // error assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("((Object)Eval).me('1')"); // error assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("new File('/etc/passwd').getText()"); // getText is not allowed assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } try { result = shell.evaluate("new File('/etc/passwd').text"); // getText is not allowed assert false; } catch (MultipleCompilationErrorsException e) { System.out.println("Successful sandboxing: "+e.getMessage()); } Double userScore = (Double) shell.evaluate("abs(cos(1+score))"); System.out.println("userScore = " + userScore); } finally { COMPILE_OPTIONS.remove(); } } }
Groovy JVM. , . , , , . , Groovy, sandboxing' (, , ).
, , . , . , , .
, sandboxing', , — SecureASTCustomizer
. , , : secure AST customizer , (, ), ( , ).
, : , , . Groovy . Groovy, , - pull request, - !
Source: https://habr.com/ru/post/445114/
All Articles