How to Sandbox Python in Pure Python

How can I sandbox Python in pure Python?

This is really non-trivial.

There are two ways to sandbox Python. One is to create a restricted environment (i.e., very few globals etc.) and exec your code inside this environment. This is what Messa is suggesting. It's nice but there are lots of ways to break out of the sandbox and create trouble. There was a thread about this on Python-dev a year ago or so in which people did things from catching exceptions and poking at internal state to break out to byte code manipulation. This is the way to go if you want a complete language.

The other way is to parse the code and then use the ast module to kick out constructs you don't want (e.g. import statements, function calls etc.) and then to compile the rest. This is the way to go if you want to use Python as a config language etc.

Another way (which might not work for you since you're using GAE), is the PyPy sandbox. While I haven't used it myself, word on the intertubes is that it's the only real sandboxed Python out there.

Based on your description of the requirements (The requirements are support for variables, basic conditionals and function calls (not definitions)) , you might want to evaluate approach 2 and kick out everything else from the code. It's a little tricky but doable.

sandbox to execute possibly unfriendly python code

You can check pysandbox which does just that, though the VM route is probably safer if you can afford it.

sandboxing/running python code line by line

After my initial success with sys.settrace(), I ended up switching to the ast module (abstract syntax trees). I parse the code I want to analyse and then insert new calls after each assignment to report on the variable name and its new value. I also insert calls to report on loop iterations and function calls. Then I execute the modified tree.

        tree = parse(source)

visitor = TraceAssignments()
new_tree = visitor.visit(tree)
fix_missing_locations(new_tree)

code = compile(new_tree, PSEUDO_FILENAME, 'exec')

self.environment[CONTEXT_NAME] = builder
exec code in self.environment

I'm working on a live coding tool like Bret Victor's, and you can see my working code on GitHub, and some examples of how it behaves in the test. You can also find links to a demo video, tutorial, and downloads from the project page.

Possible to sandbox Python configuration file?

We do this for some of our internal tools

What we do protects us from exception issues and discourages any attempts by the users to get overly creative in the config scripts. However it doesn't protect us from infinite loops or actively malicious third parties.

The core of the approach here is to run the script in a locked down exec.

  1. First we go through the __ builtin __ module and del everything we don't want them to be able to touch, especially __ import __. We actually do this in a context manager which backs the original values up and dels them on the way in and then restores the original values on the way back out.

  2. Next we create an empty dictionary to be the config scripts namespace.

  3. Then we exec the config with the namespace.

  4. The exec is of course wrapped in a try except that will catch anything.

  5. And finally we inspect the namespace to extract the variables we are interested in.

Points to note here:

  1. It might be tempting to prepopulate the namespace with stuff that might be useful to the config script, but you want to be very careful doing that you quickly open up hooks back into the host program.

  2. The config scripts can still create functions and classes so you might get back something that looks like a string for example, but is actually an arbitrary blob of executable code.

Because of these we impose the restriction that our config scripts are expected to produce pure primitive data structures (generally just ints, strings, lists, tuples and None) that we then separately verify.

Is there a way to sandbox test execution with pytest, especially filesystem access?

After quite a bit of research I didn't find any ready-made way for pytest to run a project tests with OS-level isolation and in a disposable environment. Many approaches are possible and have advantages and disadvantages, but most of them have more moving parts that I would feel comfortable with.

The absolute minimal (but opinionated) approach I devised is the following:

  • build a python docker image with:

    • a dedicated non-root user: pytest
    • all project dependencies from requirements.txt
    • the project installed in develop mode
  • run py.test in a container that mounts the project folder on the host as the home of pytest user

To implement the approach add the following Dockerfile to the top folder of the project you want to test next to the requirements.txt and setup.py files:

FROM python:3

# setup pytest user
RUN adduser --disabled-password --gecos "" --uid 7357 pytest
COPY ./ /home/pytest
WORKDIR /home/pytest

# setup the python and pytest environments
RUN pip install --upgrade pip setuptools pytest
RUN pip install --upgrade -r requirements.txt
RUN python setup.py develop

# setup entry point
USER pytest
ENTRYPOINT ["py.test"]

Build the image once with:

docker build -t pytest .

Run py.test inside the container mounting the project folder as volume on /home/pytest with:

docker run --rm -it -v `pwd`:/home/pytest pytest [USUAL_PYTEST_OPTIONS]

Note that -v mounts the volume as uid 1000 so host files are not writable by the pytest user with uid forced to 7357.

Now you should be able to develop and test your project with OS-level isolation.

Update: If you also run the test on the host you may need to remove the python and pytest caches that are not writable inside the container. On the host run:

rm -rf .cache/ && find . -name __pycache__  | xargs rm -rf


Related Topics



Leave a reply



Submit