You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2021/06/04 16:05:45 UTC

[commons-jexl] branch master updated: JEXL-350: ensure null can be allowed/blocked as a property to read/write in sandbox, tests, verbosity

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

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new be81fa5  JEXL-350: ensure null can be allowed/blocked as a property to read/write in sandbox, tests, verbosity
be81fa5 is described below

commit be81fa5325fadb28a51b0140da6eb17cb8538429
Author: henrib <he...@apache.org>
AuthorDate: Fri Jun 4 18:05:25 2021 +0200

    JEXL-350: ensure null can be allowed/blocked as a property to read/write in sandbox, tests, verbosity
---
 .../apache/commons/jexl3/internal/MapBuilder.java  |   2 +-
 .../internal/introspection/SandboxUberspect.java   |  46 +++---
 .../commons/jexl3/introspection/JexlSandbox.java   |  25 +++-
 .../java/org/apache/commons/jexl3/BuilderTest.java |  76 ++++++++++
 .../java/org/apache/commons/jexl3/JXLTTest.java    |   8 +-
 .../apache/commons/jexl3/ParseFailuresTest.java    |  10 +-
 .../commons/jexl3/introspection/SandboxTest.java   | 157 +++++++++++++++++++--
 7 files changed, 283 insertions(+), 41 deletions(-)

diff --git a/src/main/java/org/apache/commons/jexl3/internal/MapBuilder.java b/src/main/java/org/apache/commons/jexl3/internal/MapBuilder.java
index b6aac4e..57b27e7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/MapBuilder.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/MapBuilder.java
@@ -41,7 +41,7 @@ public class MapBuilder implements JexlArithmetic.MapBuilder {
     }
 
     @Override
-    public Object create() {
+    public Map<Object,Object> create() {
         return map;
     }
 }
diff --git a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
index afb94a8..fcf73ef 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/introspection/SandboxUberspect.java
@@ -78,7 +78,7 @@ public final class SandboxUberspect implements JexlUberspect {
         } else {
             className = null;
         }
-        return className != null? uberspect.getConstructor(className, args) : null;
+        return className != null && className != JexlSandbox.NULL ? uberspect.getConstructor(className, args) : null;
     }
 
     @Override
@@ -86,7 +86,7 @@ public final class SandboxUberspect implements JexlUberspect {
         if (obj != null && method != null) {
             final Class<?> clazz = (obj instanceof Class) ? (Class<?>) obj : obj.getClass();
             final String actual = sandbox.execute(clazz, method);
-            if (actual != null) {
+            if (actual != null && actual != JexlSandbox.NULL) {
                 return uberspect.getMethod(obj, actual, args);
             }
         }
@@ -107,13 +107,20 @@ public final class SandboxUberspect implements JexlUberspect {
     public JexlPropertyGet getPropertyGet(final List<PropertyResolver> resolvers,
                                           final Object obj,
                                           final Object identifier) {
-        if (obj != null && identifier != null) {
-            final String property = identifier.toString();
-            final String actual = sandbox.read(obj.getClass(), property);
-            if (actual != null) {
-                 // no transformation, strict equality: use identifier before string conversion
-                final Object pty = actual == property? identifier : actual;
-                return uberspect.getPropertyGet(resolvers, obj, pty);
+        if (obj != null) {
+            if (identifier != null) {
+                final String property = identifier.toString();
+                final String actual = sandbox.read(obj.getClass(), property);
+                if (actual != null) {
+                    // no transformation, strict equality: use identifier before string conversion
+                    final Object pty = actual == property ? identifier : actual;
+                    return uberspect.getPropertyGet(resolvers, obj, pty);
+                }
+            } else {
+                final String actual = sandbox.read(obj.getClass(), null);
+                if (actual != JexlSandbox.NULL) {
+                     return uberspect.getPropertyGet(resolvers, obj, null);
+                }
             }
         }
         return null;
@@ -129,13 +136,20 @@ public final class SandboxUberspect implements JexlUberspect {
                                           final Object obj,
                                           final Object identifier,
                                           final Object arg) {
-        if (obj != null && identifier != null) {
-            final String property = identifier.toString();
-            final String actual = sandbox.write(obj.getClass(), property);
-            if (actual != null) {
-                 // no transformation, strict equality: use identifier before string conversion
-                final Object pty = actual == property? identifier : actual;
-                return uberspect.getPropertySet(resolvers, obj, pty, arg);
+        if (obj != null) {
+            if (identifier != null) {
+                final String property = identifier.toString();
+                final String actual = sandbox.write(obj.getClass(), property);
+                if (actual != null) {
+                    // no transformation, strict equality: use identifier before string conversion
+                    final Object pty = actual == property ? identifier : actual;
+                    return uberspect.getPropertySet(resolvers, obj, pty, arg);
+                }
+            } else {
+                final String actual = sandbox.write(obj.getClass(), null);
+                if (actual != JexlSandbox.NULL) {
+                    return uberspect.getPropertySet(resolvers, obj, null, arg);
+                }
             }
         }
         return null;
diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
index f55aa64..62d521c 100644
--- a/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
+++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlSandbox.java
@@ -67,6 +67,10 @@ import java.util.concurrent.ConcurrentHashMap;
  */
 public final class JexlSandbox {
     /**
+     * The marker string for explicitly disallowed null properties.
+     */
+    public static final String NULL = "?";
+    /**
      * The map from class names to permissions.
      */
     private final Map<String, Permissions> sandbox;
@@ -173,7 +177,7 @@ public final class JexlSandbox {
      *
      * @param clazz the class
      * @param name the property name
-     * @return null if not allowed, the name of the property to use otherwise
+     * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise
      */
     public String read(final Class<?> clazz, final String name) {
         return get(clazz).read().get(name);
@@ -196,7 +200,7 @@ public final class JexlSandbox {
      *
      * @param clazz the class
      * @param name the property name
-     * @return null if not allowed, the name of the property to use otherwise
+     * @return null (or NULL if name is null) if not allowed, the name of the property to use otherwise
      */
     public String write(final Class<?> clazz, final String name) {
         return get(clazz).write().get(name);
@@ -268,7 +272,7 @@ public final class JexlSandbox {
          * Whether a given name is allowed or not.
          *
          * @param name the method/property name to check
-         * @return null if not allowed, the actual name to use otherwise
+         * @return null (or NULL if name is null) if not allowed, the actual name to use otherwise
          */
         public String get(final String name) {
             return name;
@@ -313,7 +317,7 @@ public final class JexlSandbox {
 
         @Override
         public String get(final String name) {
-            return null;
+            return name == null? NULL : null;
         }
     };
 
@@ -349,7 +353,15 @@ public final class JexlSandbox {
 
         @Override
         public String get(final String name) {
-            return names == null ? name : names.get(name);
+            if (names == null) {
+                return name;
+            }
+            String actual = names.get(name);
+            // if null is not explicitly allowed, explicit null aka NULL
+            if (name == null && actual == null && !names.containsKey(null)) {
+                return JexlSandbox.NULL;
+            }
+            return actual;
         }
     }
 
@@ -377,7 +389,8 @@ public final class JexlSandbox {
 
         @Override
         public String get(final String name) {
-            return names != null && !names.contains(name) ? name : null;
+            // if name is null and contained in set, explicit null aka NULL
+            return names != null && !names.contains(name) ? name : name != null? null : NULL;
         }
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/BuilderTest.java b/src/test/java/org/apache/commons/jexl3/BuilderTest.java
new file mode 100644
index 0000000..81fecc2
--- /dev/null
+++ b/src/test/java/org/apache/commons/jexl3/BuilderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.commons.jexl3;
+
+import org.apache.commons.jexl3.internal.introspection.SandboxUberspect;
+import org.apache.commons.jexl3.introspection.JexlSandbox;
+import org.apache.commons.jexl3.introspection.JexlUberspect;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+
+/**
+ * Checking the builder basics.
+ */
+public class BuilderTest {
+    private static JexlBuilder builder() {
+        return new JexlBuilder();
+    }
+
+    @Test
+    public void testFlags() {
+        Assert.assertTrue(builder().antish(true).antish());
+        Assert.assertFalse(builder().antish(false).antish());
+        Assert.assertTrue(builder().cancellable(true).cancellable());
+        Assert.assertFalse(builder().cancellable(false).cancellable());
+        Assert.assertTrue(builder().safe(true).safe());
+        Assert.assertFalse(builder().safe(false).safe());
+        Assert.assertTrue(builder().silent(true).silent());
+        Assert.assertFalse(builder().silent(false).silent());
+        Assert.assertTrue(builder().lexical(true).lexical());
+        Assert.assertFalse(builder().lexical(false).lexical());
+        Assert.assertTrue(builder().lexicalShade(true).lexicalShade());
+        Assert.assertFalse(builder().lexicalShade(false).lexicalShade());
+        Assert.assertTrue(builder().silent(true).silent());
+        Assert.assertFalse(builder().silent(false).silent());
+        Assert.assertTrue(builder().strict(true).strict());
+        Assert.assertFalse(builder().strict(false).strict());
+    }
+
+    @Test
+    public void testValues() {
+        Assert.assertEquals(1, builder().collectMode(1).collectMode());
+        Assert.assertEquals(0, builder().collectMode(0).collectMode());
+        Assert.assertEquals(32, builder().cacheThreshold(32).cacheThreshold());
+        Assert.assertEquals(8, builder().stackOverflow(8).stackOverflow());
+    }
+
+    @Test
+    public void testOther() {
+        ClassLoader cls = getClass().getClassLoader().getParent();
+        Assert.assertEquals(cls, builder().loader(cls).loader());
+        Charset cs = Charset.forName("UTF16");
+        Assert.assertEquals(cs, builder().charset(cs).charset());
+        Assert.assertEquals(cs, builder().loader(cs).charset());
+        JexlUberspect u0 = builder().create().getUberspect();
+        JexlSandbox sandbox = new JexlSandbox();
+        JexlUberspect uberspect = new SandboxUberspect(u0, sandbox);
+        Assert.assertEquals(sandbox, builder().sandbox(sandbox).sandbox());
+        Assert.assertEquals(uberspect, builder().uberspect(uberspect).uberspect());
+    }
+}
diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
index 8e4de30..75457e0 100644
--- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java
+++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java
@@ -366,7 +366,7 @@ public class JXLTTest extends JexlTestCase {
         } catch (final JxltEngine.Exception xjexl) {
             // expected
             final String xmsg = xjexl.getMessage();
-            LOGGER.warn(xmsg);
+            LOGGER.debug(xmsg);
         }
     }
 
@@ -380,7 +380,7 @@ public class JXLTTest extends JexlTestCase {
         } catch (final JxltEngine.Exception xjexl) {
             // expected
             final String xmsg = xjexl.getMessage();
-            LOGGER.warn(xmsg);
+            LOGGER.debug(xmsg);
         }
     }
 
@@ -395,7 +395,7 @@ public class JXLTTest extends JexlTestCase {
         } catch (final JxltEngine.Exception xjexl) {
             // expected
             final String xmsg = xjexl.getMessage();
-            LOGGER.warn(xmsg);
+            LOGGER.debug(xmsg);
         }
     }
 
@@ -409,7 +409,7 @@ public class JXLTTest extends JexlTestCase {
         } catch (final JxltEngine.Exception xjexl) {
             // expected
             final String xmsg = xjexl.getMessage();
-            LOGGER.warn(xmsg);
+            LOGGER.debug(xmsg);
         }
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/ParseFailuresTest.java b/src/test/java/org/apache/commons/jexl3/ParseFailuresTest.java
index 8c1ada7..643e54c 100644
--- a/src/test/java/org/apache/commons/jexl3/ParseFailuresTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ParseFailuresTest.java
@@ -50,7 +50,7 @@ public class ParseFailuresTest extends JexlTestCase {
                     + "\" should result in a JexlException");
         } catch (final JexlException pe) {
             // expected
-            LOGGER.info(pe);
+            LOGGER.debug(pe);
         }
     }
 
@@ -64,7 +64,7 @@ public class ParseFailuresTest extends JexlTestCase {
                     + "\" should result in a JexlException");
         } catch (final JexlException pe) {
             // expected
-            LOGGER.info(pe);
+            LOGGER.debug(pe);
         }
     }
 
@@ -78,7 +78,7 @@ public class ParseFailuresTest extends JexlTestCase {
                     + "\" should result in a JexlException");
         } catch (final JexlException pe) {
             // expected
-            LOGGER.info(pe);
+            LOGGER.debug(pe);
         }
     }
 
@@ -92,7 +92,7 @@ public class ParseFailuresTest extends JexlTestCase {
                     + "\" should result in a JexlException");
         } catch (final JexlException pe) {
             // expected
-            LOGGER.info(pe);
+            LOGGER.debug(pe);
         }
     }
 
@@ -106,7 +106,7 @@ public class ParseFailuresTest extends JexlTestCase {
                     + "\" should result in a JexlException");
         } catch (final JexlException pe) {
             // expected
-            LOGGER.error(pe);
+            LOGGER.debug(pe);
         }
     }
 
diff --git a/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java b/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
index 24190c4..2634281 100644
--- a/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
+++ b/src/test/java/org/apache/commons/jexl3/introspection/SandboxTest.java
@@ -17,16 +17,23 @@
 package org.apache.commons.jexl3.introspection;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.jexl3.JexlArithmetic;
 import org.apache.commons.jexl3.JexlBuilder;
 import org.apache.commons.jexl3.JexlContext;
 import org.apache.commons.jexl3.JexlEngine;
 import org.apache.commons.jexl3.JexlException;
+import org.apache.commons.jexl3.JexlExpression;
 import org.apache.commons.jexl3.JexlScript;
 import org.apache.commons.jexl3.JexlTestCase;
 import org.apache.commons.jexl3.MapContext;
 import org.apache.commons.jexl3.annotations.NoJexl;
 
+import org.apache.commons.jexl3.internal.introspection.Permissions;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -139,7 +146,7 @@ public class SandboxTest extends JexlTestCase {
             Assert.fail("ctor should not be accessible");
         } catch (final JexlException.Method xmethod) {
             // ok, ctor should not have been accessible
-            LOGGER.info(xmethod.toString());
+            LOGGER.debug(xmethod.toString());
         }
     }
 
@@ -162,7 +169,7 @@ public class SandboxTest extends JexlTestCase {
             Assert.fail("Quux should not be accessible");
         } catch (final JexlException.Method xmethod) {
             // ok, Quux should not have been accessible
-            LOGGER.info(xmethod.toString());
+            LOGGER.debug(xmethod.toString());
         }
     }
 
@@ -185,7 +192,7 @@ public class SandboxTest extends JexlTestCase {
             Assert.fail("alias should not be accessible");
         } catch (final JexlException.Property xvar) {
             // ok, alias should not have been accessible
-            LOGGER.info(xvar.toString());
+            LOGGER.debug(xvar.toString());
         }
     }
 
@@ -208,7 +215,7 @@ public class SandboxTest extends JexlTestCase {
             Assert.fail("alias should not be accessible");
         } catch (final JexlException.Property xvar) {
             // ok, alias should not have been accessible
-            LOGGER.info(xvar.toString());
+            LOGGER.debug(xvar.toString());
         }
     }
 
@@ -289,7 +296,7 @@ public class SandboxTest extends JexlTestCase {
                 Assert.fail("should have not been possible");
             } catch (JexlException.Method | JexlException.Property xjm) {
                 // ok
-                LOGGER.info(xjm.toString());
+                LOGGER.debug(xjm.toString());
             }
         }
     }
@@ -353,7 +360,7 @@ public class SandboxTest extends JexlTestCase {
             result = script.execute(context);
             Assert.fail("should not allow calling exit!");
         } catch (final JexlException xjexl) {
-            LOGGER.info(xjexl.toString());
+            LOGGER.debug(xjexl.toString());
         }
 
         script = sjexl.createScript("System.exit(1)");
@@ -361,7 +368,7 @@ public class SandboxTest extends JexlTestCase {
             result = script.execute(context);
             Assert.fail("should not allow calling exit!");
         } catch (final JexlException xjexl) {
-            LOGGER.info(xjexl.toString());
+            LOGGER.debug(xjexl.toString());
         }
 
         script = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')");
@@ -369,7 +376,7 @@ public class SandboxTest extends JexlTestCase {
             result = script.execute(context);
             Assert.fail("should not allow creating a file");
         } catch (final JexlException xjexl) {
-            LOGGER.info(xjexl.toString());
+            LOGGER.debug(xjexl.toString());
         }
 
         expr = "System.currentTimeMillis()";
@@ -450,7 +457,7 @@ public class SandboxTest extends JexlTestCase {
             Assert.fail("should not be possible");
         } catch (final JexlException xjm) {
             // ok
-            LOGGER.info(xjm.toString());
+            LOGGER.debug(xjm.toString());
         }
     }
 
@@ -488,4 +495,136 @@ public class SandboxTest extends JexlTestCase {
             Assert.assertNotNull(xany);
         }
     }
+
+    @Test
+    public void testGetNullKeyAllowed0() throws Exception {
+        JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create();
+        JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]");
+        Object o = expression.evaluate(null);
+        Assert.assertEquals("foo", o);
+    }
+
+    @Test
+    public void testGetNullKeyAllowed1() throws Exception {
+        JexlSandbox sandbox = new JexlSandbox(true, true);
+        JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
+        p.read().add("quux");
+        JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
+        // cant read quux
+        String q = "'quux'"; //quotes are important!
+        JexlExpression expression = jexl.createExpression("{"+q+" : 'foo'}["+q+"]");
+        try {
+            Object o = expression.evaluate(null);
+            Assert.fail("should have blocked " + q);
+        } catch (JexlException.Property xp) {
+            Assert.assertTrue(xp.getMessage().contains("undefined"));
+        }
+        // can read foo, null
+        for(String k : Arrays.asList("'foo'", "null")) {
+            expression = jexl.createExpression("{"+k+" : 'foo'}["+k+"]");
+            Object o = expression.evaluate(null);
+            Assert.assertEquals("foo", o);
+        }
+    }
+
+    @Test
+    public void testGetNullKeyBlocked() throws Exception {
+        JexlSandbox sandbox = new JexlSandbox(true, true);
+        JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
+        p.read().add(null);
+        p.read().add("quux");
+        // can read bar
+        JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
+        JexlExpression e0 = jexl.createExpression("{'bar' : 'foo'}['bar']");
+        Object r0 = e0.evaluate(null);
+        Assert.assertEquals("foo", r0);
+        // can not read quux, null
+        for(String k : Arrays.asList("'quux'", "null")) {
+            JexlExpression expression = jexl.createExpression("{"+k+" : 'foo'}["+k+"]");
+            try {
+                Object o = expression.evaluate(null);
+                Assert.fail("should have blocked " + k);
+            } catch (JexlException.Property xp) {
+                Assert.assertTrue(xp.getMessage().contains("undefined"));
+            }
+        }
+    }
+
+    public static class Arithmetic350 extends JexlArithmetic {
+        // cheat and keep the map builder around
+        MapBuilder mb = new org.apache.commons.jexl3.internal.MapBuilder(3);
+        public Arithmetic350(boolean astrict) {
+            super(astrict);
+        }
+        public MapBuilder mapBuilder(final int size) {
+            return mb;
+        }
+        Map<?,?> getLastMap() {
+            return (Map<Object,Object>) mb.create();
+        }
+    }
+
+    @Test
+    public void testSetNullKeyAllowed0() throws Exception {
+        Arithmetic350 a350 = new Arithmetic350(true);
+        JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(new JexlSandbox(true)).create();
+        JexlContext jc = new MapContext();
+        JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'");
+        expression.evaluate(jc);
+        Map<?,?> map = a350.getLastMap();
+        Assert.assertEquals("bar", map.get(null));
+    }
+
+    @Test
+    public void testSetNullKeyAllowed1() throws Exception {
+        Arithmetic350 a350 = new Arithmetic350(true);
+        JexlSandbox sandbox = new JexlSandbox(true, true);
+        JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
+        p.write().add("quux");
+        JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
+        // can not write quux
+        String q = "'quux'"; //quotes are important!
+        JexlExpression expression = jexl.createExpression("{"+q+" : 'foo'}["+q+"] = '42'");
+        try {
+            Object o = expression.evaluate(null);
+            Assert.fail("should have blocked " + q);
+        } catch (JexlException.Property xp) {
+            Assert.assertTrue(xp.getMessage().contains("undefined"));
+        }
+        // can write bar, null
+        expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
+        expression.evaluate(null);
+        Map<?, ?> map = a350.getLastMap();
+        Assert.assertEquals("42", map.get("bar"));
+        map.clear();
+        expression = jexl.createExpression("{null : 'foo'}[null] = '42'");
+        expression.evaluate(null);
+        map = a350.getLastMap();
+        Assert.assertEquals("42", map.get(null));
+    }
+
+    @Test
+    public void testSetNullKeyBlocked() throws Exception {
+        Arithmetic350 a350 = new Arithmetic350(true);
+        JexlSandbox sandbox = new JexlSandbox(true, true);
+        JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
+        p.write().add(null);
+        p.write().add("quux");
+        JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
+        // can write bar
+        JexlExpression expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
+        expression.evaluate(null);
+        Map<?,?> map = a350.getLastMap();
+        Assert.assertEquals("42", map.get("bar"));
+        // can not write quux, null
+        for(String k : Arrays.asList("'quux'", "null")) {
+            expression = jexl.createExpression("{"+k+" : 'foo'}["+k+"] = '42'");
+            try {
+                Object o = expression.evaluate(null);
+                Assert.fail("should have blocked " + k);
+            } catch (JexlException.Property xp) {
+                Assert.assertTrue(xp.getMessage().contains("undefined"));
+            }
+        }
+    }
 }