View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.introspection;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertNotNull;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  import static org.junit.jupiter.api.Assertions.fail;
24  
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.LinkedHashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import org.apache.commons.jexl3.JexlArithmetic;
32  import org.apache.commons.jexl3.JexlBuilder;
33  import org.apache.commons.jexl3.JexlContext;
34  import org.apache.commons.jexl3.JexlEngine;
35  import org.apache.commons.jexl3.JexlException;
36  import org.apache.commons.jexl3.JexlExpression;
37  import org.apache.commons.jexl3.JexlScript;
38  import org.apache.commons.jexl3.JexlTestCase;
39  import org.apache.commons.jexl3.MapContext;
40  import org.apache.commons.jexl3.annotations.NoJexl;
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.junit.jupiter.api.Test;
44  
45  /**
46   * Tests sandbox features.
47   */
48  @SuppressWarnings({ "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes" })
49  class SandboxTest extends JexlTestCase {
50      public abstract static class AbstractCallMeNot {
51          public @NoJexl String NONO = "should not be accessible!";
52  
53          public String allowInherit() {
54              return "this is allowed";
55          }
56  
57          @NoJexl
58          public void callMeNot() {
59              fail("should not be callable!");
60          }
61      }
62  
63      public static class Arithmetic350 extends JexlArithmetic {
64          // cheat and keep the map builder around
65          MapBuilder mb = new org.apache.commons.jexl3.internal.MapBuilder(3);
66  
67          public Arithmetic350(final boolean astrict) {
68              super(astrict);
69          }
70  
71          Map<?, ?> getLastMap() {
72              return (Map<Object, Object>) mb.create();
73          }
74  
75          @Override
76          public MapBuilder mapBuilder(final int size, final boolean extended) {
77              return mb;
78          }
79      }
80  
81      @NoJexl
82      public interface CantCallMe {
83          void tryMe();
84      }
85  
86      public static class CantSeeMe {
87          public boolean doIt() {
88              return false;
89          }
90      }
91  
92      public static class Foo extends AbstractCallMeNot implements CantCallMe, TryCallMe {
93          String name;
94          public String alias;
95  
96          public Foo(final String name) {
97              this.name = name;
98              this.alias = name + "-alias";
99          }
100 
101         public @NoJexl Foo(final String name, final String notcallable) {
102             throw new UnsupportedOperationException("should not be callable!");
103         }
104 
105         @NoJexl
106         public String cantCallMe() {
107             throw new UnsupportedOperationException("should not be callable!");
108         }
109 
110         public int doIt() {
111             return 42;
112         }
113 
114         public String getName() {
115             return name;
116         }
117 
118         public String Quux() {
119             return name + "-quux";
120         }
121 
122         public void setName(final String name) {
123             this.name = name;
124         }
125 
126         @Override
127         public void tryMe() {
128             throw new UnsupportedOperationException("should not be callable!");
129         }
130 
131         @Override
132         public void tryMeARiver() {
133             throw new UnsupportedOperationException("should not be callable!");
134         }
135     }
136 
137     public static class Foo386 implements SomeInterface {
138         @Override
139         public int bar() {
140             return 42;
141         }
142     }
143 
144     public static class Foo42 {
145         public int getFoo() {
146             return 42;
147         }
148     }
149 
150     public static class Foo43 extends Foo42 {
151         @Override
152         @NoJexl
153         public int getFoo() {
154             return 43;
155         }
156     }
157 
158     public static class Foo44 extends Foo43 {
159         @Override
160         public int getFoo() {
161             return 44;
162         }
163     }
164 
165     public abstract static class Operation {
166         protected final int base;
167 
168         public Operation(final int sz) {
169             base = sz;
170         }
171 
172         public abstract int nonCallable(int y);
173 
174         public abstract int someOp(int x);
175     }
176 
177     public static class Operation2 extends Operation {
178         public Operation2(final int sz) {
179             super(sz);
180         }
181 
182         @Override
183         public int nonCallable(final int y) {
184             throw new UnsupportedOperationException("do NOT call");
185         }
186 
187         @Override
188         public int someOp(final int x) {
189             return base + x;
190         }
191     }
192 
193     public static class Quux386 extends Foo386 {
194         @Override
195         public int bar() {
196             return -42;
197         }
198     }
199 
200     public interface SomeInterface {
201         int bar();
202     }
203 
204     public interface TryCallMe {
205         @NoJexl
206         void tryMeARiver();
207     }
208 
209     static final Log LOGGER = LogFactory.getLog(SandboxTest.class.getName());
210 
211     public SandboxTest() {
212         super("SandboxTest");
213     }
214 
215     @Test
216     void testCantSeeMe() {
217         final JexlContext jc = new MapContext();
218         final String expr = "foo.doIt()";
219         JexlScript script;
220         Object result;
221 
222         final JexlSandbox sandbox = new JexlSandbox(false);
223         sandbox.allow(Foo.class.getName());
224         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
225 
226         jc.set("foo", new CantSeeMe());
227         script = sjexl.createScript(expr);
228         assertThrows(JexlException.class, () -> script.execute(jc));
229 
230         jc.set("foo", new Foo("42"));
231         result = script.execute(jc);
232         assertEquals(42, ((Integer) result).intValue());
233     }
234 
235     @Test
236     void testCtorAllow() {
237         final String expr = "new('" + Foo.class.getName() + "', '42')";
238         JexlScript script;
239         Object result;
240 
241         final JexlSandbox sandbox = new JexlSandbox();
242         sandbox.allow(Foo.class.getName()).execute("");
243         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
244 
245         script = sjexl.createScript(expr);
246         result = script.execute(null);
247         assertEquals("42", ((Foo) result).getName());
248     }
249 
250     @Test
251     void testCtorBlock() {
252         final String expr = "new('" + Foo.class.getName() + "', '42')";
253         final JexlScript script = JEXL.createScript(expr);
254         Object result;
255         result = script.execute(null);
256         assertEquals("42", ((Foo) result).getName());
257 
258         final JexlSandbox sandbox = new JexlSandbox();
259         sandbox.block(Foo.class.getName()).execute("");
260         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
261 
262         final JexlScript script2 = sjexl.createScript(expr);
263         assertThrows(JexlException.Method.class, () -> script2.execute(null), "ctor should not be accessible");
264 
265     }
266 
267     @Test
268     void testGetAllow() {
269         final Foo foo = new Foo("42");
270         final String expr = "foo.alias";
271         JexlScript script;
272         Object result;
273 
274         final JexlSandbox sandbox = new JexlSandbox();
275         sandbox.allow(Foo.class.getName()).read("alias");
276         sandbox.get(Foo.class.getName()).read().alias("alias", "ALIAS");
277         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
278 
279         script = sjexl.createScript(expr, "foo");
280         result = script.execute(null, foo);
281         assertEquals(foo.alias, result);
282 
283         script = sjexl.createScript("foo.ALIAS", "foo");
284         result = script.execute(null, foo);
285         assertEquals(foo.alias, result);
286     }
287 
288     @Test
289     void testGetBlock() {
290         final String expr = "foo.alias";
291         final JexlScript script = JEXL.createScript(expr, "foo");
292         final Foo foo = new Foo("42");
293         Object result;
294         result = script.execute(null, foo);
295         assertEquals(foo.alias, result);
296 
297         final JexlSandbox sandbox = new JexlSandbox();
298         sandbox.block(Foo.class.getName()).read("alias");
299         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
300 
301         final JexlScript script2 = sjexl.createScript(expr, "foo");
302         assertThrows(JexlException.Property.class, () -> script2.execute(null, foo), "alias should not be accessible");
303     }
304 
305     @Test
306     void testGetNullKeyAllowed0() {
307         final JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create();
308         final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]");
309         final Object o = expression.evaluate(null);
310         assertEquals("foo", o);
311     }
312 
313     @Test
314     void testGetNullKeyAllowed1() {
315         final JexlSandbox sandbox = new JexlSandbox(true, true);
316         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
317         p.read().add("quux");
318         final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
319         // cant read quux
320         final String q = "'quux'"; // quotes are important!
321         final JexlExpression expression = jexl.createExpression("{" + q + " : 'foo'}[" + q + "]");
322         assertTrue(assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
323 
324         // can read foo, null
325         for (final String k : Arrays.asList("'foo'", "null")) {
326             final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
327             final Object o = expression2.evaluate(null);
328             assertEquals("foo", o);
329         }
330     }
331 
332     @Test
333     void testGetNullKeyBlocked() {
334         final JexlSandbox sandbox = new JexlSandbox(true, true);
335         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
336         p.read().add(null);
337         p.read().add("quux");
338         // can read bar
339         final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
340         final JexlExpression e0 = jexl.createExpression("{'bar' : 'foo'}['bar']");
341         final Object r0 = e0.evaluate(null);
342         assertEquals("foo", r0);
343         // cannot read quux, null
344         for (final String k : Arrays.asList("'quux'", "null")) {
345             final JexlExpression expression = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
346             assertTrue(
347                     assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + k).getMessage().contains("undefined"));
348         }
349     }
350 
351     @Test
352     void testInheritedPermission0() {
353         final Foo386 foo = new Foo386();
354         final JexlSandbox sandbox = new JexlSandbox(false, true);
355         sandbox.permissions(SomeInterface.class.getName(), true, true, true, true);
356         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
357         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
358         assertEquals(42, (int) someOp.execute(null, foo));
359     }
360 
361     @Test
362     void testInheritedPermission1() {
363         final Quux386 foo = new Quux386();
364         final JexlSandbox sandbox = new JexlSandbox(false, true);
365         sandbox.permissions(Foo386.class.getName(), true, true, true, true);
366         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
367         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
368         assertEquals(-42, (int) someOp.execute(null, foo));
369     }
370 
371     @Test
372     void testMethodAllow() {
373         final Foo foo = new Foo("42");
374         final String expr = "foo.Quux()";
375         JexlScript script;
376         Object result;
377 
378         final JexlSandbox sandbox = new JexlSandbox();
379         sandbox.allow(Foo.class.getName()).execute("Quux");
380         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
381 
382         script = sjexl.createScript(expr, "foo");
383         result = script.execute(null, foo);
384         assertEquals(foo.Quux(), result);
385     }
386 
387     @Test
388     void testMethodBlock() {
389         final String expr = "foo.Quux()";
390         final JexlScript script = JEXL.createScript(expr, "foo");
391         final Foo foo = new Foo("42");
392         Object result;
393         result = script.execute(null, foo);
394         assertEquals(foo.Quux(), result);
395 
396         final JexlSandbox sandbox = new JexlSandbox();
397         sandbox.block(Foo.class.getName()).execute("Quux");
398         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
399 
400         final JexlScript script2 = sjexl.createScript(expr, "foo");
401         assertThrows(JexlException.Method.class, () -> script2.execute(null, foo), "Quux should not be accessible");
402     }
403 
404     @Test
405     void testMethodNoJexl() {
406         final Foo foo = new Foo("42");
407         // @formatter:off
408         final String[] exprs = {
409                 "foo.cantCallMe()",
410                 "foo.tryMe()",
411                 "foo.tryMeARiver()",
412                 "foo.callMeNot()",
413                 "foo.NONO",
414                 "new('org.apache.commons.jexl3.SandboxTest$Foo', 'one', 'two')" };
415         // @formatter:on
416         final JexlEngine sjexl = new JexlBuilder().strict(true).safe(false).create();
417         for (final String expr : exprs) {
418             final JexlScript script = sjexl.createScript(expr, "foo");
419             assertThrows(JexlException.class, () -> script.execute(null, foo), "should have not been possible");
420         }
421     }
422 
423     @Test
424     void testNoJexl312() {
425         final JexlContext ctxt = new MapContext();
426         final JexlEngine sjexl = new JexlBuilder().safe(false).strict(true).create();
427         final JexlScript foo = sjexl.createScript("x.getFoo()", "x");
428         assertThrows(JexlException.class, () -> foo.execute(ctxt, new Foo44()));
429     }
430 
431     @Test
432     void testNonInheritedPermission0() {
433         final Foo386 foo = new Foo386();
434         final JexlSandbox sandbox = new JexlSandbox(false, true);
435         sandbox.permissions(SomeInterface.class.getName(), false, true, true, true);
436         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
437         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
438         assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
439     }
440 
441     @Test
442     void testNonInheritedPermission1() {
443         final Quux386 foo = new Quux386();
444         final JexlSandbox sandbox = new JexlSandbox(false, true);
445         sandbox.permissions(Foo386.class.getName(), false, true, true, true);
446         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
447         final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
448         assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
449     }
450 
451     @Test
452     void testRestrict() {
453         final JexlContext context = new MapContext();
454         context.set("System", System.class);
455         final JexlSandbox sandbox = new JexlSandbox();
456         // only allow call to currentTimeMillis (avoid exit, gc, loadLibrary, etc.)
457         sandbox.allow(System.class.getName()).execute("currentTimeMillis");
458         // cannot create a new file
459         sandbox.block(java.io.File.class.getName()).execute("");
460         final JexlEngine sjexl = new JexlBuilder().permissions(JexlPermissions.UNRESTRICTED).sandbox(sandbox).safe(false).strict(true).create();
461         Object result;
462         final JexlScript script1 = sjexl.createScript("System.exit()");
463         assertThrows(JexlException.class, () -> script1.execute(context), "should not allow calling exit!");
464         final JexlScript script2 = sjexl.createScript("System.exit(1)");
465         assertThrows(JexlException.class, () -> script2.execute(context), "should not allow calling exit!");
466         final JexlScript script3 = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')");
467         assertThrows(JexlException.class, () -> script3.execute(context), "should not allow creating a file");
468         final JexlScript script4 = sjexl.createScript("System.currentTimeMillis()");
469         result = script4.execute(context);
470         assertNotNull(result);
471     }
472 
473     @Test
474     void testSandboxInherit0() {
475         Object result;
476         final JexlContext ctxt = null;
477         final List<String> foo = new ArrayList<>();
478         final JexlSandbox sandbox = new JexlSandbox(false, true);
479         sandbox.allow(List.class.getName());
480 
481         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
482         final JexlScript method = sjexl.createScript("foo.add(y)", "foo", "y");
483         final JexlScript set = sjexl.createScript("foo[x] = y", "foo", "x", "y");
484         final JexlScript get = sjexl.createScript("foo[x]", "foo", "x");
485 
486         result = method.execute(ctxt, foo, "nothing");
487         assertTrue((boolean) result);
488         result = get.execute(null, foo, Integer.valueOf(0));
489         assertEquals("nothing", result);
490         result = set.execute(null, foo, Integer.valueOf(0), "42");
491         assertEquals("42", result);
492         result = get.execute(null, foo, Integer.valueOf(0));
493         assertEquals("42", result);
494     }
495 
496     @Test
497     void testSandboxInherit1() {
498         Object result;
499         final JexlContext ctxt = null;
500         final Operation2 foo = new Operation2(12);
501         final JexlSandbox sandbox = new JexlSandbox(false, true);
502         sandbox.allow(Operation.class.getName());
503         sandbox.block(Operation.class.getName()).execute("nonCallable");
504         // sandbox.block(Foo.class.getName()).execute();
505         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
506         final JexlScript someOp = sjexl.createScript("foo.someOp(y)", "foo", "y");
507         result = someOp.execute(ctxt, foo, Integer.valueOf(30));
508         assertEquals(42, (int) result);
509         final JexlScript nonCallable = sjexl.createScript("foo.nonCallable(y)", "foo", "y");
510         assertThrows(JexlException.class, () -> nonCallable.execute(null, foo, Integer.valueOf(0)));
511     }
512 
513     @Test
514     void testSetAllow() {
515         final Foo foo = new Foo("42");
516         final String expr = "foo.alias = $0";
517         JexlScript script;
518         Object result;
519 
520         final JexlSandbox sandbox = new JexlSandbox();
521         sandbox.allow(Foo.class.getName()).write("alias");
522         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
523 
524         script = sjexl.createScript(expr, "foo", "$0");
525         result = script.execute(null, foo, "43");
526         assertEquals("43", result);
527         assertEquals("43", foo.alias);
528     }
529 
530     @Test
531     void testSetBlock() {
532         final String expr = "foo.alias = $0";
533         final JexlScript script1 = JEXL.createScript(expr, "foo", "$0");
534         final Foo foo = new Foo("42");
535         Object result;
536         result = script1.execute(null, foo, "43");
537         assertEquals("43", result);
538 
539         final JexlSandbox sandbox = new JexlSandbox();
540         sandbox.block(Foo.class.getName()).write("alias");
541         final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
542 
543         final JexlScript script2 = sjexl.createScript(expr, "foo", "$0");
544         assertThrows(JexlException.class, () -> script2.execute(null, foo, "43"), "alias should not be accessible");
545 
546     }
547 
548     @Test
549     void testSetNullKeyAllowed0() {
550         final Arithmetic350 a350 = new Arithmetic350(true);
551         final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(new JexlSandbox(true)).create();
552         final JexlContext jc = new MapContext();
553         final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'");
554         expression.evaluate(jc);
555         final Map<?, ?> map = a350.getLastMap();
556         assertEquals("bar", map.get(null));
557     }
558 
559     @Test
560     void testSetNullKeyAllowed1() {
561         final Arithmetic350 a350 = new Arithmetic350(true);
562         final JexlSandbox sandbox = new JexlSandbox(true, true);
563         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
564         p.write().add("quux");
565         final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
566         // cannot write quux
567         final String q = "'quux'"; // quotes are important!
568         final JexlExpression expression1 = jexl.createExpression("{" + q + " : 'foo'}[" + q + "] = '42'");
569         assertTrue(assertThrows(JexlException.Property.class, () -> expression1.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
570         // can write bar, null
571         JexlExpression expression2 = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
572         expression2.evaluate(null);
573         Map<?, ?> map = a350.getLastMap();
574         assertEquals("42", map.get("bar"));
575         map.clear();
576         expression2 = jexl.createExpression("{null : 'foo'}[null] = '42'");
577         expression2.evaluate(null);
578         map = a350.getLastMap();
579         assertEquals("42", map.get(null));
580     }
581 
582     @Test
583     void testSetNullKeyBlocked() {
584         final Arithmetic350 a350 = new Arithmetic350(true);
585         final JexlSandbox sandbox = new JexlSandbox(true, true);
586         final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
587         p.write().add(null);
588         p.write().add("quux");
589         final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
590         // can write bar
591         final JexlExpression expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
592         expression.evaluate(null);
593         final Map<?, ?> map = a350.getLastMap();
594         assertEquals("42", map.get("bar"));
595         // cannot write quux, null
596         for (final String k : Arrays.asList("'quux'", "null")) {
597             final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "] = '42'");
598             assertTrue(assertThrows(JexlException.Property.class, () -> expression2.evaluate(null), "should have blocked " + k).getMessage()
599                     .contains("undefined"));
600         }
601     }
602     interface I{}
603     static class A implements I{}
604     static class B extends A{}
605 
606     @Test
607     void testPermissionOrder() {
608         // permissions should not be dependent on order of evaluation
609         final JexlSandbox sandboxAB = new JexlSandbox(false, true);
610         sandboxAB.permissions(I.class.getName(), true, true, true, false);
611         assertEquals("allow{all}", sandboxAB.get(A.class.getName()).write().toString());
612         assertEquals("allow{all}", sandboxAB.get(B.class.getName()).write().toString());
613         final JexlSandbox sandboxBA = new JexlSandbox(false, true);
614         sandboxBA.permissions(I.class.getName(), true, true, true, false);
615         assertEquals("allow{all}", sandboxBA.get(B.class.getName()).write().toString());
616         assertEquals("allow{all}", sandboxBA.get(A.class.getName()).write().toString());
617     }
618 
619     @Test
620     void testIssue424() {
621         final JexlSandbox sandbox = new JexlSandbox(false, true);
622         sandbox.permissions(Map.class.getName(), true, true, true, true);
623         final String jexlCode = "x.foo = 'bar'";
624         final JexlEngine engine = new JexlBuilder()
625                     .sandbox(sandbox)
626                     .safe(false)
627                     .strict(true).create();
628         final JexlContext context = new MapContext();
629         final Map<String, Object> x = new LinkedHashMap<>();
630         context.set("x",  x);
631         final Object result = engine.createScript(jexlCode).execute(context);
632         assertEquals("bar", result);
633         assertEquals("bar", x.get("foo"));
634     }
635 
636 }