{"id":23799781,"url":"https://github.com/python-discord/snekbox","last_synced_at":"2025-04-07T11:10:36.303Z","repository":{"id":37335826,"uuid":"134289141","full_name":"python-discord/snekbox","owner":"python-discord","description":"Easy, safe evaluation of arbitrary Python code","archived":false,"fork":false,"pushed_at":"2024-04-09T14:18:24.000Z","size":1534,"stargazers_count":191,"open_issues_count":10,"forks_count":38,"subscribers_count":28,"default_branch":"main","last_synced_at":"2024-04-09T16:34:04.473Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://pythondiscord.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/python-discord.png","metadata":{"funding":{"patreon":"python_discord","custom":"https://www.redbubble.com/people/pythondiscord"},"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2018-05-21T15:38:24.000Z","updated_at":"2024-04-15T11:51:06.028Z","dependencies_parsed_at":"2023-10-03T08:46:12.824Z","dependency_job_id":"7470d103-0892-45d8-b875-d4a24b6226c7","html_url":"https://github.com/python-discord/snekbox","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-discord%2Fsnekbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-discord%2Fsnekbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-discord%2Fsnekbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/python-discord%2Fsnekbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/python-discord","download_url":"https://codeload.github.com/python-discord/snekbox/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247640465,"owners_count":20971557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-01T21:27:20.785Z","updated_at":"2025-04-07T11:10:36.279Z","avatar_url":"https://github.com/python-discord.png","language":"Python","funding_links":["https://patreon.com/python_discord","https://www.redbubble.com/people/pythondiscord"],"categories":[],"sub_categories":[],"readme":"[![Discord][5]][6]\n[![Build Status][1]][2]\n[![Coverage Status][3]][4]\n[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)\n\n# snekbox\n\nPython sandbox runners for executing code in isolation aka snekbox.\n\nSupports a memory [read/write file system](#memory-file-system) within the sandbox, allowing text or binary files to be sent and returned.\n\nA client sends Python code to a snekbox, the snekbox executes the code, and finally the results of the execution are returned to the client.\n\n```mermaid\n%%{init: { 'sequence': {'mirrorActors': false, 'messageFontWeight': 300, 'actorFontFamily': '-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif' } } }%%\nsequenceDiagram\n\nactor Client\nparticipant Snekbox\nparticipant NsJail\nparticipant Python as Python Subprocess\n\nClient -\u003e\u003e+ Snekbox: HTTP POST\nSnekbox -\u003e\u003e+ NsJail: Python code\nNsJail -\u003e\u003e+ Python: Python code\nPython --\u003e\u003e- NsJail: Execution result\nNsJail --\u003e\u003e- Snekbox: Execution result\nSnekbox --\u003e\u003e- Client: JSON response\n```\n\nThe code is executed in a Python process that is launched through [NsJail], which is responsible for sandboxing the Python process.\n\nThe output returned by snekbox is truncated at around 1 MB by default, but this can be [configured](#gunicorn).\n\n## HTTP REST API\n\nCommunication with snekbox is done over a HTTP REST API. The framework for the HTTP REST API is [Falcon] and the WSGI being used is [Gunicorn]. By default, the server is hosted on `0.0.0.0:8060` with two workers.\n\nSee [`snekapi.py`] and [`resources`] for API documentation.\n\n## Running snekbox\n\nA Docker image is available in the [GitHub Container Registry]. A container can be started with the following command, which will also pull the image if it doesn't currently exist locally:\n\n```\ndocker run --ipc=none --privileged -p 8060:8060 ghcr.io/python-discord/snekbox\n```\n\nTo run it in the background, use the `-d` option. See the documentation on [`docker run`] for more information.\n\nThe above command will make the API accessible on the host via `http://localhost:8060/`. Currently, there's only one endpoint: `http://localhost:8060/eval`.\n\n### Python multi-version support\n\nBy default, the executable that runs within nsjail is defined by `DEFAULT_EXECUTABLE_PATH` at the top of [`nsjail.py`]. This can be overridden by specifying `executable_path` in the request body of calls to `POST /eval` or by setting the `executable_path` kwarg if calling `NSJail.python3()` directly.\n\nAny executable that exists within the container is a valid value for `executable_path`. The main use case of this feature is currently to specify the version of Python to use.\n\nPython versions currently available can be found in the [`Dockerfile`] by looking for build stages that match `builder-py-*`. These binaries are then copied into the `base` build stage further down.\n\n## Configuration\n\nConfiguration files can be edited directly. However, this requires rebuilding the image. Alternatively, a Docker volume or bind mounts can be used to override the configuration files at their default locations.\n\n### NsJail\n\nThe main features of the default configuration are:\n\n* Time limit\n* Memory limit\n* Process count limit\n* No networking\n* Restricted, read-only system filesystem\n* Memory-based read-write filesystem mounted as working directory `/home`\n\nNsJail is configured through [`snekbox.cfg`]. It contains the exact values for the items listed above. The configuration format is defined by a [protobuf file][7] which can be referred to for documentation. The command-line options of NsJail can also serve as documentation since they closely follow the config file format.\n\n### Memory File System\n\nOn each execution, the host will mount an instance-specific `tmpfs` drive, this is used as a limited read-write folder for the sandboxed code. There is no access to other files or directories on the host container beyond the other read-only mounted system folders. Instance file systems are isolated; it is not possible for sandboxed code to access another instance's writeable directory.\n\nThe following options for the memory file system are configurable as options in [gunicorn.conf.py](config/gunicorn.conf.py)\n\n* `memfs_instance_size` Size in bytes for the capacity of each instance file system.\n* `memfs_home` Path to the home directory within the instance file system.\n* `memfs_output` Path to the output directory within the instance file system.\n* `files_limit` Maximum number of valid output files to parse.\n* `files_timeout` Maximum time in seconds for output file parsing and encoding.\n* `files_pattern` Glob pattern to match files within `output`.\n\nThe sandboxed code execution will start with a writeable working directory of `home`. By default, the output folder is also `home`. New files, and uploaded files with a newer last modified time, will be uploaded on completion.\n\n### Gunicorn\n\n[Gunicorn settings] can be found in [`gunicorn.conf.py`]. In the default configuration, the worker count, the bind address, and the WSGI app URI are likely the only things of any interest. Since it uses the default synchronous workers, the [worker count] effectively determines how many concurrent code evaluations can be performed.\n\n`wsgi_app` can be given arguments which are forwarded to the [`NsJail`] object. For example, `wsgi_app = \"snekbox:SnekAPI(max_output_size=2_000_000, read_chunk_size=20_000)\"`.\n\n### Environment Variables\n\nAll environment variables have defaults and are therefore not required to be set.\n\nName | Description\n---- | -----------\n`SNEKBOX_DEBUG` | Enable debug logging if set to a non-empty value.\n`SNEKBOX_SENTRY_DSN` | [Data Source Name] for Sentry. Sentry is disabled if left unset.\n\n## Third-party Packages\n\nBy default, the Python interpreter has no access to any packages besides the  standard library. Even snekbox's own dependencies like Falcon and Gunicorn are not exposed.\n\nTo expose third-party Python packages during evaluation, install them to a custom user site:\n\n```sh\ndocker exec snekbox /bin/sh -c \\\n    'PYTHONUSERBASE=/snekbox/user_base /snekbin/python/default/bin/python -m pip install --user numpy'\n```\n\nIn the above command, `snekbox` is the name of the running container. The name may be different and can be checked with `docker ps`.\n\nThe packages will be installed to the user site within `/snekbox/user_base`. To persist the installed packages, a volume for the directory can be created with Docker. For an example, see [`docker-compose.yml`].\n\n## Development Environment\n\nSee [CONTRIBUTING.md](.github/CONTRIBUTING.md).\n\n\n[1]: https://github.com/python-discord/snekbox/workflows/main/badge.svg?branch=main\n[2]: https://github.com/python-discord/snekbox/actions/workflows/main.yaml?query=event%3Apush+branch%3Amain\n[3]: https://coveralls.io/repos/github/python-discord/snekbox/badge.svg?branch=main\n[4]: https://coveralls.io/github/python-discord/snekbox?branch=main\n[5]: https://raw.githubusercontent.com/python-discord/branding/main/logos/badge/badge_github.svg\n[6]: https://discord.gg/python\n[7]: https://github.com/google/nsjail/blob/master/config.proto\n[`gunicorn.conf.py`]: config/gunicorn.conf.py\n[`snekbox.cfg`]: config/snekbox.cfg\n[`nsjail.py`]: snekbox/nsjail.py\n[`snekapi.py`]: snekbox/api/snekapi.py\n[`resources`]: snekbox/api/resources\n[`docker-compose.yml`]: docker-compose.yml\n[`Dockerfile`]: Dockerfile\n[`docker run`]: https://docs.docker.com/engine/reference/commandline/run/\n[nsjail]: https://github.com/google/nsjail\n[falcon]: https://falconframework.org/\n[gunicorn]: https://gunicorn.org/\n[gunicorn settings]: https://docs.gunicorn.org/en/latest/settings.html\n[worker count]: https://docs.gunicorn.org/en/latest/design.html#how-many-workers\n[sentry release]: https://docs.sentry.io/platforms/python/configuration/releases/\n[data source name]: https://docs.sentry.io/product/sentry-basics/dsn-explainer/\n[GitHub Container Registry]: https://github.com/orgs/python-discord/packages/container/package/snekbox\n[`NsJail`]: snekbox/nsjail.py\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-discord%2Fsnekbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpython-discord%2Fsnekbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpython-discord%2Fsnekbox/lists"}