Mcp Sandbox — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Mcp Sandbox (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<!-- SPDX-FileCopyrightText: 2026 Peter Lemenkov <[email protected]> SPDX-License-Identifier: Apache-2.0 -->
MCP server for isolated scientific script execution. Run R, Python, GMT, GRASS GIS, LaTeX, Octave, Julia, and more in disposable containers — fully stateless, no host filesystem access.
| Tool | Description |
|---|---|
run_script | Execute a script in an isolated sandbox container |
list_runtimes | List available scientific runtimes |
| Runtime | Tools |
|---|---|
r | R + ggplot2, dplyr, tidyr, jsonlite |
python | Python 3 + numpy, scipy, pandas, matplotlib, sympy, scikit-learn, geopandas, pyproj, rasterio, obspy |
gmt | Generic Mapping Tools |
grass | GRASS GIS — headless; each run gets a throwaway location |
latex | pdflatex (texlive) |
octave | GNU Octave |
julia | Julia |
gnuplot | Gnuplot |
bash | Bash scripts |
--rm)/sandbox/input/ (base64)/sandbox/output/, returned by reference: acapability URL plus metadata (name, bytes, SHA-256, MIME, expiry). Small text outputs (< 4 KB) are still inlined. See "Output handling" below.
The sandbox runs containers rootless, as a dedicated `sandbox` user. The image and the running service must live in that user's podman store. This is the single most important thing to get right — see Troubleshooting.
useradd -r -m -d /var/lib/mcp-sandbox -s /bin/bash sandbox
echo "sandbox:100000:65536" >> /etc/subuid
echo "sandbox:100000:65536" >> /etc/subgid
# rootless podman needs a runtime dir (/run/user/<uid>) present at boot:
loginctl enable-linger sandboxBuild into the sandbox user's rootless store. Not your login user's, not root's — the server only ever reads sandbox's store:
sudo -u sandbox -H podman build -t mcp-sandbox:latest -f Containerfile .Already built the image elsewhere? Don't rebuild — transfer it across the store boundary:
podman save mcp-sandbox:latest | sudo -u sandbox -H podman loadpython3 -m venv venv
venv/bin/pip install -e .Run the server as sandbox so it reads that user's image store. Minimal unit (adjust the checkout path and entrypoint to your install):
# /etc/systemd/system/mcp-sandbox.service
[Unit]
Description=mcp-sandbox MCP server
After=network.target
[Service]
User=sandbox
ExecStart=/var/lib/mcp-sandbox/mcp-sandbox/venv/bin/mcp-sandbox
Restart=on-failure
[Install]
WantedBy=multi-user.targetThe image is the single source of truth for what a runtime can do. Because every run is a fresh --rm container, a runtime pip install inside a sandbox does not persist — it re-downloads and rebuilds on every invocation, and on current Fedora it hits PEP 668 (externally-managed-environment) anyway. So:
python3-* (or R-*, etc.) RPM to theContainerfile and rebuild the image — don't pip-install at runtime. Prefer Fedora RPMs; they keep the image reproducible and versioned in git.
RUN pip install … layer to theContainerfile, baked into the image — never a runtime install in the sandbox.
Rebuild after any change (as the sandbox user — see Install/Troubleshooting):
sudo -u sandbox -H podman build -t mcp-sandbox:latest -f Containerfile .
Files written to /sandbox/output/ are returned by reference, not inlined as base64. After a run, the server stages each output under a fresh unguessable token and returns a compact metadata record in place of the bytes:
{ "name": "figure.png", "bytes": 29901, "sha256": "…", "mime": "image/png", "url": "https://host.example/sandbox-out/<token>/figure.png", "expires_in": 3600 }
/<token>/<name>, where <token>is ~256 bits from a CSPRNG (secrets.token_urlsafe(32)). Possessing the URL is the authorization to fetch it — there is no separate credential.
round-trip to read three lines isn't worth it. Everything else is referenced.
which the GC timer deletes them and the URL 404s — this bounds the exposure window of any leaked URL.
sha256 lets a consumer verify the bytes, or confirm afile's identity without fetching it.
nginx serves the staging directory read-only, with listing, logging, and auth off (the token is the authentication):
location /sandbox-out/ { alias /var/cache/mcp-sandbox/outputs/; auth_basic off; # the capability token is the authorization autoindex off; # non-enumerable: no directory listing access_log off; # keep tokens out of logs add_header X-Robots-Tag "noindex" always; }
On SELinux, the staging dir must carry httpd_sys_content_t, applied before it is populated:
semanage fcontext -a -t httpd_sys_content_t "/var/cache/mcp-sandbox/outputs(/.*)?" restorecon -RFv /var/cache/mcp-sandbox/outputs
Expiry is a systemd .timer firing every 15 min that deletes token directories older than the TTL.
HOST=127.0.0.1
PORT=8815
SANDBOX_IMAGE=mcp-sandbox:latest
DEFAULT_TIMEOUT=60
MAX_TIMEOUT=300
# Output-by-reference — REPLACE names with the actual ones from the code:
SANDBOX_PUBLIC_URL=https://host.example # base for capability links
OUTPUT_DIR=/var/cache/mcp-sandbox/outputs # staging directory
OUTPUT_TTL=3600 # seconds; keep in sync with GC timerRebuilt the image but the server still runs the old one? You almost certainly built into the wrong podman store. Containers run rootless as sandbox, which reads its own store under /var/lib/mcp-sandbox/.local/share/containers. An image built as your login user or as root lands in a different store the server never reads — so the build "succeeds" and changes nothing.
Confirm what the service actually sees, and rebuild/load into that store:
sudo -u sandbox -H podman images mcp-sandbox
sudo -u sandbox -H podman build -t mcp-sandbox:latest -f Containerfile .
# or: podman save mcp-sandbox:latest | sudo -u sandbox -H podman loadNo MCP restart is needed afterwards — each run is a fresh --rm container that resolves mcp-sandbox:latest from the sandbox store at launch. Restart the service only when you change the server code or RUNTIMES.
Apache-2.0
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.